/src/capstonev5/arch/BPF/BPFDisassembler.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Capstone Disassembly Engine */ |
2 | | /* BPF Backend by david942j <david942j@gmail.com>, 2019 */ |
3 | | |
4 | | #ifdef CAPSTONE_HAS_BPF |
5 | | |
6 | | #include <string.h> |
7 | | #include <stddef.h> // offsetof macro |
8 | | |
9 | | #include "BPFConstants.h" |
10 | | #include "BPFDisassembler.h" |
11 | | #include "BPFMapping.h" |
12 | | #include "../../cs_priv.h" |
13 | | |
14 | | static uint16_t read_u16(cs_struct *ud, const uint8_t *code) |
15 | 47.3k | { |
16 | 47.3k | if (MODE_IS_BIG_ENDIAN(ud->mode)) |
17 | 25.9k | return (((uint16_t)code[0] << 8) | code[1]); |
18 | 21.4k | else |
19 | 21.4k | return (((uint16_t)code[1] << 8) | code[0]); |
20 | 47.3k | } |
21 | | |
22 | | static uint32_t read_u32(cs_struct *ud, const uint8_t *code) |
23 | 16.1k | { |
24 | 16.1k | if (MODE_IS_BIG_ENDIAN(ud->mode)) |
25 | 8.79k | return ((uint32_t)read_u16(ud, code) << 16) | read_u16(ud, code + 2); |
26 | 7.33k | else |
27 | 7.33k | return ((uint32_t)read_u16(ud, code + 2) << 16) | read_u16(ud, code); |
28 | 16.1k | } |
29 | | |
30 | | ///< Malloc bpf_internal, also checks if code_len is large enough. |
31 | | static bpf_internal *alloc_bpf_internal(size_t code_len) |
32 | 15.8k | { |
33 | 15.8k | bpf_internal *bpf; |
34 | | |
35 | 15.8k | if (code_len < 8) |
36 | 251 | return NULL; |
37 | 15.6k | bpf = cs_mem_malloc(sizeof(bpf_internal)); |
38 | 15.6k | if (bpf == NULL) |
39 | 0 | return NULL; |
40 | | /* default value */ |
41 | 15.6k | bpf->insn_size = 8; |
42 | 15.6k | return bpf; |
43 | 15.6k | } |
44 | | |
45 | | ///< Fetch a cBPF structure from code |
46 | | static bpf_internal* fetch_cbpf(cs_struct *ud, const uint8_t *code, |
47 | | size_t code_len) |
48 | 6.25k | { |
49 | 6.25k | bpf_internal *bpf; |
50 | | |
51 | 6.25k | bpf = alloc_bpf_internal(code_len); |
52 | 6.25k | if (bpf == NULL) |
53 | 88 | return NULL; |
54 | | |
55 | 6.17k | bpf->op = read_u16(ud, code); |
56 | 6.17k | bpf->jt = code[2]; |
57 | 6.17k | bpf->jf = code[3]; |
58 | 6.17k | bpf->k = read_u32(ud, code + 4); |
59 | 6.17k | return bpf; |
60 | 6.25k | } |
61 | | |
62 | | ///< Fetch an eBPF structure from code |
63 | | static bpf_internal* fetch_ebpf(cs_struct *ud, const uint8_t *code, |
64 | | size_t code_len) |
65 | 9.60k | { |
66 | 9.60k | bpf_internal *bpf; |
67 | | |
68 | 9.60k | bpf = alloc_bpf_internal(code_len); |
69 | 9.60k | if (bpf == NULL) |
70 | 163 | return NULL; |
71 | | |
72 | 9.44k | bpf->op = (uint16_t)code[0]; |
73 | 9.44k | bpf->dst = code[1] & 0xf; |
74 | 9.44k | bpf->src = (code[1] & 0xf0) >> 4; |
75 | | |
76 | | // eBPF has one 16-byte instruction: BPF_LD | BPF_DW | BPF_IMM, |
77 | | // in this case imm is combined with the next block's imm. |
78 | 9.44k | if (bpf->op == (BPF_CLASS_LD | BPF_SIZE_DW | BPF_MODE_IMM)) { |
79 | 521 | if (code_len < 16) { |
80 | 2 | cs_mem_free(bpf); |
81 | 2 | return NULL; |
82 | 2 | } |
83 | 519 | bpf->k = read_u32(ud, code + 4) | (((uint64_t)read_u32(ud, code + 12)) << 32); |
84 | 519 | bpf->insn_size = 16; |
85 | 519 | } |
86 | 8.92k | else { |
87 | 8.92k | bpf->offset = read_u16(ud, code + 2); |
88 | 8.92k | bpf->k = read_u32(ud, code + 4); |
89 | 8.92k | } |
90 | 9.43k | return bpf; |
91 | 9.44k | } |
92 | | |
93 | 5.12k | #define CHECK_READABLE_REG(ud, reg) do { \ |
94 | 5.12k | if (! ((reg) >= BPF_REG_R0 && (reg) <= BPF_REG_R10)) \ |
95 | 5.12k | return false; \ |
96 | 5.12k | } while (0) |
97 | | |
98 | 3.96k | #define CHECK_WRITABLE_REG(ud, reg) do { \ |
99 | 3.96k | if (! ((reg) >= BPF_REG_R0 && (reg) < BPF_REG_R10)) \ |
100 | 3.96k | return false; \ |
101 | 3.96k | } while (0) |
102 | | |
103 | 5.12k | #define CHECK_READABLE_AND_PUSH(ud, MI, r) do { \ |
104 | 5.12k | CHECK_READABLE_REG(ud, r + BPF_REG_R0); \ |
105 | 5.12k | MCOperand_CreateReg0(MI, r + BPF_REG_R0); \ |
106 | 5.07k | } while (0) |
107 | | |
108 | 3.96k | #define CHECK_WRITABLE_AND_PUSH(ud, MI, r) do { \ |
109 | 3.96k | CHECK_WRITABLE_REG(ud, r + BPF_REG_R0); \ |
110 | 3.96k | MCOperand_CreateReg0(MI, r + BPF_REG_R0); \ |
111 | 3.95k | } while (0) |
112 | | |
113 | | static bool decodeLoad(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
114 | 4.93k | { |
115 | 4.93k | if (!EBPF_MODE(ud)) { |
116 | | /* |
117 | | * +-----+-----------+--------------------+ |
118 | | * | ldb | [k] | [x+k] | |
119 | | * | ldh | [k] | [x+k] | |
120 | | * +-----+-----------+--------------------+ |
121 | | */ |
122 | 2.40k | if (BPF_SIZE(bpf->op) == BPF_SIZE_DW) |
123 | 6 | return false; |
124 | 2.39k | if (BPF_SIZE(bpf->op) == BPF_SIZE_B || BPF_SIZE(bpf->op) == BPF_SIZE_H) { |
125 | | /* no ldx */ |
126 | 676 | if (BPF_CLASS(bpf->op) != BPF_CLASS_LD) |
127 | 2 | return false; |
128 | | /* can only be BPF_ABS and BPF_IND */ |
129 | 674 | if (BPF_MODE(bpf->op) == BPF_MODE_ABS) { |
130 | 231 | MCOperand_CreateImm0(MI, bpf->k); |
131 | 231 | return true; |
132 | 231 | } |
133 | 443 | else if (BPF_MODE(bpf->op) == BPF_MODE_IND) { |
134 | 439 | MCOperand_CreateReg0(MI, BPF_REG_X); |
135 | 439 | MCOperand_CreateImm0(MI, bpf->k); |
136 | 439 | return true; |
137 | 439 | } |
138 | 4 | return false; |
139 | 674 | } |
140 | | /* |
141 | | * +-----+----+------+------+-----+-------+ |
142 | | * | ld | #k | #len | M[k] | [k] | [x+k] | |
143 | | * +-----+----+------+------+-----+-------+ |
144 | | * | ldx | #k | #len | M[k] | 4*([k]&0xf) | |
145 | | * +-----+----+------+------+-------------+ |
146 | | */ |
147 | 1.72k | switch (BPF_MODE(bpf->op)) { |
148 | 784 | default: |
149 | 784 | break; |
150 | 784 | case BPF_MODE_IMM: |
151 | 521 | MCOperand_CreateImm0(MI, bpf->k); |
152 | 521 | return true; |
153 | 216 | case BPF_MODE_LEN: |
154 | 216 | return true; |
155 | 201 | case BPF_MODE_MEM: |
156 | 201 | MCOperand_CreateImm0(MI, bpf->k); |
157 | 201 | return true; |
158 | 1.72k | } |
159 | 784 | if (BPF_CLASS(bpf->op) == BPF_CLASS_LD) { |
160 | 495 | if (BPF_MODE(bpf->op) == BPF_MODE_ABS) { |
161 | 296 | MCOperand_CreateImm0(MI, bpf->k); |
162 | 296 | return true; |
163 | 296 | } |
164 | 199 | else if (BPF_MODE(bpf->op) == BPF_MODE_IND) { |
165 | 197 | MCOperand_CreateReg0(MI, BPF_REG_X); |
166 | 197 | MCOperand_CreateImm0(MI, bpf->k); |
167 | 197 | return true; |
168 | 197 | } |
169 | 495 | } |
170 | 289 | else { /* LDX */ |
171 | 289 | if (BPF_MODE(bpf->op) == BPF_MODE_MSH) { |
172 | 287 | MCOperand_CreateImm0(MI, bpf->k); |
173 | 287 | return true; |
174 | 287 | } |
175 | 289 | } |
176 | 4 | return false; |
177 | 784 | } |
178 | | |
179 | | /* eBPF mode */ |
180 | | /* |
181 | | * - IMM: lddw dst, imm64 |
182 | | * - ABS: ld{w,h,b,dw} [k] |
183 | | * - IND: ld{w,h,b,dw} [src+k] |
184 | | * - MEM: ldx{w,h,b,dw} dst, [src+off] |
185 | | */ |
186 | 2.52k | if (BPF_CLASS(bpf->op) == BPF_CLASS_LD) { |
187 | 2.04k | switch (BPF_MODE(bpf->op)) { |
188 | 526 | case BPF_MODE_IMM: |
189 | 526 | if (bpf->op != (BPF_CLASS_LD | BPF_SIZE_DW | BPF_MODE_IMM)) |
190 | 7 | return false; |
191 | 519 | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
192 | 516 | MCOperand_CreateImm0(MI, bpf->k); |
193 | 516 | return true; |
194 | 1.00k | case BPF_MODE_ABS: |
195 | 1.00k | MCOperand_CreateImm0(MI, bpf->k); |
196 | 1.00k | return true; |
197 | 500 | case BPF_MODE_IND: |
198 | 500 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
199 | 499 | MCOperand_CreateImm0(MI, bpf->k); |
200 | 499 | return true; |
201 | 2.04k | } |
202 | 5 | return false; |
203 | | |
204 | 2.04k | } |
205 | | /* LDX */ |
206 | 487 | if (BPF_MODE(bpf->op) == BPF_MODE_MEM) { |
207 | 481 | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
208 | 480 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
209 | 479 | MCOperand_CreateImm0(MI, bpf->offset); |
210 | 479 | return true; |
211 | 480 | } |
212 | 6 | return false; |
213 | 487 | } |
214 | | |
215 | | static bool decodeStore(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
216 | 1.77k | { |
217 | | /* in cBPF, only BPF_ST* | BPF_MEM | BPF_W is valid |
218 | | * while in eBPF: |
219 | | * - BPF_STX | BPF_XADD | BPF_{W,DW} |
220 | | * - BPF_ST* | BPF_MEM | BPF_{W,H,B,DW} |
221 | | * are valid |
222 | | */ |
223 | 1.77k | if (!EBPF_MODE(ud)) { |
224 | | /* can only store to M[] */ |
225 | 312 | if (bpf->op != (BPF_CLASS(bpf->op) | BPF_MODE_MEM | BPF_SIZE_W)) |
226 | 14 | return false; |
227 | 298 | MCOperand_CreateImm0(MI, bpf->k); |
228 | 298 | return true; |
229 | 312 | } |
230 | | |
231 | | /* eBPF */ |
232 | | |
233 | 1.46k | if (BPF_MODE(bpf->op) == BPF_MODE_XADD) { |
234 | 312 | if (BPF_CLASS(bpf->op) != BPF_CLASS_STX) |
235 | 1 | return false; |
236 | 311 | if (BPF_SIZE(bpf->op) != BPF_SIZE_W && BPF_SIZE(bpf->op) != BPF_SIZE_DW) |
237 | 1 | return false; |
238 | | /* xadd [dst + off], src */ |
239 | 310 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
240 | 309 | MCOperand_CreateImm0(MI, bpf->offset); |
241 | 309 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
242 | 307 | return true; |
243 | 309 | } |
244 | | |
245 | 1.14k | if (BPF_MODE(bpf->op) != BPF_MODE_MEM) |
246 | 9 | return false; |
247 | | |
248 | | /* st [dst + off], src */ |
249 | 1.13k | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
250 | 1.13k | MCOperand_CreateImm0(MI, bpf->offset); |
251 | 1.13k | if (BPF_CLASS(bpf->op) == BPF_CLASS_ST) |
252 | 689 | MCOperand_CreateImm0(MI, bpf->k); |
253 | 447 | else |
254 | 447 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
255 | 1.13k | return true; |
256 | 1.13k | } |
257 | | |
258 | | static bool decodeALU(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
259 | 4.27k | { |
260 | | /* Set MI->Operands */ |
261 | | |
262 | | /* cBPF */ |
263 | 4.27k | if (!EBPF_MODE(ud)) { |
264 | 1.27k | if (BPF_OP(bpf->op) > BPF_ALU_XOR) |
265 | 1 | return false; |
266 | | /* cBPF's NEG has no operands */ |
267 | 1.27k | if (BPF_OP(bpf->op) == BPF_ALU_NEG) |
268 | 202 | return true; |
269 | 1.07k | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
270 | 526 | MCOperand_CreateImm0(MI, bpf->k); |
271 | 544 | else /* BPF_SRC_X */ |
272 | 544 | MCOperand_CreateReg0(MI, BPF_REG_X); |
273 | 1.07k | return true; |
274 | 1.27k | } |
275 | | |
276 | | /* eBPF */ |
277 | | |
278 | 2.99k | if (BPF_OP(bpf->op) > BPF_ALU_END) |
279 | 2 | return false; |
280 | | /* ALU64 class doesn't have ENDian */ |
281 | | /* ENDian's imm must be one of 16, 32, 64 */ |
282 | 2.99k | if (BPF_OP(bpf->op) == BPF_ALU_END) { |
283 | 276 | if (BPF_CLASS(bpf->op) == BPF_CLASS_ALU64) |
284 | 1 | return false; |
285 | 275 | if (bpf->k != 16 && bpf->k != 32 && bpf->k != 64) |
286 | 34 | return false; |
287 | 275 | } |
288 | | |
289 | | /* - op dst, imm |
290 | | * - op dst, src |
291 | | * - neg dst |
292 | | * - le<imm> dst |
293 | | */ |
294 | | /* every ALU instructions have dst op */ |
295 | 2.96k | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
296 | | |
297 | | /* special cases */ |
298 | 2.95k | if (BPF_OP(bpf->op) == BPF_ALU_NEG) |
299 | 356 | return true; |
300 | 2.60k | if (BPF_OP(bpf->op) == BPF_ALU_END) { |
301 | | /* bpf->k must be one of 16, 32, 64 */ |
302 | 241 | MCInst_setOpcode(MI, MCInst_getOpcode(MI) | ((uint32_t)bpf->k << 4)); |
303 | 241 | return true; |
304 | 241 | } |
305 | | |
306 | | /* normal cases */ |
307 | 2.36k | if (BPF_SRC(bpf->op) == BPF_SRC_K) { |
308 | 2.09k | MCOperand_CreateImm0(MI, bpf->k); |
309 | 2.09k | } |
310 | 272 | else { /* BPF_SRC_X */ |
311 | 272 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
312 | 272 | } |
313 | 2.36k | return true; |
314 | 2.36k | } |
315 | | |
316 | | static bool decodeJump(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
317 | 3.54k | { |
318 | | /* cBPF and eBPF are very different in class jump */ |
319 | 3.54k | if (!EBPF_MODE(ud)) { |
320 | 1.10k | if (BPF_OP(bpf->op) > BPF_JUMP_JSET) |
321 | 3 | return false; |
322 | | |
323 | | /* ja is a special case of jumps */ |
324 | 1.09k | if (BPF_OP(bpf->op) == BPF_JUMP_JA) { |
325 | 204 | MCOperand_CreateImm0(MI, bpf->k); |
326 | 204 | return true; |
327 | 204 | } |
328 | | |
329 | 893 | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
330 | 395 | MCOperand_CreateImm0(MI, bpf->k); |
331 | 498 | else /* BPF_SRC_X */ |
332 | 498 | MCOperand_CreateReg0(MI, BPF_REG_X); |
333 | 893 | MCOperand_CreateImm0(MI, bpf->jt); |
334 | 893 | MCOperand_CreateImm0(MI, bpf->jf); |
335 | 893 | } |
336 | 2.44k | else { |
337 | 2.44k | if (BPF_OP(bpf->op) > BPF_JUMP_JSLE) |
338 | 1 | return false; |
339 | | |
340 | | /* No operands for exit */ |
341 | 2.44k | if (BPF_OP(bpf->op) == BPF_JUMP_EXIT) |
342 | 266 | return bpf->op == (BPF_CLASS_JMP | BPF_JUMP_EXIT); |
343 | 2.17k | if (BPF_OP(bpf->op) == BPF_JUMP_CALL) { |
344 | 603 | if (bpf->op == (BPF_CLASS_JMP | BPF_JUMP_CALL)) { |
345 | 558 | MCOperand_CreateImm0(MI, bpf->k); |
346 | 558 | return true; |
347 | 558 | } |
348 | 45 | if (bpf->op == (BPF_CLASS_JMP | BPF_JUMP_CALL | BPF_SRC_X)) { |
349 | 45 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->k); |
350 | 12 | return true; |
351 | 45 | } |
352 | 0 | return false; |
353 | 45 | } |
354 | | |
355 | | /* ja is a special case of jumps */ |
356 | 1.57k | if (BPF_OP(bpf->op) == BPF_JUMP_JA) { |
357 | 278 | if (BPF_SRC(bpf->op) != BPF_SRC_K) |
358 | 1 | return false; |
359 | 277 | MCOperand_CreateImm0(MI, bpf->offset); |
360 | 277 | return true; |
361 | 278 | } |
362 | | |
363 | | /* <j> dst, src, +off */ |
364 | 1.29k | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
365 | 1.29k | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
366 | 969 | MCOperand_CreateImm0(MI, bpf->k); |
367 | 326 | else |
368 | 326 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
369 | 1.29k | MCOperand_CreateImm0(MI, bpf->offset); |
370 | 1.29k | } |
371 | 2.18k | return true; |
372 | 3.54k | } |
373 | | |
374 | | static bool decodeReturn(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
375 | 924 | { |
376 | | /* Here only handles the BPF_RET class in cBPF */ |
377 | 924 | switch (BPF_RVAL(bpf->op)) { |
378 | 384 | case BPF_SRC_K: |
379 | 384 | MCOperand_CreateImm0(MI, bpf->k); |
380 | 384 | return true; |
381 | 282 | case BPF_SRC_X: |
382 | 282 | MCOperand_CreateReg0(MI, BPF_REG_X); |
383 | 282 | return true; |
384 | 257 | case BPF_SRC_A: |
385 | 257 | MCOperand_CreateReg0(MI, BPF_REG_A); |
386 | 257 | return true; |
387 | 924 | } |
388 | 1 | return false; |
389 | 924 | } |
390 | | |
391 | | static bool decodeMISC(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
392 | 158 | { |
393 | 158 | uint16_t op = bpf->op ^ BPF_CLASS_MISC; |
394 | 158 | return op == BPF_MISCOP_TAX || op == BPF_MISCOP_TXA; |
395 | 158 | } |
396 | | |
397 | | ///< 1. Check if the instruction is valid |
398 | | ///< 2. Set MI->opcode |
399 | | ///< 3. Set MI->Operands |
400 | | static bool getInstruction(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
401 | 15.6k | { |
402 | 15.6k | cs_detail *detail; |
403 | | |
404 | 15.6k | detail = MI->flat_insn->detail; |
405 | | // initialize detail |
406 | 15.6k | if (detail) { |
407 | 15.6k | memset(detail, 0, offsetof(cs_detail, bpf) + sizeof(cs_bpf)); |
408 | 15.6k | } |
409 | | |
410 | 15.6k | MCInst_clear(MI); |
411 | 15.6k | MCInst_setOpcode(MI, bpf->op); |
412 | | |
413 | 15.6k | switch (BPF_CLASS(bpf->op)) { |
414 | 0 | default: /* should never happen */ |
415 | 0 | return false; |
416 | 3.74k | case BPF_CLASS_LD: |
417 | 4.93k | case BPF_CLASS_LDX: |
418 | 4.93k | return decodeLoad(ud, MI, bpf); |
419 | 732 | case BPF_CLASS_ST: |
420 | 1.77k | case BPF_CLASS_STX: |
421 | 1.77k | return decodeStore(ud, MI, bpf); |
422 | 2.57k | case BPF_CLASS_ALU: |
423 | 2.57k | return decodeALU(ud, MI, bpf); |
424 | 3.54k | case BPF_CLASS_JMP: |
425 | 3.54k | return decodeJump(ud, MI, bpf); |
426 | 933 | case BPF_CLASS_RET: |
427 | | /* eBPF doesn't have this class */ |
428 | 933 | if (EBPF_MODE(ud)) |
429 | 9 | return false; |
430 | 924 | return decodeReturn(ud, MI, bpf); |
431 | 1.86k | case BPF_CLASS_MISC: |
432 | | /* case BPF_CLASS_ALU64: */ |
433 | 1.86k | if (EBPF_MODE(ud)) |
434 | 1.70k | return decodeALU(ud, MI, bpf); |
435 | 158 | else |
436 | 158 | return decodeMISC(ud, MI, bpf); |
437 | 15.6k | } |
438 | 15.6k | } |
439 | | |
440 | | bool BPF_getInstruction(csh ud, const uint8_t *code, size_t code_len, |
441 | | MCInst *instr, uint16_t *size, uint64_t address, void *info) |
442 | 15.8k | { |
443 | 15.8k | cs_struct *cs; |
444 | 15.8k | bpf_internal *bpf; |
445 | | |
446 | 15.8k | cs = (cs_struct*)ud; |
447 | 15.8k | if (EBPF_MODE(cs)) |
448 | 9.60k | bpf = fetch_ebpf(cs, code, code_len); |
449 | 6.25k | else |
450 | 6.25k | bpf = fetch_cbpf(cs, code, code_len); |
451 | 15.8k | if (bpf == NULL) |
452 | 253 | return false; |
453 | 15.6k | if (!getInstruction(cs, instr, bpf)) { |
454 | 180 | cs_mem_free(bpf); |
455 | 180 | return false; |
456 | 180 | } |
457 | | |
458 | 15.4k | *size = bpf->insn_size; |
459 | 15.4k | cs_mem_free(bpf); |
460 | | |
461 | 15.4k | return true; |
462 | 15.6k | } |
463 | | |
464 | | #endif |