Line data Source code
1 : /* Disassembler for BPF.
2 : Copyright (C) 2016, 2018 Red Hat, Inc.
3 : This file is part of elfutils.
4 :
5 : This file is free software; you can redistribute it and/or modify
6 : it under the terms of either
7 :
8 : * the GNU Lesser General Public License as published by the Free
9 : Software Foundation; either version 3 of the License, or (at
10 : your option) any later version
11 :
12 : or
13 :
14 : * the GNU General Public License as published by the Free
15 : Software Foundation; either version 2 of the License, or (at
16 : your option) any later version
17 :
18 : or both in parallel, as here.
19 :
20 : elfutils is distributed in the hope that it will be useful, but
21 : WITHOUT ANY WARRANTY; without even the implied warranty of
22 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 : General Public License for more details.
24 :
25 : You should have received copies of the GNU General Public License and
26 : the GNU Lesser General Public License along with this program. If
27 : not, see <http://www.gnu.org/licenses/>. */
28 :
29 : #ifdef HAVE_CONFIG_H
30 : # include <config.h>
31 : #endif
32 :
33 : #include <assert.h>
34 : #include <string.h>
35 : #include <stdio.h>
36 : #include <gelf.h>
37 : #include <inttypes.h>
38 : #include "bpf.h"
39 :
40 : #include "../libelf/common.h"
41 : #include "../libebl/libeblP.h"
42 :
43 : static const char class_string[8][8] = {
44 : [BPF_LD] = "ld",
45 : [BPF_LDX] = "ldx",
46 : [BPF_ST] = "st",
47 : [BPF_STX] = "stx",
48 : [BPF_ALU] = "alu",
49 : [BPF_JMP] = "jmp",
50 : [BPF_RET] = "6", /* completely unused in ebpf */
51 : [BPF_ALU64] = "alu64",
52 : };
53 :
54 :
55 : #define REG(N) "r%" #N "$d"
56 : #define REGU(N) "(u32)" REG(N)
57 : #define REGS(N) "(s64)" REG(N)
58 :
59 : #define IMMS(N) "%" #N "$d"
60 : #define IMMX(N) "%" #N "$#x"
61 :
62 : #define OFF(N) "%" #N "$+d"
63 : #define JMP(N) "%" #N "$#x"
64 :
65 : #define A32(O, S) REG(1) " = " REGU(1) " " #O " " S
66 : #define A64(O, S) REG(1) " " #O "= " S
67 : #define J64(D, O, S) "if " D " " #O " " S " goto " JMP(3)
68 : #define LOAD(T) REG(1) " = *(" #T " *)(" REG(2) OFF(3) ")"
69 : #define STORE(T, S) "*(" #T " *)(" REG(1) OFF(3) ") = " S
70 : #define XADD(T, S) "lock *(" #T " *)(" REG(1) OFF(3) ") += " S
71 : #define LDSKB(T, S) "r0 = *(" #T " *)skb[" S "]"
72 :
73 : static void
74 : bswap_bpf_insn (struct bpf_insn *p)
75 : {
76 : /* Note that the dst_reg and src_reg fields are 4-bit bitfields.
77 : That means these two nibbles are (typically) layed out in the
78 : opposite order between big- and little-endian hosts. This is
79 : not required by any standard, but does happen to be true for
80 : at least ppc, s390, arm and mips as big-endian hosts. */
81 0 : int t = p->dst_reg;
82 0 : p->dst_reg = p->src_reg;
83 0 : p->src_reg = t;
84 :
85 : /* The other 2 and 4 byte fields are trivially converted. */
86 0 : CONVERT (p->off);
87 0 : CONVERT (p->imm);
88 : }
89 :
90 : int
91 1 : bpf_disasm (Ebl *ebl, const uint8_t **startp, const uint8_t *end,
92 : GElf_Addr addr, const char *fmt __attribute__((unused)),
93 : DisasmOutputCB_t outcb,
94 : DisasmGetSymCB_t symcb __attribute__((unused)),
95 : void *outcbarg,
96 : void *symcbarg __attribute__((unused)))
97 : {
98 1 : const bool need_bswap = MY_ELFDATA != ebl->data;
99 1 : const uint8_t *start = *startp;
100 1 : char buf[128];
101 1 : int len, retval = 0;
102 :
103 257 : while (start + sizeof(struct bpf_insn) <= end)
104 : {
105 256 : struct bpf_insn i;
106 256 : unsigned code, class, jmp;
107 256 : const char *code_fmt;
108 :
109 256 : memcpy(&i, start, sizeof(struct bpf_insn));
110 256 : if (need_bswap)
111 0 : bswap_bpf_insn (&i);
112 :
113 256 : start += sizeof(struct bpf_insn);
114 256 : addr += sizeof(struct bpf_insn);
115 256 : jmp = addr + i.off * sizeof(struct bpf_insn);
116 :
117 256 : code = i.code;
118 256 : switch (code)
119 : {
120 1 : case BPF_LD | BPF_IMM | BPF_DW:
121 1 : {
122 1 : struct bpf_insn i2;
123 1 : uint64_t imm64;
124 :
125 1 : if (start + sizeof(struct bpf_insn) > end)
126 : {
127 0 : start -= sizeof(struct bpf_insn);
128 0 : *startp = start;
129 0 : goto done;
130 : }
131 1 : memcpy(&i2, start, sizeof(struct bpf_insn));
132 1 : if (need_bswap)
133 0 : bswap_bpf_insn (&i2);
134 1 : start += sizeof(struct bpf_insn);
135 1 : addr += sizeof(struct bpf_insn);
136 :
137 1 : imm64 = (uint32_t)i.imm | ((uint64_t)i2.imm << 32);
138 1 : switch (i.src_reg)
139 : {
140 : case 0:
141 : code_fmt = REG(1) " = %2$#" PRIx64;
142 : break;
143 0 : case BPF_PSEUDO_MAP_FD:
144 0 : code_fmt = REG(1) " = map_fd(%2$#" PRIx64 ")";
145 0 : break;
146 0 : default:
147 0 : code_fmt = REG(1) " = ld_pseudo(%3$d, %2$#" PRIx64 ")";
148 0 : break;
149 : }
150 3 : len = snprintf(buf, sizeof(buf), code_fmt,
151 1 : i.dst_reg, imm64, i.src_reg);
152 : }
153 1 : break;
154 :
155 : case BPF_JMP | BPF_EXIT:
156 1 : len = snprintf(buf, sizeof(buf), "exit");
157 1 : break;
158 : case BPF_JMP | BPF_JA:
159 1 : len = snprintf(buf, sizeof(buf), "goto " JMP(1), jmp);
160 1 : break;
161 : case BPF_JMP | BPF_CALL:
162 : code_fmt = "call " IMMS(1);
163 : goto do_imm;
164 :
165 : case BPF_ALU | BPF_END | BPF_TO_LE:
166 : /* The imm field contains {16,32,64}. */
167 : code_fmt = REG(1) " = le" IMMS(2) "(" REG(1) ")";
168 : goto do_dst_imm;
169 1 : case BPF_ALU | BPF_END | BPF_TO_BE:
170 1 : code_fmt = REG(1) " = be" IMMS(2) "(" REG(1) ")";
171 1 : goto do_dst_imm;
172 :
173 1 : case BPF_ALU | BPF_ADD | BPF_K:
174 1 : code_fmt = A32(+, IMMS(2));
175 1 : goto do_dst_imm;
176 1 : case BPF_ALU | BPF_SUB | BPF_K:
177 1 : code_fmt = A32(-, IMMS(2));
178 1 : goto do_dst_imm;
179 1 : case BPF_ALU | BPF_MUL | BPF_K:
180 1 : code_fmt = A32(*, IMMS(2));
181 1 : goto do_dst_imm;
182 1 : case BPF_ALU | BPF_DIV | BPF_K:
183 1 : code_fmt = A32(/, IMMS(2));
184 1 : goto do_dst_imm;
185 1 : case BPF_ALU | BPF_OR | BPF_K:
186 1 : code_fmt = A32(|, IMMX(2));
187 1 : goto do_dst_imm;
188 1 : case BPF_ALU | BPF_AND | BPF_K:
189 1 : code_fmt = A32(&, IMMX(2));
190 1 : goto do_dst_imm;
191 1 : case BPF_ALU | BPF_LSH | BPF_K:
192 1 : code_fmt = A32(<<, IMMS(2));
193 1 : goto do_dst_imm;
194 1 : case BPF_ALU | BPF_RSH | BPF_K:
195 1 : code_fmt = A32(>>, IMMS(2));
196 1 : goto do_dst_imm;
197 1 : case BPF_ALU | BPF_MOD | BPF_K:
198 1 : code_fmt = A32(%%, IMMS(2));
199 1 : goto do_dst_imm;
200 1 : case BPF_ALU | BPF_XOR | BPF_K:
201 1 : code_fmt = A32(^, IMMX(2));
202 1 : goto do_dst_imm;
203 1 : case BPF_ALU | BPF_MOV | BPF_K:
204 1 : code_fmt = REG(1) " = " IMMX(2);
205 1 : goto do_dst_imm;
206 1 : case BPF_ALU | BPF_ARSH | BPF_K:
207 1 : code_fmt = REG(1) " = (u32)((s32)" REG(1) " >> " IMMS(2) ")";
208 1 : goto do_dst_imm;
209 :
210 : case BPF_ALU | BPF_ADD | BPF_X:
211 : code_fmt = A32(+, REGU(2));
212 : goto do_dst_src;
213 1 : case BPF_ALU | BPF_SUB | BPF_X:
214 1 : code_fmt = A32(-, REGU(2));
215 1 : goto do_dst_src;
216 1 : case BPF_ALU | BPF_MUL | BPF_X:
217 1 : code_fmt = A32(*, REGU(2));
218 1 : goto do_dst_src;
219 1 : case BPF_ALU | BPF_DIV | BPF_X:
220 1 : code_fmt = A32(/, REGU(2));
221 1 : goto do_dst_src;
222 1 : case BPF_ALU | BPF_OR | BPF_X:
223 1 : code_fmt = A32(|, REGU(2));
224 1 : goto do_dst_src;
225 1 : case BPF_ALU | BPF_AND | BPF_X:
226 1 : code_fmt = A32(&, REGU(2));
227 1 : goto do_dst_src;
228 1 : case BPF_ALU | BPF_LSH | BPF_X:
229 1 : code_fmt = A32(<<, REGU(2));
230 1 : goto do_dst_src;
231 1 : case BPF_ALU | BPF_RSH | BPF_X:
232 1 : code_fmt = A32(>>, REGU(2));
233 1 : goto do_dst_src;
234 1 : case BPF_ALU | BPF_MOD | BPF_X:
235 1 : code_fmt = A32(%%, REGU(2));
236 1 : goto do_dst_src;
237 1 : case BPF_ALU | BPF_XOR | BPF_X:
238 1 : code_fmt = A32(^, REGU(2));
239 1 : goto do_dst_src;
240 1 : case BPF_ALU | BPF_MOV | BPF_X:
241 1 : code_fmt = REG(1) " = " REGU(2);
242 1 : goto do_dst_src;
243 1 : case BPF_ALU | BPF_ARSH | BPF_X:
244 1 : code_fmt = REG(1) " = (u32)((s32)" REG(1) " >> " REG(2) ")";
245 1 : goto do_dst_src;
246 :
247 1 : case BPF_ALU64 | BPF_ADD | BPF_K:
248 1 : code_fmt = A64(+, IMMS(2));
249 1 : goto do_dst_imm;
250 1 : case BPF_ALU64 | BPF_SUB | BPF_K:
251 1 : code_fmt = A64(-, IMMS(2));
252 1 : goto do_dst_imm;
253 1 : case BPF_ALU64 | BPF_MUL | BPF_K:
254 1 : code_fmt = A64(*, IMMS(2));
255 1 : goto do_dst_imm;
256 1 : case BPF_ALU64 | BPF_DIV | BPF_K:
257 1 : code_fmt = A64(/, IMMS(2));
258 1 : goto do_dst_imm;
259 1 : case BPF_ALU64 | BPF_OR | BPF_K:
260 1 : code_fmt = A64(|, IMMS(2));
261 1 : goto do_dst_imm;
262 1 : case BPF_ALU64 | BPF_AND | BPF_K:
263 1 : code_fmt = A64(&, IMMS(2));
264 1 : goto do_dst_imm;
265 1 : case BPF_ALU64 | BPF_LSH | BPF_K:
266 1 : code_fmt = A64(<<, IMMS(2));
267 1 : goto do_dst_imm;
268 1 : case BPF_ALU64 | BPF_RSH | BPF_K:
269 1 : code_fmt = A64(>>, IMMS(2));
270 1 : goto do_dst_imm;
271 1 : case BPF_ALU64 | BPF_MOD | BPF_K:
272 1 : code_fmt = A64(%%, IMMS(2));
273 1 : goto do_dst_imm;
274 1 : case BPF_ALU64 | BPF_XOR | BPF_K:
275 1 : code_fmt = A64(^, IMMS(2));
276 1 : goto do_dst_imm;
277 1 : case BPF_ALU64 | BPF_MOV | BPF_K:
278 1 : code_fmt = REG(1) " = " IMMS(2);
279 1 : goto do_dst_imm;
280 1 : case BPF_ALU64 | BPF_ARSH | BPF_K:
281 1 : code_fmt = REG(1) " = (s64)" REG(1) " >> " IMMS(2);
282 1 : goto do_dst_imm;
283 :
284 1 : case BPF_ALU64 | BPF_ADD | BPF_X:
285 1 : code_fmt = A64(+, REG(2));
286 1 : goto do_dst_src;
287 1 : case BPF_ALU64 | BPF_SUB | BPF_X:
288 1 : code_fmt = A64(-, REG(2));
289 1 : goto do_dst_src;
290 1 : case BPF_ALU64 | BPF_MUL | BPF_X:
291 1 : code_fmt = A64(*, REG(2));
292 1 : goto do_dst_src;
293 1 : case BPF_ALU64 | BPF_DIV | BPF_X:
294 1 : code_fmt = A64(/, REG(2));
295 1 : goto do_dst_src;
296 1 : case BPF_ALU64 | BPF_OR | BPF_X:
297 1 : code_fmt = A64(|, REG(2));
298 1 : goto do_dst_src;
299 1 : case BPF_ALU64 | BPF_AND | BPF_X:
300 1 : code_fmt = A64(&, REG(2));
301 1 : goto do_dst_src;
302 1 : case BPF_ALU64 | BPF_LSH | BPF_X:
303 1 : code_fmt = A64(<<, REG(2));
304 1 : goto do_dst_src;
305 1 : case BPF_ALU64 | BPF_RSH | BPF_X:
306 1 : code_fmt = A64(>>, REG(2));
307 1 : goto do_dst_src;
308 1 : case BPF_ALU64 | BPF_MOD | BPF_X:
309 1 : code_fmt = A64(%%, REG(2));
310 1 : goto do_dst_src;
311 1 : case BPF_ALU64 | BPF_XOR | BPF_X:
312 1 : code_fmt = A64(^, REG(2));
313 1 : goto do_dst_src;
314 1 : case BPF_ALU64 | BPF_MOV | BPF_X:
315 1 : code_fmt = REG(1) " = " REG(2);
316 1 : goto do_dst_src;
317 1 : case BPF_ALU64 | BPF_ARSH | BPF_X:
318 1 : code_fmt = REG(1) " = (s64)" REG(1) " >> " REG(2);
319 1 : goto do_dst_src;
320 :
321 1 : case BPF_ALU | BPF_NEG:
322 1 : code_fmt = REG(1) " = (u32)-" REG(1);
323 1 : goto do_dst_src;
324 1 : case BPF_ALU64 | BPF_NEG:
325 1 : code_fmt = REG(1) " = -" REG(1);
326 1 : goto do_dst_src;
327 :
328 : case BPF_JMP | BPF_JEQ | BPF_K:
329 : code_fmt = J64(REG(1), ==, IMMS(2));
330 : goto do_dst_imm_jmp;
331 1 : case BPF_JMP | BPF_JGT | BPF_K:
332 1 : code_fmt = J64(REG(1), >, IMMS(2));
333 1 : goto do_dst_imm_jmp;
334 1 : case BPF_JMP | BPF_JGE | BPF_K:
335 1 : code_fmt = J64(REG(1), >=, IMMS(2));
336 1 : goto do_dst_imm_jmp;
337 1 : case BPF_JMP | BPF_JSET | BPF_K:
338 1 : code_fmt = J64(REG(1), &, IMMS(2));
339 1 : goto do_dst_imm_jmp;
340 1 : case BPF_JMP | BPF_JNE | BPF_K:
341 1 : code_fmt = J64(REG(1), !=, IMMS(2));
342 1 : goto do_dst_imm_jmp;
343 1 : case BPF_JMP | BPF_JSGT | BPF_K:
344 1 : code_fmt = J64(REGS(1), >, IMMS(2));
345 1 : goto do_dst_imm_jmp;
346 1 : case BPF_JMP | BPF_JSGE | BPF_K:
347 1 : code_fmt = J64(REGS(1), >=, IMMS(2));
348 1 : goto do_dst_imm_jmp;
349 1 : case BPF_JMP | BPF_JLT | BPF_K:
350 1 : code_fmt = J64(REG(1), <, IMMS(2));
351 1 : goto do_dst_imm_jmp;
352 1 : case BPF_JMP | BPF_JLE | BPF_K:
353 1 : code_fmt = J64(REG(1), <=, IMMS(2));
354 1 : goto do_dst_imm_jmp;
355 1 : case BPF_JMP | BPF_JSLT | BPF_K:
356 1 : code_fmt = J64(REGS(1), <, IMMS(2));
357 1 : goto do_dst_imm_jmp;
358 1 : case BPF_JMP | BPF_JSLE | BPF_K:
359 1 : code_fmt = J64(REGS(1), <=, IMMS(2));
360 1 : goto do_dst_imm_jmp;
361 :
362 : case BPF_JMP | BPF_JEQ | BPF_X:
363 : code_fmt = J64(REG(1), ==, REG(2));
364 : goto do_dst_src_jmp;
365 1 : case BPF_JMP | BPF_JGT | BPF_X:
366 1 : code_fmt = J64(REG(1), >, REG(2));
367 1 : goto do_dst_src_jmp;
368 1 : case BPF_JMP | BPF_JGE | BPF_X:
369 1 : code_fmt = J64(REG(1), >=, REG(2));
370 1 : goto do_dst_src_jmp;
371 1 : case BPF_JMP | BPF_JSET | BPF_X:
372 1 : code_fmt = J64(REG(1), &, REG(2));
373 1 : goto do_dst_src_jmp;
374 1 : case BPF_JMP | BPF_JNE | BPF_X:
375 1 : code_fmt = J64(REG(1), !=, REG(2));
376 1 : goto do_dst_src_jmp;
377 1 : case BPF_JMP | BPF_JSGT | BPF_X:
378 1 : code_fmt = J64(REGS(1), >, REGS(2));
379 1 : goto do_dst_src_jmp;
380 1 : case BPF_JMP | BPF_JSGE | BPF_X:
381 1 : code_fmt = J64(REGS(1), >=, REGS(2));
382 1 : goto do_dst_src_jmp;
383 1 : case BPF_JMP | BPF_JLT | BPF_X:
384 1 : code_fmt = J64(REG(1), <, REG(2));
385 1 : goto do_dst_src_jmp;
386 1 : case BPF_JMP | BPF_JLE | BPF_X:
387 1 : code_fmt = J64(REG(1), <=, REG(2));
388 1 : goto do_dst_src_jmp;
389 1 : case BPF_JMP | BPF_JSLT | BPF_X:
390 1 : code_fmt = J64(REGS(1), <, REGS(2));
391 1 : goto do_dst_src_jmp;
392 1 : case BPF_JMP | BPF_JSLE | BPF_X:
393 1 : code_fmt = J64(REGS(1), <=, REGS(2));
394 1 : goto do_dst_src_jmp;
395 :
396 : case BPF_LDX | BPF_MEM | BPF_B:
397 : code_fmt = LOAD(u8);
398 : goto do_dst_src_off;
399 1 : case BPF_LDX | BPF_MEM | BPF_H:
400 1 : code_fmt = LOAD(u16);
401 1 : goto do_dst_src_off;
402 1 : case BPF_LDX | BPF_MEM | BPF_W:
403 1 : code_fmt = LOAD(u32);
404 1 : goto do_dst_src_off;
405 1 : case BPF_LDX | BPF_MEM | BPF_DW:
406 1 : code_fmt = LOAD(u64);
407 1 : goto do_dst_src_off;
408 :
409 1 : case BPF_STX | BPF_MEM | BPF_B:
410 1 : code_fmt = STORE(u8, REG(2));
411 1 : goto do_dst_src_off;
412 1 : case BPF_STX | BPF_MEM | BPF_H:
413 1 : code_fmt = STORE(u16, REG(2));
414 1 : goto do_dst_src_off;
415 1 : case BPF_STX | BPF_MEM | BPF_W:
416 1 : code_fmt = STORE(u32, REG(2));
417 1 : goto do_dst_src_off;
418 1 : case BPF_STX | BPF_MEM | BPF_DW:
419 1 : code_fmt = STORE(u64, REG(2));
420 1 : goto do_dst_src_off;
421 :
422 1 : case BPF_STX | BPF_XADD | BPF_W:
423 1 : code_fmt = XADD(u32, REG(2));
424 1 : goto do_dst_src_off;
425 1 : case BPF_STX | BPF_XADD | BPF_DW:
426 1 : code_fmt = XADD(u64, REG(2));
427 1 : goto do_dst_src_off;
428 :
429 : case BPF_ST | BPF_MEM | BPF_B:
430 : code_fmt = STORE(u8, IMMS(2));
431 : goto do_dst_imm_off;
432 1 : case BPF_ST | BPF_MEM | BPF_H:
433 1 : code_fmt = STORE(u16, IMMS(2));
434 1 : goto do_dst_imm_off;
435 1 : case BPF_ST | BPF_MEM | BPF_W:
436 1 : code_fmt = STORE(u32, IMMS(2));
437 1 : goto do_dst_imm_off;
438 1 : case BPF_ST | BPF_MEM | BPF_DW:
439 1 : code_fmt = STORE(u64, IMMS(2));
440 1 : goto do_dst_imm_off;
441 :
442 1 : case BPF_LD | BPF_ABS | BPF_B:
443 1 : code_fmt = LDSKB(u8, IMMS(1));
444 1 : goto do_imm;
445 1 : case BPF_LD | BPF_ABS | BPF_H:
446 1 : code_fmt = LDSKB(u16, IMMS(1));
447 1 : goto do_imm;
448 1 : case BPF_LD | BPF_ABS | BPF_W:
449 1 : code_fmt = LDSKB(u32, IMMS(1));
450 1 : goto do_imm;
451 :
452 : case BPF_LD | BPF_IND | BPF_B:
453 : code_fmt = LDSKB(u8, REG(1) "+" IMMS(2));
454 : goto do_src_imm;
455 1 : case BPF_LD | BPF_IND | BPF_H:
456 1 : code_fmt = LDSKB(u16, REG(1) "+" IMMS(2));
457 1 : goto do_src_imm;
458 1 : case BPF_LD | BPF_IND | BPF_W:
459 1 : code_fmt = LDSKB(u32, REG(1) "+" IMMS(2));
460 1 : goto do_src_imm;
461 :
462 4 : do_imm:
463 4 : len = snprintf(buf, sizeof(buf), code_fmt, i.imm);
464 4 : break;
465 26 : do_dst_imm:
466 26 : len = snprintf(buf, sizeof(buf), code_fmt, i.dst_reg, i.imm);
467 26 : break;
468 3 : do_src_imm:
469 3 : len = snprintf(buf, sizeof(buf), code_fmt, i.src_reg, i.imm);
470 3 : break;
471 26 : do_dst_src:
472 26 : len = snprintf(buf, sizeof(buf), code_fmt, i.dst_reg, i.src_reg);
473 26 : break;
474 11 : do_dst_imm_jmp:
475 11 : len = snprintf(buf, sizeof(buf), code_fmt, i.dst_reg, i.imm, jmp);
476 11 : break;
477 11 : do_dst_src_jmp:
478 44 : len = snprintf(buf, sizeof(buf), code_fmt,
479 11 : i.dst_reg, i.src_reg, jmp);
480 11 : break;
481 4 : do_dst_imm_off:
482 4 : len = snprintf(buf, sizeof(buf), code_fmt, i.dst_reg, i.imm, i.off);
483 4 : break;
484 10 : do_dst_src_off:
485 40 : len = snprintf(buf, sizeof(buf), code_fmt,
486 10 : i.dst_reg, i.src_reg, i.off);
487 10 : break;
488 :
489 158 : default:
490 158 : class = BPF_CLASS(code);
491 474 : len = snprintf(buf, sizeof(buf), "invalid class %s",
492 158 : class_string[class]);
493 158 : break;
494 : }
495 :
496 256 : *startp = start;
497 256 : retval = outcb (buf, len, outcbarg);
498 256 : if (retval != 0)
499 0 : goto done;
500 : }
501 :
502 1 : done:
503 1 : return retval;
504 : }
|