/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 | 42.6k | { |
16 | 42.6k | if (MODE_IS_BIG_ENDIAN(ud->mode)) |
17 | 22.4k | return (((uint16_t)code[0] << 8) | code[1]); |
18 | 20.1k | else |
19 | 20.1k | return (((uint16_t)code[1] << 8) | code[0]); |
20 | 42.6k | } |
21 | | |
22 | | static uint32_t read_u32(cs_struct *ud, const uint8_t *code) |
23 | 14.5k | { |
24 | 14.5k | if (MODE_IS_BIG_ENDIAN(ud->mode)) |
25 | 7.71k | return ((uint32_t)read_u16(ud, code) << 16) | read_u16(ud, code + 2); |
26 | 6.84k | else |
27 | 6.84k | return ((uint32_t)read_u16(ud, code + 2) << 16) | read_u16(ud, code); |
28 | 14.5k | } |
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 | 14.2k | { |
33 | 14.2k | bpf_internal *bpf; |
34 | | |
35 | 14.2k | if (code_len < 8) |
36 | 202 | return NULL; |
37 | 14.0k | bpf = cs_mem_malloc(sizeof(bpf_internal)); |
38 | 14.0k | if (bpf == NULL) |
39 | 0 | return NULL; |
40 | | /* default value */ |
41 | 14.0k | bpf->insn_size = 8; |
42 | 14.0k | return bpf; |
43 | 14.0k | } |
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 | 5.93k | { |
49 | 5.93k | bpf_internal *bpf; |
50 | | |
51 | 5.93k | bpf = alloc_bpf_internal(code_len); |
52 | 5.93k | if (bpf == NULL) |
53 | 85 | return NULL; |
54 | | |
55 | 5.84k | bpf->op = read_u16(ud, code); |
56 | 5.84k | bpf->jt = code[2]; |
57 | 5.84k | bpf->jf = code[3]; |
58 | 5.84k | bpf->k = read_u32(ud, code + 4); |
59 | 5.84k | return bpf; |
60 | 5.93k | } |
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 | 8.30k | { |
66 | 8.30k | bpf_internal *bpf; |
67 | | |
68 | 8.30k | bpf = alloc_bpf_internal(code_len); |
69 | 8.30k | if (bpf == NULL) |
70 | 117 | return NULL; |
71 | | |
72 | 8.19k | bpf->op = (uint16_t)code[0]; |
73 | 8.19k | bpf->dst = code[1] & 0xf; |
74 | 8.19k | 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 | 8.19k | if (bpf->op == (BPF_CLASS_LD | BPF_SIZE_DW | BPF_MODE_IMM)) { |
79 | 519 | if (code_len < 16) { |
80 | 2 | cs_mem_free(bpf); |
81 | 2 | return NULL; |
82 | 2 | } |
83 | 517 | bpf->k = read_u32(ud, code + 4) | (((uint64_t)read_u32(ud, code + 12)) << 32); |
84 | 517 | bpf->insn_size = 16; |
85 | 517 | } |
86 | 7.67k | else { |
87 | 7.67k | bpf->offset = read_u16(ud, code + 2); |
88 | 7.67k | bpf->k = read_u32(ud, code + 4); |
89 | 7.67k | } |
90 | 8.19k | return bpf; |
91 | 8.19k | } |
92 | | |
93 | 4.70k | #define CHECK_READABLE_REG(ud, reg) do { \ |
94 | 4.70k | if (! ((reg) >= BPF_REG_R0 && (reg) <= BPF_REG_R10)) \ |
95 | 4.70k | return false; \ |
96 | 4.70k | } while (0) |
97 | | |
98 | 3.84k | #define CHECK_WRITABLE_REG(ud, reg) do { \ |
99 | 3.84k | if (! ((reg) >= BPF_REG_R0 && (reg) < BPF_REG_R10)) \ |
100 | 3.84k | return false; \ |
101 | 3.84k | } while (0) |
102 | | |
103 | 4.70k | #define CHECK_READABLE_AND_PUSH(ud, MI, r) do { \ |
104 | 4.70k | CHECK_READABLE_REG(ud, r + BPF_REG_R0); \ |
105 | 4.70k | MCOperand_CreateReg0(MI, r + BPF_REG_R0); \ |
106 | 4.66k | } while (0) |
107 | | |
108 | 3.84k | #define CHECK_WRITABLE_AND_PUSH(ud, MI, r) do { \ |
109 | 3.84k | CHECK_WRITABLE_REG(ud, r + BPF_REG_R0); \ |
110 | 3.84k | MCOperand_CreateReg0(MI, r + BPF_REG_R0); \ |
111 | 3.83k | } while (0) |
112 | | |
113 | | static bool decodeLoad(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
114 | 4.40k | { |
115 | 4.40k | if (!EBPF_MODE(ud)) { |
116 | | /* |
117 | | * +-----+-----------+--------------------+ |
118 | | * | ldb | [k] | [x+k] | |
119 | | * | ldh | [k] | [x+k] | |
120 | | * +-----+-----------+--------------------+ |
121 | | */ |
122 | 2.20k | if (BPF_SIZE(bpf->op) == BPF_SIZE_DW) |
123 | 3 | return false; |
124 | 2.20k | if (BPF_SIZE(bpf->op) == BPF_SIZE_B || BPF_SIZE(bpf->op) == BPF_SIZE_H) { |
125 | | /* no ldx */ |
126 | 497 | if (BPF_CLASS(bpf->op) != BPF_CLASS_LD) |
127 | 1 | return false; |
128 | | /* can only be BPF_ABS and BPF_IND */ |
129 | 496 | if (BPF_MODE(bpf->op) == BPF_MODE_ABS) { |
130 | 278 | MCOperand_CreateImm0(MI, bpf->k); |
131 | 278 | return true; |
132 | 278 | } |
133 | 218 | else if (BPF_MODE(bpf->op) == BPF_MODE_IND) { |
134 | 215 | MCOperand_CreateReg0(MI, BPF_REG_X); |
135 | 215 | MCOperand_CreateImm0(MI, bpf->k); |
136 | 215 | return true; |
137 | 215 | } |
138 | 3 | return false; |
139 | 496 | } |
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.70k | switch (BPF_MODE(bpf->op)) { |
148 | 745 | default: |
149 | 745 | break; |
150 | 745 | case BPF_MODE_IMM: |
151 | 531 | MCOperand_CreateImm0(MI, bpf->k); |
152 | 531 | return true; |
153 | 228 | case BPF_MODE_LEN: |
154 | 228 | return true; |
155 | 205 | case BPF_MODE_MEM: |
156 | 205 | MCOperand_CreateImm0(MI, bpf->k); |
157 | 205 | return true; |
158 | 1.70k | } |
159 | 745 | if (BPF_CLASS(bpf->op) == BPF_CLASS_LD) { |
160 | 478 | if (BPF_MODE(bpf->op) == BPF_MODE_ABS) { |
161 | 281 | MCOperand_CreateImm0(MI, bpf->k); |
162 | 281 | return true; |
163 | 281 | } |
164 | 197 | else if (BPF_MODE(bpf->op) == BPF_MODE_IND) { |
165 | 196 | MCOperand_CreateReg0(MI, BPF_REG_X); |
166 | 196 | MCOperand_CreateImm0(MI, bpf->k); |
167 | 196 | return true; |
168 | 196 | } |
169 | 478 | } |
170 | 267 | else { /* LDX */ |
171 | 267 | if (BPF_MODE(bpf->op) == BPF_MODE_MSH) { |
172 | 265 | MCOperand_CreateImm0(MI, bpf->k); |
173 | 265 | return true; |
174 | 265 | } |
175 | 267 | } |
176 | 3 | return false; |
177 | 745 | } |
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.19k | if (BPF_CLASS(bpf->op) == BPF_CLASS_LD) { |
187 | 1.63k | switch (BPF_MODE(bpf->op)) { |
188 | 525 | case BPF_MODE_IMM: |
189 | 525 | if (bpf->op != (BPF_CLASS_LD | BPF_SIZE_DW | BPF_MODE_IMM)) |
190 | 8 | return false; |
191 | 517 | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
192 | 515 | MCOperand_CreateImm0(MI, bpf->k); |
193 | 515 | return true; |
194 | 558 | case BPF_MODE_ABS: |
195 | 558 | MCOperand_CreateImm0(MI, bpf->k); |
196 | 558 | return true; |
197 | 549 | case BPF_MODE_IND: |
198 | 549 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
199 | 548 | MCOperand_CreateImm0(MI, bpf->k); |
200 | 548 | return true; |
201 | 1.63k | } |
202 | 1 | return false; |
203 | | |
204 | 1.63k | } |
205 | | /* LDX */ |
206 | 563 | if (BPF_MODE(bpf->op) == BPF_MODE_MEM) { |
207 | 559 | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
208 | 558 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
209 | 557 | MCOperand_CreateImm0(MI, bpf->offset); |
210 | 557 | return true; |
211 | 558 | } |
212 | 4 | return false; |
213 | 563 | } |
214 | | |
215 | | static bool decodeStore(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
216 | 1.29k | { |
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.29k | if (!EBPF_MODE(ud)) { |
224 | | /* can only store to M[] */ |
225 | 95 | if (bpf->op != (BPF_CLASS(bpf->op) | BPF_MODE_MEM | BPF_SIZE_W)) |
226 | 5 | return false; |
227 | 90 | MCOperand_CreateImm0(MI, bpf->k); |
228 | 90 | return true; |
229 | 95 | } |
230 | | |
231 | | /* eBPF */ |
232 | | |
233 | 1.20k | if (BPF_MODE(bpf->op) == BPF_MODE_XADD) { |
234 | 105 | if (BPF_CLASS(bpf->op) != BPF_CLASS_STX) |
235 | 1 | return false; |
236 | 104 | 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 | 103 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
240 | 102 | MCOperand_CreateImm0(MI, bpf->offset); |
241 | 102 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
242 | 101 | return true; |
243 | 102 | } |
244 | | |
245 | 1.09k | if (BPF_MODE(bpf->op) != BPF_MODE_MEM) |
246 | 4 | return false; |
247 | | |
248 | | /* st [dst + off], src */ |
249 | 1.09k | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
250 | 1.08k | MCOperand_CreateImm0(MI, bpf->offset); |
251 | 1.08k | if (BPF_CLASS(bpf->op) == BPF_CLASS_ST) |
252 | 554 | MCOperand_CreateImm0(MI, bpf->k); |
253 | 535 | else |
254 | 535 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
255 | 1.08k | return true; |
256 | 1.08k | } |
257 | | |
258 | | static bool decodeALU(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
259 | 4.44k | { |
260 | | /* Set MI->Operands */ |
261 | | |
262 | | /* cBPF */ |
263 | 4.44k | if (!EBPF_MODE(ud)) { |
264 | 1.63k | if (BPF_OP(bpf->op) > BPF_ALU_XOR) |
265 | 2 | return false; |
266 | | /* cBPF's NEG has no operands */ |
267 | 1.63k | if (BPF_OP(bpf->op) == BPF_ALU_NEG) |
268 | 258 | return true; |
269 | 1.37k | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
270 | 605 | MCOperand_CreateImm0(MI, bpf->k); |
271 | 772 | else /* BPF_SRC_X */ |
272 | 772 | MCOperand_CreateReg0(MI, BPF_REG_X); |
273 | 1.37k | return true; |
274 | 1.63k | } |
275 | | |
276 | | /* eBPF */ |
277 | | |
278 | 2.80k | if (BPF_OP(bpf->op) > BPF_ALU_END) |
279 | 4 | return false; |
280 | | /* ALU64 class doesn't have ENDian */ |
281 | | /* ENDian's imm must be one of 16, 32, 64 */ |
282 | 2.79k | if (BPF_OP(bpf->op) == BPF_ALU_END) { |
283 | 257 | if (BPF_CLASS(bpf->op) == BPF_CLASS_ALU64) |
284 | 1 | return false; |
285 | 256 | if (bpf->k != 16 && bpf->k != 32 && bpf->k != 64) |
286 | 32 | return false; |
287 | 256 | } |
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.76k | CHECK_WRITABLE_AND_PUSH(ud, MI, bpf->dst); |
296 | | |
297 | | /* special cases */ |
298 | 2.76k | if (BPF_OP(bpf->op) == BPF_ALU_NEG) |
299 | 273 | return true; |
300 | 2.48k | if (BPF_OP(bpf->op) == BPF_ALU_END) { |
301 | | /* bpf->k must be one of 16, 32, 64 */ |
302 | 224 | MCInst_setOpcode(MI, MCInst_getOpcode(MI) | ((uint32_t)bpf->k << 4)); |
303 | 224 | return true; |
304 | 224 | } |
305 | | |
306 | | /* normal cases */ |
307 | 2.26k | if (BPF_SRC(bpf->op) == BPF_SRC_K) { |
308 | 1.96k | MCOperand_CreateImm0(MI, bpf->k); |
309 | 1.96k | } |
310 | 300 | else { /* BPF_SRC_X */ |
311 | 300 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
312 | 300 | } |
313 | 2.26k | return true; |
314 | 2.26k | } |
315 | | |
316 | | static bool decodeJump(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
317 | 3.02k | { |
318 | | /* cBPF and eBPF are very different in class jump */ |
319 | 3.02k | if (!EBPF_MODE(ud)) { |
320 | 1.03k | if (BPF_OP(bpf->op) > BPF_JUMP_JSET) |
321 | 1 | return false; |
322 | | |
323 | | /* ja is a special case of jumps */ |
324 | 1.03k | if (BPF_OP(bpf->op) == BPF_JUMP_JA) { |
325 | 414 | MCOperand_CreateImm0(MI, bpf->k); |
326 | 414 | return true; |
327 | 414 | } |
328 | | |
329 | 621 | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
330 | 207 | MCOperand_CreateImm0(MI, bpf->k); |
331 | 414 | else /* BPF_SRC_X */ |
332 | 414 | MCOperand_CreateReg0(MI, BPF_REG_X); |
333 | 621 | MCOperand_CreateImm0(MI, bpf->jt); |
334 | 621 | MCOperand_CreateImm0(MI, bpf->jf); |
335 | 621 | } |
336 | 1.98k | else { |
337 | 1.98k | if (BPF_OP(bpf->op) > BPF_JUMP_JSLE) |
338 | 1 | return false; |
339 | | |
340 | | /* No operands for exit */ |
341 | 1.98k | if (BPF_OP(bpf->op) == BPF_JUMP_EXIT) |
342 | 272 | return bpf->op == (BPF_CLASS_JMP | BPF_JUMP_EXIT); |
343 | 1.71k | if (BPF_OP(bpf->op) == BPF_JUMP_CALL) { |
344 | 321 | if (bpf->op == (BPF_CLASS_JMP | BPF_JUMP_CALL)) { |
345 | 272 | MCOperand_CreateImm0(MI, bpf->k); |
346 | 272 | return true; |
347 | 272 | } |
348 | 49 | if (bpf->op == (BPF_CLASS_JMP | BPF_JUMP_CALL | BPF_SRC_X)) { |
349 | 49 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->k); |
350 | 18 | return true; |
351 | 49 | } |
352 | 0 | return false; |
353 | 49 | } |
354 | | |
355 | | /* ja is a special case of jumps */ |
356 | 1.39k | if (BPF_OP(bpf->op) == BPF_JUMP_JA) { |
357 | 230 | if (BPF_SRC(bpf->op) != BPF_SRC_K) |
358 | 1 | return false; |
359 | 229 | MCOperand_CreateImm0(MI, bpf->offset); |
360 | 229 | return true; |
361 | 230 | } |
362 | | |
363 | | /* <j> dst, src, +off */ |
364 | 1.16k | CHECK_READABLE_AND_PUSH(ud, MI, bpf->dst); |
365 | 1.15k | if (BPF_SRC(bpf->op) == BPF_SRC_K) |
366 | 900 | MCOperand_CreateImm0(MI, bpf->k); |
367 | 259 | else |
368 | 259 | CHECK_READABLE_AND_PUSH(ud, MI, bpf->src); |
369 | 1.15k | MCOperand_CreateImm0(MI, bpf->offset); |
370 | 1.15k | } |
371 | 1.77k | return true; |
372 | 3.02k | } |
373 | | |
374 | | static bool decodeReturn(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
375 | 700 | { |
376 | | /* Here only handles the BPF_RET class in cBPF */ |
377 | 700 | switch (BPF_RVAL(bpf->op)) { |
378 | 244 | case BPF_SRC_K: |
379 | 244 | MCOperand_CreateImm0(MI, bpf->k); |
380 | 244 | return true; |
381 | 218 | case BPF_SRC_X: |
382 | 218 | MCOperand_CreateReg0(MI, BPF_REG_X); |
383 | 218 | return true; |
384 | 236 | case BPF_SRC_A: |
385 | 236 | MCOperand_CreateReg0(MI, BPF_REG_A); |
386 | 236 | return true; |
387 | 700 | } |
388 | 2 | return false; |
389 | 700 | } |
390 | | |
391 | | static bool decodeMISC(cs_struct *ud, MCInst *MI, bpf_internal *bpf) |
392 | 171 | { |
393 | 171 | uint16_t op = bpf->op ^ BPF_CLASS_MISC; |
394 | 171 | return op == BPF_MISCOP_TAX || op == BPF_MISCOP_TXA; |
395 | 171 | } |
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 | 14.0k | { |
402 | 14.0k | cs_detail *detail; |
403 | | |
404 | 14.0k | detail = MI->flat_insn->detail; |
405 | | // initialize detail |
406 | 14.0k | if (detail) { |
407 | 14.0k | memset(detail, 0, offsetof(cs_detail, bpf) + sizeof(cs_bpf)); |
408 | 14.0k | } |
409 | | |
410 | 14.0k | MCInst_clear(MI); |
411 | 14.0k | MCInst_setOpcode(MI, bpf->op); |
412 | | |
413 | 14.0k | switch (BPF_CLASS(bpf->op)) { |
414 | 0 | default: /* should never happen */ |
415 | 0 | return false; |
416 | 3.24k | case BPF_CLASS_LD: |
417 | 4.40k | case BPF_CLASS_LDX: |
418 | 4.40k | return decodeLoad(ud, MI, bpf); |
419 | 630 | case BPF_CLASS_ST: |
420 | 1.29k | case BPF_CLASS_STX: |
421 | 1.29k | return decodeStore(ud, MI, bpf); |
422 | 2.59k | case BPF_CLASS_ALU: |
423 | 2.59k | return decodeALU(ud, MI, bpf); |
424 | 3.02k | case BPF_CLASS_JMP: |
425 | 3.02k | return decodeJump(ud, MI, bpf); |
426 | 707 | case BPF_CLASS_RET: |
427 | | /* eBPF doesn't have this class */ |
428 | 707 | if (EBPF_MODE(ud)) |
429 | 7 | return false; |
430 | 700 | return decodeReturn(ud, MI, bpf); |
431 | 2.02k | case BPF_CLASS_MISC: |
432 | | /* case BPF_CLASS_ALU64: */ |
433 | 2.02k | if (EBPF_MODE(ud)) |
434 | 1.84k | return decodeALU(ud, MI, bpf); |
435 | 171 | else |
436 | 171 | return decodeMISC(ud, MI, bpf); |
437 | 14.0k | } |
438 | 14.0k | } |
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 | 14.2k | { |
443 | 14.2k | cs_struct *cs; |
444 | 14.2k | bpf_internal *bpf; |
445 | | |
446 | 14.2k | cs = (cs_struct*)ud; |
447 | 14.2k | if (EBPF_MODE(cs)) |
448 | 8.30k | bpf = fetch_ebpf(cs, code, code_len); |
449 | 5.93k | else |
450 | 5.93k | bpf = fetch_cbpf(cs, code, code_len); |
451 | 14.2k | if (bpf == NULL) |
452 | 204 | return false; |
453 | 14.0k | if (!getInstruction(cs, instr, bpf)) { |
454 | 146 | cs_mem_free(bpf); |
455 | 146 | return false; |
456 | 146 | } |
457 | | |
458 | 13.8k | *size = bpf->insn_size; |
459 | 13.8k | cs_mem_free(bpf); |
460 | | |
461 | 13.8k | return true; |
462 | 14.0k | } |
463 | | |
464 | | #endif |