Commit | Line | Data |
---|---|---|
6d8a639a | 1 | // SPDX-License-Identifier: GPL-2.0-only |
598b7c69 SE |
2 | /* |
3 | * genelf_debug.c | |
4 | * Copyright (C) 2015, Google, Inc | |
5 | * | |
6 | * Contributed by: | |
7 | * Stephane Eranian <eranian@google.com> | |
8 | * | |
598b7c69 SE |
9 | * based on GPLv2 source code from Oprofile |
10 | * @remark Copyright 2007 OProfile authors | |
11 | * @author Philippe Elie | |
12 | */ | |
c9f5da74 | 13 | #include <linux/compiler.h> |
598b7c69 SE |
14 | #include <sys/types.h> |
15 | #include <stdio.h> | |
16 | #include <getopt.h> | |
17 | #include <stddef.h> | |
18 | #include <libelf.h> | |
19 | #include <string.h> | |
20 | #include <stdlib.h> | |
21 | #include <inttypes.h> | |
22 | #include <limits.h> | |
23 | #include <fcntl.h> | |
24 | #include <err.h> | |
25 | #include <dwarf.h> | |
26 | ||
598b7c69 SE |
27 | #include "genelf.h" |
28 | #include "../util/jitdump.h" | |
29 | ||
30 | #define BUFFER_EXT_DFL_SIZE (4 * 1024) | |
31 | ||
32 | typedef uint32_t uword; | |
33 | typedef uint16_t uhalf; | |
34 | typedef int32_t sword; | |
35 | typedef int16_t shalf; | |
36 | typedef uint8_t ubyte; | |
37 | typedef int8_t sbyte; | |
38 | ||
39 | struct buffer_ext { | |
40 | size_t cur_pos; | |
41 | size_t max_sz; | |
42 | void *data; | |
43 | }; | |
44 | ||
45 | static void | |
46 | buffer_ext_dump(struct buffer_ext *be, const char *msg) | |
47 | { | |
48 | size_t i; | |
49 | warnx("DUMP for %s", msg); | |
50 | for (i = 0 ; i < be->cur_pos; i++) | |
51 | warnx("%4zu 0x%02x", i, (((char *)be->data)[i]) & 0xff); | |
52 | } | |
53 | ||
54 | static inline int | |
55 | buffer_ext_add(struct buffer_ext *be, void *addr, size_t sz) | |
56 | { | |
57 | void *tmp; | |
58 | size_t be_sz = be->max_sz; | |
59 | ||
60 | retry: | |
61 | if ((be->cur_pos + sz) < be_sz) { | |
62 | memcpy(be->data + be->cur_pos, addr, sz); | |
63 | be->cur_pos += sz; | |
64 | return 0; | |
65 | } | |
66 | ||
67 | if (!be_sz) | |
68 | be_sz = BUFFER_EXT_DFL_SIZE; | |
69 | else | |
70 | be_sz <<= 1; | |
71 | ||
72 | tmp = realloc(be->data, be_sz); | |
73 | if (!tmp) | |
74 | return -1; | |
75 | ||
76 | be->data = tmp; | |
77 | be->max_sz = be_sz; | |
78 | ||
79 | goto retry; | |
80 | } | |
81 | ||
82 | static void | |
83 | buffer_ext_init(struct buffer_ext *be) | |
84 | { | |
85 | be->data = NULL; | |
86 | be->cur_pos = 0; | |
87 | be->max_sz = 0; | |
88 | } | |
89 | ||
dc67c783 IR |
90 | static void |
91 | buffer_ext_exit(struct buffer_ext *be) | |
92 | { | |
93 | free(be->data); | |
94 | } | |
95 | ||
598b7c69 SE |
96 | static inline size_t |
97 | buffer_ext_size(struct buffer_ext *be) | |
98 | { | |
99 | return be->cur_pos; | |
100 | } | |
101 | ||
102 | static inline void * | |
103 | buffer_ext_addr(struct buffer_ext *be) | |
104 | { | |
105 | return be->data; | |
106 | } | |
107 | ||
108 | struct debug_line_header { | |
109 | // Not counting this field | |
110 | uword total_length; | |
111 | // version number (2 currently) | |
112 | uhalf version; | |
113 | // relative offset from next field to | |
114 | // program statement | |
115 | uword prolog_length; | |
116 | ubyte minimum_instruction_length; | |
117 | ubyte default_is_stmt; | |
118 | // line_base - see DWARF 2 specs | |
119 | sbyte line_base; | |
120 | // line_range - see DWARF 2 specs | |
121 | ubyte line_range; | |
122 | // number of opcode + 1 | |
123 | ubyte opcode_base; | |
124 | /* follow the array of opcode args nr: ubytes [nr_opcode_base] */ | |
125 | /* follow the search directories index, zero terminated string | |
126 | * terminated by an empty string. | |
127 | */ | |
128 | /* follow an array of { filename, LEB128, LEB128, LEB128 }, first is | |
129 | * the directory index entry, 0 means current directory, then mtime | |
130 | * and filesize, last entry is followed by en empty string. | |
131 | */ | |
132 | /* follow the first program statement */ | |
c9f5da74 | 133 | } __packed; |
598b7c69 SE |
134 | |
135 | /* DWARF 2 spec talk only about one possible compilation unit header while | |
136 | * binutils can handle two flavours of dwarf 2, 32 and 64 bits, this is not | |
137 | * related to the used arch, an ELF 32 can hold more than 4 Go of debug | |
138 | * information. For now we handle only DWARF 2 32 bits comp unit. It'll only | |
139 | * become a problem if we generate more than 4GB of debug information. | |
140 | */ | |
141 | struct compilation_unit_header { | |
142 | uword total_length; | |
143 | uhalf version; | |
144 | uword debug_abbrev_offset; | |
145 | ubyte pointer_size; | |
c9f5da74 | 146 | } __packed; |
598b7c69 SE |
147 | |
148 | #define DW_LNS_num_opcode (DW_LNS_set_isa + 1) | |
149 | ||
150 | /* field filled at run time are marked with -1 */ | |
151 | static struct debug_line_header const default_debug_line_header = { | |
152 | .total_length = -1, | |
153 | .version = 2, | |
154 | .prolog_length = -1, | |
155 | .minimum_instruction_length = 1, /* could be better when min instruction size != 1 */ | |
156 | .default_is_stmt = 1, /* we don't take care about basic block */ | |
157 | .line_base = -5, /* sensible value for line base ... */ | |
158 | .line_range = -14, /* ... and line range are guessed statically */ | |
159 | .opcode_base = DW_LNS_num_opcode | |
160 | }; | |
161 | ||
162 | static ubyte standard_opcode_length[] = | |
163 | { | |
164 | 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 | |
165 | }; | |
166 | #if 0 | |
167 | { | |
168 | [DW_LNS_advance_pc] = 1, | |
169 | [DW_LNS_advance_line] = 1, | |
170 | [DW_LNS_set_file] = 1, | |
171 | [DW_LNS_set_column] = 1, | |
172 | [DW_LNS_fixed_advance_pc] = 1, | |
173 | [DW_LNS_set_isa] = 1, | |
174 | }; | |
175 | #endif | |
176 | ||
177 | /* field filled at run time are marked with -1 */ | |
178 | static struct compilation_unit_header default_comp_unit_header = { | |
179 | .total_length = -1, | |
180 | .version = 2, | |
181 | .debug_abbrev_offset = 0, /* we reuse the same abbrev entries for all comp unit */ | |
182 | .pointer_size = sizeof(void *) | |
183 | }; | |
184 | ||
185 | static void emit_uword(struct buffer_ext *be, uword data) | |
186 | { | |
187 | buffer_ext_add(be, &data, sizeof(uword)); | |
188 | } | |
189 | ||
190 | static void emit_string(struct buffer_ext *be, const char *s) | |
191 | { | |
192 | buffer_ext_add(be, (void *)s, strlen(s) + 1); | |
193 | } | |
194 | ||
195 | static void emit_unsigned_LEB128(struct buffer_ext *be, | |
196 | unsigned long data) | |
197 | { | |
198 | do { | |
199 | ubyte cur = data & 0x7F; | |
200 | data >>= 7; | |
201 | if (data) | |
202 | cur |= 0x80; | |
203 | buffer_ext_add(be, &cur, 1); | |
204 | } while (data); | |
205 | } | |
206 | ||
207 | static void emit_signed_LEB128(struct buffer_ext *be, long data) | |
208 | { | |
209 | int more = 1; | |
210 | int negative = data < 0; | |
211 | int size = sizeof(long) * CHAR_BIT; | |
212 | while (more) { | |
213 | ubyte cur = data & 0x7F; | |
214 | data >>= 7; | |
215 | if (negative) | |
216 | data |= - (1 << (size - 7)); | |
217 | if ((data == 0 && !(cur & 0x40)) || | |
218 | (data == -1l && (cur & 0x40))) | |
219 | more = 0; | |
220 | else | |
221 | cur |= 0x80; | |
222 | buffer_ext_add(be, &cur, 1); | |
223 | } | |
224 | } | |
225 | ||
226 | static void emit_extended_opcode(struct buffer_ext *be, ubyte opcode, | |
227 | void *data, size_t data_len) | |
228 | { | |
229 | buffer_ext_add(be, (char *)"", 1); | |
230 | ||
231 | emit_unsigned_LEB128(be, data_len + 1); | |
232 | ||
233 | buffer_ext_add(be, &opcode, 1); | |
234 | buffer_ext_add(be, data, data_len); | |
235 | } | |
236 | ||
237 | static void emit_opcode(struct buffer_ext *be, ubyte opcode) | |
238 | { | |
239 | buffer_ext_add(be, &opcode, 1); | |
240 | } | |
241 | ||
242 | static void emit_opcode_signed(struct buffer_ext *be, | |
243 | ubyte opcode, long data) | |
244 | { | |
245 | buffer_ext_add(be, &opcode, 1); | |
246 | emit_signed_LEB128(be, data); | |
247 | } | |
248 | ||
249 | static void emit_opcode_unsigned(struct buffer_ext *be, ubyte opcode, | |
250 | unsigned long data) | |
251 | { | |
252 | buffer_ext_add(be, &opcode, 1); | |
253 | emit_unsigned_LEB128(be, data); | |
254 | } | |
255 | ||
256 | static void emit_advance_pc(struct buffer_ext *be, unsigned long delta_pc) | |
257 | { | |
258 | emit_opcode_unsigned(be, DW_LNS_advance_pc, delta_pc); | |
259 | } | |
260 | ||
261 | static void emit_advance_lineno(struct buffer_ext *be, long delta_lineno) | |
262 | { | |
263 | emit_opcode_signed(be, DW_LNS_advance_line, delta_lineno); | |
264 | } | |
265 | ||
266 | static void emit_lne_end_of_sequence(struct buffer_ext *be) | |
267 | { | |
268 | emit_extended_opcode(be, DW_LNE_end_sequence, NULL, 0); | |
269 | } | |
270 | ||
271 | static void emit_set_file(struct buffer_ext *be, unsigned long idx) | |
272 | { | |
273 | emit_opcode_unsigned(be, DW_LNS_set_file, idx); | |
274 | } | |
275 | ||
276 | static void emit_lne_define_filename(struct buffer_ext *be, | |
277 | const char *filename) | |
278 | { | |
279 | buffer_ext_add(be, (void *)"", 1); | |
280 | ||
281 | /* LNE field, strlen(filename) + zero termination, 3 bytes for: the dir entry, timestamp, filesize */ | |
282 | emit_unsigned_LEB128(be, strlen(filename) + 5); | |
283 | emit_opcode(be, DW_LNE_define_file); | |
284 | emit_string(be, filename); | |
285 | /* directory index 0=do not know */ | |
286 | emit_unsigned_LEB128(be, 0); | |
287 | /* last modification date on file 0=do not know */ | |
288 | emit_unsigned_LEB128(be, 0); | |
289 | /* filesize 0=do not know */ | |
290 | emit_unsigned_LEB128(be, 0); | |
291 | } | |
292 | ||
293 | static void emit_lne_set_address(struct buffer_ext *be, | |
294 | void *address) | |
295 | { | |
296 | emit_extended_opcode(be, DW_LNE_set_address, &address, sizeof(unsigned long)); | |
297 | } | |
298 | ||
299 | static ubyte get_special_opcode(struct debug_entry *ent, | |
300 | unsigned int last_line, | |
301 | unsigned long last_vma) | |
302 | { | |
303 | unsigned int temp; | |
304 | unsigned long delta_addr; | |
305 | ||
306 | /* | |
307 | * delta from line_base | |
308 | */ | |
309 | temp = (ent->lineno - last_line) - default_debug_line_header.line_base; | |
310 | ||
311 | if (temp >= default_debug_line_header.line_range) | |
312 | return 0; | |
313 | ||
314 | /* | |
315 | * delta of addresses | |
316 | */ | |
317 | delta_addr = (ent->addr - last_vma) / default_debug_line_header.minimum_instruction_length; | |
318 | ||
319 | /* This is not sufficient to ensure opcode will be in [0-256] but | |
320 | * sufficient to ensure when summing with the delta lineno we will | |
321 | * not overflow the unsigned long opcode */ | |
322 | ||
323 | if (delta_addr <= 256 / default_debug_line_header.line_range) { | |
324 | unsigned long opcode = temp + | |
325 | (delta_addr * default_debug_line_header.line_range) + | |
326 | default_debug_line_header.opcode_base; | |
327 | ||
328 | return opcode <= 255 ? opcode : 0; | |
329 | } | |
330 | return 0; | |
331 | } | |
332 | ||
333 | static void emit_lineno_info(struct buffer_ext *be, | |
334 | struct debug_entry *ent, size_t nr_entry, | |
335 | unsigned long code_addr) | |
336 | { | |
337 | size_t i; | |
338 | ||
339 | /* | |
340 | * Machine state at start of a statement program | |
341 | * address = 0 | |
342 | * file = 1 | |
343 | * line = 1 | |
344 | * column = 0 | |
345 | * is_stmt = default_is_stmt as given in the debug_line_header | |
346 | * basic block = 0 | |
347 | * end sequence = 0 | |
348 | */ | |
349 | ||
350 | /* start state of the state machine we take care of */ | |
1e4bd2ae | 351 | unsigned long last_vma = 0; |
598b7c69 SE |
352 | char const *cur_filename = NULL; |
353 | unsigned long cur_file_idx = 0; | |
354 | int last_line = 1; | |
355 | ||
356 | emit_lne_set_address(be, (void *)code_addr); | |
357 | ||
358 | for (i = 0; i < nr_entry; i++, ent = debug_entry_next(ent)) { | |
359 | int need_copy = 0; | |
360 | ubyte special_opcode; | |
361 | ||
362 | /* | |
363 | * check if filename changed, if so add it | |
364 | */ | |
365 | if (!cur_filename || strcmp(cur_filename, ent->name)) { | |
366 | emit_lne_define_filename(be, ent->name); | |
367 | cur_filename = ent->name; | |
368 | emit_set_file(be, ++cur_file_idx); | |
369 | need_copy = 1; | |
370 | } | |
371 | ||
372 | special_opcode = get_special_opcode(ent, last_line, last_vma); | |
373 | if (special_opcode != 0) { | |
374 | last_line = ent->lineno; | |
375 | last_vma = ent->addr; | |
376 | emit_opcode(be, special_opcode); | |
377 | } else { | |
378 | /* | |
379 | * lines differ, emit line delta | |
380 | */ | |
381 | if (last_line != ent->lineno) { | |
382 | emit_advance_lineno(be, ent->lineno - last_line); | |
383 | last_line = ent->lineno; | |
384 | need_copy = 1; | |
385 | } | |
386 | /* | |
387 | * addresses differ, emit address delta | |
388 | */ | |
389 | if (last_vma != ent->addr) { | |
390 | emit_advance_pc(be, ent->addr - last_vma); | |
391 | last_vma = ent->addr; | |
392 | need_copy = 1; | |
393 | } | |
394 | /* | |
395 | * add new row to matrix | |
396 | */ | |
397 | if (need_copy) | |
398 | emit_opcode(be, DW_LNS_copy); | |
399 | } | |
400 | } | |
401 | } | |
402 | ||
403 | static void add_debug_line(struct buffer_ext *be, | |
404 | struct debug_entry *ent, size_t nr_entry, | |
405 | unsigned long code_addr) | |
406 | { | |
407 | struct debug_line_header * dbg_header; | |
408 | size_t old_size; | |
409 | ||
410 | old_size = buffer_ext_size(be); | |
411 | ||
412 | buffer_ext_add(be, (void *)&default_debug_line_header, | |
413 | sizeof(default_debug_line_header)); | |
414 | ||
415 | buffer_ext_add(be, &standard_opcode_length, sizeof(standard_opcode_length)); | |
416 | ||
417 | // empty directory entry | |
418 | buffer_ext_add(be, (void *)"", 1); | |
419 | ||
420 | // empty filename directory | |
421 | buffer_ext_add(be, (void *)"", 1); | |
422 | ||
423 | dbg_header = buffer_ext_addr(be) + old_size; | |
424 | dbg_header->prolog_length = (buffer_ext_size(be) - old_size) - | |
425 | offsetof(struct debug_line_header, minimum_instruction_length); | |
426 | ||
427 | emit_lineno_info(be, ent, nr_entry, code_addr); | |
428 | ||
429 | emit_lne_end_of_sequence(be); | |
430 | ||
431 | dbg_header = buffer_ext_addr(be) + old_size; | |
432 | dbg_header->total_length = (buffer_ext_size(be) - old_size) - | |
433 | offsetof(struct debug_line_header, version); | |
434 | } | |
435 | ||
436 | static void | |
437 | add_debug_abbrev(struct buffer_ext *be) | |
438 | { | |
439 | emit_unsigned_LEB128(be, 1); | |
440 | emit_unsigned_LEB128(be, DW_TAG_compile_unit); | |
441 | emit_unsigned_LEB128(be, DW_CHILDREN_yes); | |
442 | emit_unsigned_LEB128(be, DW_AT_stmt_list); | |
443 | emit_unsigned_LEB128(be, DW_FORM_data4); | |
444 | emit_unsigned_LEB128(be, 0); | |
445 | emit_unsigned_LEB128(be, 0); | |
446 | emit_unsigned_LEB128(be, 0); | |
447 | } | |
448 | ||
449 | static void | |
450 | add_compilation_unit(struct buffer_ext *be, | |
451 | size_t offset_debug_line) | |
452 | { | |
453 | struct compilation_unit_header *comp_unit_header; | |
454 | size_t old_size = buffer_ext_size(be); | |
455 | ||
456 | buffer_ext_add(be, &default_comp_unit_header, | |
457 | sizeof(default_comp_unit_header)); | |
458 | ||
459 | emit_unsigned_LEB128(be, 1); | |
460 | emit_uword(be, offset_debug_line); | |
461 | ||
462 | comp_unit_header = buffer_ext_addr(be) + old_size; | |
463 | comp_unit_header->total_length = (buffer_ext_size(be) - old_size) - | |
464 | offsetof(struct compilation_unit_header, version); | |
465 | } | |
466 | ||
467 | static int | |
468 | jit_process_debug_info(uint64_t code_addr, | |
469 | void *debug, int nr_debug_entries, | |
470 | struct buffer_ext *dl, | |
471 | struct buffer_ext *da, | |
472 | struct buffer_ext *di) | |
473 | { | |
474 | struct debug_entry *ent = debug; | |
475 | int i; | |
476 | ||
477 | for (i = 0; i < nr_debug_entries; i++) { | |
478 | ent->addr = ent->addr - code_addr; | |
479 | ent = debug_entry_next(ent); | |
480 | } | |
481 | add_compilation_unit(di, buffer_ext_size(dl)); | |
1e4bd2ae | 482 | add_debug_line(dl, debug, nr_debug_entries, GEN_ELF_TEXT_OFFSET); |
598b7c69 SE |
483 | add_debug_abbrev(da); |
484 | if (0) buffer_ext_dump(da, "abbrev"); | |
485 | ||
486 | return 0; | |
487 | } | |
488 | ||
489 | int | |
490 | jit_add_debug_info(Elf *e, uint64_t code_addr, void *debug, int nr_debug_entries) | |
491 | { | |
492 | Elf_Data *d; | |
493 | Elf_Scn *scn; | |
494 | Elf_Shdr *shdr; | |
495 | struct buffer_ext dl, di, da; | |
dc67c783 | 496 | int ret = -1; |
598b7c69 SE |
497 | |
498 | buffer_ext_init(&dl); | |
499 | buffer_ext_init(&di); | |
500 | buffer_ext_init(&da); | |
501 | ||
dc67c783 IR |
502 | if (jit_process_debug_info(code_addr, debug, nr_debug_entries, &dl, &da, &di)) |
503 | goto out; | |
504 | ||
598b7c69 SE |
505 | /* |
506 | * setup .debug_line section | |
507 | */ | |
508 | scn = elf_newscn(e); | |
509 | if (!scn) { | |
510 | warnx("cannot create section"); | |
dc67c783 | 511 | goto out; |
598b7c69 SE |
512 | } |
513 | ||
514 | d = elf_newdata(scn); | |
515 | if (!d) { | |
516 | warnx("cannot get new data"); | |
dc67c783 | 517 | goto out; |
598b7c69 SE |
518 | } |
519 | ||
520 | d->d_align = 1; | |
521 | d->d_off = 0LL; | |
522 | d->d_buf = buffer_ext_addr(&dl); | |
523 | d->d_type = ELF_T_BYTE; | |
524 | d->d_size = buffer_ext_size(&dl); | |
525 | d->d_version = EV_CURRENT; | |
526 | ||
527 | shdr = elf_getshdr(scn); | |
528 | if (!shdr) { | |
529 | warnx("cannot get section header"); | |
dc67c783 | 530 | goto out; |
598b7c69 SE |
531 | } |
532 | ||
533 | shdr->sh_name = 52; /* .debug_line */ | |
534 | shdr->sh_type = SHT_PROGBITS; | |
535 | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | |
536 | shdr->sh_flags = 0; | |
537 | shdr->sh_entsize = 0; | |
538 | ||
539 | /* | |
540 | * setup .debug_info section | |
541 | */ | |
542 | scn = elf_newscn(e); | |
543 | if (!scn) { | |
544 | warnx("cannot create section"); | |
dc67c783 | 545 | goto out; |
598b7c69 SE |
546 | } |
547 | ||
548 | d = elf_newdata(scn); | |
549 | if (!d) { | |
550 | warnx("cannot get new data"); | |
dc67c783 | 551 | goto out; |
598b7c69 SE |
552 | } |
553 | ||
554 | d->d_align = 1; | |
555 | d->d_off = 0LL; | |
556 | d->d_buf = buffer_ext_addr(&di); | |
557 | d->d_type = ELF_T_BYTE; | |
558 | d->d_size = buffer_ext_size(&di); | |
559 | d->d_version = EV_CURRENT; | |
560 | ||
561 | shdr = elf_getshdr(scn); | |
562 | if (!shdr) { | |
563 | warnx("cannot get section header"); | |
dc67c783 | 564 | goto out; |
598b7c69 SE |
565 | } |
566 | ||
567 | shdr->sh_name = 64; /* .debug_info */ | |
568 | shdr->sh_type = SHT_PROGBITS; | |
569 | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | |
570 | shdr->sh_flags = 0; | |
571 | shdr->sh_entsize = 0; | |
572 | ||
573 | /* | |
574 | * setup .debug_abbrev section | |
575 | */ | |
576 | scn = elf_newscn(e); | |
577 | if (!scn) { | |
578 | warnx("cannot create section"); | |
dc67c783 | 579 | goto out; |
598b7c69 SE |
580 | } |
581 | ||
582 | d = elf_newdata(scn); | |
583 | if (!d) { | |
584 | warnx("cannot get new data"); | |
dc67c783 | 585 | goto out; |
598b7c69 SE |
586 | } |
587 | ||
588 | d->d_align = 1; | |
589 | d->d_off = 0LL; | |
590 | d->d_buf = buffer_ext_addr(&da); | |
591 | d->d_type = ELF_T_BYTE; | |
592 | d->d_size = buffer_ext_size(&da); | |
593 | d->d_version = EV_CURRENT; | |
594 | ||
595 | shdr = elf_getshdr(scn); | |
596 | if (!shdr) { | |
597 | warnx("cannot get section header"); | |
dc67c783 | 598 | goto out; |
598b7c69 SE |
599 | } |
600 | ||
601 | shdr->sh_name = 76; /* .debug_info */ | |
602 | shdr->sh_type = SHT_PROGBITS; | |
603 | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | |
604 | shdr->sh_flags = 0; | |
605 | shdr->sh_entsize = 0; | |
606 | ||
607 | /* | |
608 | * now we update the ELF image with all the sections | |
609 | */ | |
dc67c783 | 610 | if (elf_update(e, ELF_C_WRITE) < 0) |
598b7c69 | 611 | warnx("elf_update debug failed"); |
dc67c783 IR |
612 | else |
613 | ret = 0; | |
614 | ||
615 | out: | |
616 | buffer_ext_exit(&dl); | |
617 | buffer_ext_exit(&di); | |
618 | buffer_ext_exit(&da); | |
619 | return ret; | |
598b7c69 | 620 | } |