1 | /*
|
---|
2 | * Copyright (c) 2023 Jiří Zárevúcky
|
---|
3 | * All rights reserved.
|
---|
4 | *
|
---|
5 | * Redistribution and use in source and binary forms, with or without
|
---|
6 | * modification, are permitted provided that the following conditions
|
---|
7 | * are met:
|
---|
8 | *
|
---|
9 | * - Redistributions of source code must retain the above copyright
|
---|
10 | * notice, this list of conditions and the following disclaimer.
|
---|
11 | * - Redistributions in binary form must reproduce the above copyright
|
---|
12 | * notice, this list of conditions and the following disclaimer in the
|
---|
13 | * documentation and/or other materials provided with the distribution.
|
---|
14 | * - The name of the author may not be used to endorse or promote products
|
---|
15 | * derived from this software without specific prior written permission.
|
---|
16 | *
|
---|
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
---|
18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
---|
19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
---|
20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
---|
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
---|
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
---|
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
27 | */
|
---|
28 |
|
---|
29 | #include <stdatomic.h>
|
---|
30 |
|
---|
31 | #include <stdlib.h>
|
---|
32 |
|
---|
33 | #include <debug/constants.h>
|
---|
34 | #include <debug/line.h>
|
---|
35 | #include <debug/names.h>
|
---|
36 | #include <debug/sections.h>
|
---|
37 |
|
---|
38 | #include "util.h"
|
---|
39 |
|
---|
40 | static inline void line_program_reset(struct debug_line_program *lp)
|
---|
41 | {
|
---|
42 | lp->address = 0;
|
---|
43 | lp->op_advance = 0;
|
---|
44 | lp->file = 1;
|
---|
45 | lp->line = 1;
|
---|
46 | lp->column = 0;
|
---|
47 | lp->end_sequence = false;
|
---|
48 | }
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * Quickly advance program just past the next sequence end, without processing
|
---|
52 | * anything on the way. This is useful for when we just want to get start
|
---|
53 | * addresses of every range to build a table.
|
---|
54 | *
|
---|
55 | * @param lp
|
---|
56 | */
|
---|
57 | static void debug_line_program_skip_to_sequence_end(struct debug_line_program *lp)
|
---|
58 | {
|
---|
59 | const uint8_t opcode_base = lp->hdr->opcode_base;
|
---|
60 |
|
---|
61 | const uint8_t *program = lp->program;
|
---|
62 | const uint8_t *const program_end = lp->program_end;
|
---|
63 |
|
---|
64 | if (lp->end_sequence)
|
---|
65 | line_program_reset(lp);
|
---|
66 |
|
---|
67 | while (program < program_end) {
|
---|
68 | uint8_t opcode = read_byte(&program, program_end);
|
---|
69 |
|
---|
70 | if (opcode >= opcode_base) {
|
---|
71 | // Special opcode.
|
---|
72 | continue;
|
---|
73 | }
|
---|
74 |
|
---|
75 | switch (opcode) {
|
---|
76 | case DW_LNS_fixed_advance_pc:
|
---|
77 | safe_increment(&program, program_end, 2);
|
---|
78 | break;
|
---|
79 |
|
---|
80 | case DW_LNS_copy:
|
---|
81 | case DW_LNS_negate_stmt:
|
---|
82 | case DW_LNS_set_basic_block:
|
---|
83 | case DW_LNS_set_prologue_end:
|
---|
84 | case DW_LNS_set_epilogue_begin:
|
---|
85 | case DW_LNS_const_add_pc:
|
---|
86 | break;
|
---|
87 |
|
---|
88 | case DW_LNS_advance_pc:
|
---|
89 | case DW_LNS_advance_line:
|
---|
90 | case DW_LNS_set_file:
|
---|
91 | case DW_LNS_set_column:
|
---|
92 | case DW_LNS_set_isa:
|
---|
93 | skip_leb128(&program, program_end);
|
---|
94 | break;
|
---|
95 |
|
---|
96 | case 0:
|
---|
97 | // Extended opcodes.
|
---|
98 | size_t length = read_uleb128(&program, program_end);
|
---|
99 |
|
---|
100 | if (read_byte(&program, program_end) == DW_LNE_end_sequence) {
|
---|
101 | lp->program = program;
|
---|
102 | lp->end_sequence = true;
|
---|
103 | return;
|
---|
104 | }
|
---|
105 |
|
---|
106 | safe_increment(&program, program_end, length - 1);
|
---|
107 | break;
|
---|
108 |
|
---|
109 | default:
|
---|
110 | // Unknown standard opcode.
|
---|
111 |
|
---|
112 | // If the opcode length array is truncated, there is already
|
---|
113 | // something wrong with the parse, so we don't care if we misparse
|
---|
114 | // the remainder.
|
---|
115 | if (opcode < lp->hdr->standard_opcode_lengths_size) {
|
---|
116 | int len = lp->hdr->standard_opcode_lengths[opcode];
|
---|
117 | for (int i = 0; i < len; i++) {
|
---|
118 | // Drop arguments.
|
---|
119 | skip_leb128(&program, program_end);
|
---|
120 | }
|
---|
121 | }
|
---|
122 | }
|
---|
123 | }
|
---|
124 |
|
---|
125 | lp->program = program;
|
---|
126 | lp->truncated = true;
|
---|
127 | }
|
---|
128 |
|
---|
129 | static void transfer_op_advance(struct debug_line_program *lp)
|
---|
130 | {
|
---|
131 | if (lp->hdr->version < 5) {
|
---|
132 | lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
|
---|
133 | lp->op_advance = 0;
|
---|
134 | } else {
|
---|
135 | int maxops = lp->hdr->v5.maximum_operations_per_instruction;
|
---|
136 |
|
---|
137 | // For branch prediction, tell GCC maxops is likely to be 1, because
|
---|
138 | // I don't want to punish sensible architectures for VLIW madness.
|
---|
139 | if (__builtin_expect(maxops == 1, 1)) {
|
---|
140 | lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
|
---|
141 | lp->op_advance = 0;
|
---|
142 | } else {
|
---|
143 | int adv = lp->op_advance;
|
---|
144 | int d = adv / maxops;
|
---|
145 | int r = adv % maxops;
|
---|
146 |
|
---|
147 | lp->address += d * lp->hdr->minimum_instruction_length;
|
---|
148 | lp->op_advance = r;
|
---|
149 | }
|
---|
150 | }
|
---|
151 | }
|
---|
152 |
|
---|
153 | static void debug_line_program_advance(struct debug_line_program *lp)
|
---|
154 | {
|
---|
155 | const uint8_t opcode_base = lp->hdr->opcode_base;
|
---|
156 | const int8_t line_base = lp->hdr->line_base;
|
---|
157 | const uint8_t line_range = lp->hdr->line_range;
|
---|
158 | const int const_advance = (255 - opcode_base) / lp->hdr->line_range;
|
---|
159 |
|
---|
160 | const uint8_t *program = lp->program;
|
---|
161 | const uint8_t *const program_end = lp->program_end;
|
---|
162 |
|
---|
163 | if (lp->end_sequence)
|
---|
164 | line_program_reset(lp);
|
---|
165 |
|
---|
166 | while (program < program_end) {
|
---|
167 | uint8_t opcode = read_byte(&program, program_end);
|
---|
168 |
|
---|
169 | if (opcode >= opcode_base) {
|
---|
170 | // Special opcode.
|
---|
171 | int adjusted = opcode - opcode_base;
|
---|
172 | DEBUGF("DW_LNS_special(%d)\n", adjusted);
|
---|
173 |
|
---|
174 | lp->op_advance += adjusted / line_range;
|
---|
175 | lp->line += line_base + (adjusted % line_range);
|
---|
176 |
|
---|
177 | transfer_op_advance(lp);
|
---|
178 | lp->program = program;
|
---|
179 | return;
|
---|
180 | }
|
---|
181 |
|
---|
182 | const char *opname = dw_lns_name(opcode);
|
---|
183 |
|
---|
184 | uint64_t arg;
|
---|
185 | uint16_t arg16;
|
---|
186 |
|
---|
187 | switch (opcode) {
|
---|
188 | case DW_LNS_copy:
|
---|
189 | DEBUGF("%s()\n", opname);
|
---|
190 |
|
---|
191 | transfer_op_advance(lp);
|
---|
192 | lp->program = program;
|
---|
193 | return;
|
---|
194 |
|
---|
195 | case DW_LNS_advance_pc:
|
---|
196 | arg = read_uleb128(&program, program_end);
|
---|
197 | DEBUGF("%s(%" PRIu64 ")\n", opname, arg);
|
---|
198 | lp->op_advance += arg;
|
---|
199 | break;
|
---|
200 |
|
---|
201 | case DW_LNS_advance_line:
|
---|
202 | lp->line += read_sleb128(&program, program_end);
|
---|
203 | DEBUGF("%s(line = %d)\n", opname, lp->line);
|
---|
204 | break;
|
---|
205 |
|
---|
206 | case DW_LNS_set_file:
|
---|
207 | lp->file = read_uleb128(&program, program_end);
|
---|
208 | DEBUGF("%s(%d)\n", opname, lp->file);
|
---|
209 | break;
|
---|
210 |
|
---|
211 | case DW_LNS_set_column:
|
---|
212 | lp->column = read_uleb128(&program, program_end);
|
---|
213 | DEBUGF("%s(%d)\n", opname, lp->column);
|
---|
214 | break;
|
---|
215 |
|
---|
216 | case DW_LNS_negate_stmt:
|
---|
217 | case DW_LNS_set_basic_block:
|
---|
218 | case DW_LNS_set_prologue_end:
|
---|
219 | case DW_LNS_set_epilogue_begin:
|
---|
220 | DEBUGF("%s()\n", opname);
|
---|
221 | // ignore
|
---|
222 | break;
|
---|
223 |
|
---|
224 | case DW_LNS_set_isa:
|
---|
225 | // discard argument and ignore
|
---|
226 | arg = read_uleb128(&program, program_end);
|
---|
227 | DEBUGF("%s(%" PRIu64 ")\n", opname, arg);
|
---|
228 | break;
|
---|
229 |
|
---|
230 | case DW_LNS_const_add_pc:
|
---|
231 | DEBUGF("%s(%d)\n", opname, const_advance);
|
---|
232 | lp->op_advance += const_advance;
|
---|
233 | break;
|
---|
234 |
|
---|
235 | case DW_LNS_fixed_advance_pc:
|
---|
236 | arg16 = read_uint16(&program, program_end);
|
---|
237 | DEBUGF("%s(%u)\n", opname, arg16);
|
---|
238 |
|
---|
239 | transfer_op_advance(lp);
|
---|
240 | lp->address += arg16;
|
---|
241 | lp->op_advance = 0;
|
---|
242 | break;
|
---|
243 |
|
---|
244 | case 0:
|
---|
245 | /* Extended opcodes. */
|
---|
246 | size_t length = read_uleb128(&program, program_end);
|
---|
247 |
|
---|
248 | opcode = read_byte(&program, program_end);
|
---|
249 | const char *opname = dw_lne_name(opcode);
|
---|
250 |
|
---|
251 | switch (opcode) {
|
---|
252 | case DW_LNE_end_sequence:
|
---|
253 | DEBUGF("%s:%zu()\n", opname, length);
|
---|
254 |
|
---|
255 | transfer_op_advance(lp);
|
---|
256 | lp->program = program;
|
---|
257 | lp->end_sequence = true;
|
---|
258 | return;
|
---|
259 |
|
---|
260 | case DW_LNE_set_address:
|
---|
261 | lp->address = read_uint(&program, program_end, sizeof(uintptr_t));
|
---|
262 | lp->op_advance = 0;
|
---|
263 |
|
---|
264 | DEBUGF("%s:%zu(0x%zx)\n", opname, length, lp->address);
|
---|
265 | break;
|
---|
266 |
|
---|
267 | case DW_LNE_set_discriminator:
|
---|
268 | uint64_t arg = read_uleb128(&program, program_end);
|
---|
269 | DEBUGF("%s:%zu(%" PRIu64 ")\n", opname, length, arg);
|
---|
270 | break;
|
---|
271 |
|
---|
272 | default:
|
---|
273 | DEBUGF("unknown extended opcode %d:%zu\n", opcode, length);
|
---|
274 | safe_increment(&program, program_end, length - 1);
|
---|
275 | }
|
---|
276 | break;
|
---|
277 |
|
---|
278 | default:
|
---|
279 | DEBUGF("unknown standard opcode %d\n", opcode);
|
---|
280 |
|
---|
281 | // If the opcode length array is truncated, there is already
|
---|
282 | // something wrong with the parse, so we don't care if we misparse
|
---|
283 | // the remainder.
|
---|
284 | if (opcode < lp->hdr->standard_opcode_lengths_size) {
|
---|
285 | int len = lp->hdr->standard_opcode_lengths[opcode];
|
---|
286 | for (int i = 0; i < len; i++) {
|
---|
287 | // Drop arguments.
|
---|
288 | skip_leb128(&program, program_end);
|
---|
289 | }
|
---|
290 | }
|
---|
291 | }
|
---|
292 | }
|
---|
293 |
|
---|
294 | transfer_op_advance(lp);
|
---|
295 | lp->program = program;
|
---|
296 | lp->truncated = true;
|
---|
297 | }
|
---|
298 |
|
---|
299 | static void debug_line_program_header_parse(debug_sections_t *scs, const uint8_t *data, const uint8_t *data_end, struct debug_line_program_header *hdr)
|
---|
300 | {
|
---|
301 | const uint8_t *unit_start = data;
|
---|
302 |
|
---|
303 | #define FIELD(name, spec, expr) (hdr->name = expr, DEBUGF(#name ": %" spec "\n", hdr->name))
|
---|
304 |
|
---|
305 | unsigned width;
|
---|
306 |
|
---|
307 | FIELD(unit_length, PRId64, read_initial_length(&data, data_end, &width));
|
---|
308 | FIELD(width, "d", width);
|
---|
309 |
|
---|
310 | hdr->unit_end = data;
|
---|
311 | safe_increment(&hdr->unit_end, data_end, hdr->unit_length);
|
---|
312 | DEBUGF("unit size: %zu\n", hdr->unit_end - unit_start);
|
---|
313 |
|
---|
314 | data_end = hdr->unit_end;
|
---|
315 |
|
---|
316 | FIELD(version, "u", read_uint16(&data, data_end));
|
---|
317 |
|
---|
318 | if (hdr->version < 3 || hdr->version > 5) {
|
---|
319 | // Not supported.
|
---|
320 | hdr->header_end = hdr->unit_end;
|
---|
321 | return;
|
---|
322 | }
|
---|
323 |
|
---|
324 | if (hdr->version >= 5) {
|
---|
325 | FIELD(v5.address_size, "u", read_byte(&data, data_end));
|
---|
326 | FIELD(v5.segment_selector_size, "u", read_byte(&data, data_end));
|
---|
327 | }
|
---|
328 |
|
---|
329 | FIELD(header_length, PRIu64, read_uint(&data, data_end, width));
|
---|
330 |
|
---|
331 | hdr->header_end = data;
|
---|
332 | safe_increment(&hdr->header_end, data_end, hdr->header_length);
|
---|
333 |
|
---|
334 | data_end = hdr->header_end;
|
---|
335 |
|
---|
336 | FIELD(minimum_instruction_length, "u", read_byte(&data, data_end));
|
---|
337 | if (hdr->version >= 5)
|
---|
338 | FIELD(v5.maximum_operations_per_instruction, "u", read_byte(&data, data_end));
|
---|
339 | FIELD(default_is_stmt, "u", read_byte(&data, data_end));
|
---|
340 | FIELD(line_base, "d", (int8_t) read_byte(&data, data_end));
|
---|
341 | FIELD(line_range, "u", read_byte(&data, data_end));
|
---|
342 | FIELD(opcode_base, "u", read_byte(&data, data_end));
|
---|
343 |
|
---|
344 | hdr->standard_opcode_lengths = data;
|
---|
345 | safe_increment(&data, data_end, hdr->opcode_base - 1);
|
---|
346 | hdr->standard_opcode_lengths_size = data - hdr->standard_opcode_lengths;
|
---|
347 |
|
---|
348 | if (hdr->version < 5) {
|
---|
349 | hdr->v3.include_directories = data;
|
---|
350 | while (data < data_end && *data != 0)
|
---|
351 | skip_string(&data, data_end);
|
---|
352 | hdr->v3.include_directories_end = data;
|
---|
353 |
|
---|
354 | // Sanitize the list a little.
|
---|
355 | while (hdr->v3.include_directories < hdr->v3.include_directories_end && hdr->v3.include_directories_end[-1] != 0)
|
---|
356 | hdr->v3.include_directories_end--;
|
---|
357 |
|
---|
358 | safe_increment(&data, data_end, 1);
|
---|
359 |
|
---|
360 | hdr->v3.file_names = data;
|
---|
361 | }
|
---|
362 |
|
---|
363 | if (hdr->version >= 5) {
|
---|
364 | FIELD(v5.directory_entry_format_count, "u", read_byte(&data, data_end));
|
---|
365 |
|
---|
366 | hdr->v5.directory_entry_format = data;
|
---|
367 | skip_format(&data, data_end, hdr->v5.directory_entry_format_count);
|
---|
368 | hdr->v5.directory_entry_format_end = data;
|
---|
369 |
|
---|
370 | print_format("directory_entry_format",
|
---|
371 | hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end);
|
---|
372 |
|
---|
373 | FIELD(v5.directories_count, PRIu64, read_uleb128(&data, data_end));
|
---|
374 |
|
---|
375 | hdr->v5.directories = data;
|
---|
376 | skip_formatted_list(&data, data_end, hdr->v5.directories_count,
|
---|
377 | hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
|
---|
378 | hdr->v5.directories_end = data;
|
---|
379 |
|
---|
380 | print_formatted_list(scs, "directories", hdr->v5.directories, hdr->v5.directories_end,
|
---|
381 | hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
|
---|
382 |
|
---|
383 | FIELD(v5.file_name_entry_format_count, "u", read_byte(&data, data_end));
|
---|
384 |
|
---|
385 | hdr->v5.file_name_entry_format = data;
|
---|
386 | skip_format(&data, data_end, hdr->v5.file_name_entry_format_count);
|
---|
387 | hdr->v5.file_name_entry_format_end = data;
|
---|
388 |
|
---|
389 | print_format("file_name_entry_format",
|
---|
390 | hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end);
|
---|
391 |
|
---|
392 | FIELD(v5.file_names_count, PRIu64, read_uleb128(&data, data_end));
|
---|
393 |
|
---|
394 | hdr->v5.file_names = data;
|
---|
395 | skip_formatted_list(&data, data_end, hdr->v5.file_names_count,
|
---|
396 | hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
|
---|
397 | hdr->v5.file_names_end = data;
|
---|
398 |
|
---|
399 | print_formatted_list(scs, "file_names", hdr->v5.file_names, hdr->v5.file_names_end,
|
---|
400 | hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
|
---|
401 | }
|
---|
402 | }
|
---|
403 |
|
---|
404 | static bool has_usable_name(const uint8_t *format, const uint8_t *format_end, unsigned width)
|
---|
405 | {
|
---|
406 | // Check if there is an appropriate entry we can use.
|
---|
407 | bool has_usable_name = false;
|
---|
408 | const uint8_t *dummy = NULL;
|
---|
409 |
|
---|
410 | while (format < format_end) {
|
---|
411 | uint64_t type = read_uleb128(&format, format_end);
|
---|
412 | uint64_t form = read_uleb128(&format, format_end);
|
---|
413 |
|
---|
414 | if (type == DW_LNCT_path) {
|
---|
415 | if (form == DW_FORM_string || form == DW_FORM_line_strp) {
|
---|
416 | has_usable_name = true;
|
---|
417 | }
|
---|
418 | }
|
---|
419 |
|
---|
420 | if (!skip_data(form, &dummy, NULL, width)) {
|
---|
421 | // Encountered DW_FORM that we don't understand,
|
---|
422 | // which means we can't skip it.
|
---|
423 | return false;
|
---|
424 | }
|
---|
425 | }
|
---|
426 |
|
---|
427 | return has_usable_name;
|
---|
428 | }
|
---|
429 |
|
---|
430 | static const char *get_file_name_v3(struct debug_line_program_header *hdr, int file, int *dir)
|
---|
431 | {
|
---|
432 | if (file == 0) {
|
---|
433 | // We'd have to find and read the compilation unit header for this one,
|
---|
434 | // and we don't wanna.
|
---|
435 | return NULL;
|
---|
436 | }
|
---|
437 |
|
---|
438 | file--;
|
---|
439 |
|
---|
440 | const uint8_t *files = hdr->v3.file_names;
|
---|
441 | const uint8_t *files_end = hdr->header_end;
|
---|
442 |
|
---|
443 | for (int i = 0; i < file; i++) {
|
---|
444 | if (files >= files_end || *files == 0) {
|
---|
445 | // End of list.
|
---|
446 | return NULL;
|
---|
447 | }
|
---|
448 |
|
---|
449 | // Skip an entry.
|
---|
450 | skip_string(&files, files_end);
|
---|
451 | skip_leb128(&files, files_end);
|
---|
452 | skip_leb128(&files, files_end);
|
---|
453 | skip_leb128(&files, files_end);
|
---|
454 | }
|
---|
455 |
|
---|
456 | if (files >= files_end || *files == 0)
|
---|
457 | return NULL;
|
---|
458 |
|
---|
459 | const char *fn = (const char *) files;
|
---|
460 | skip_string(&files, files_end);
|
---|
461 | *dir = read_uleb128(&files, files_end);
|
---|
462 | return fn;
|
---|
463 | }
|
---|
464 |
|
---|
465 | static const char *get_file_name_v5(debug_sections_t *scs, struct debug_line_program_header *hdr, int file, int *dir)
|
---|
466 | {
|
---|
467 | // DWARF5 has dynamic layout for file information, which is why
|
---|
468 | // this is so horrible to decode. Enjoy.
|
---|
469 |
|
---|
470 | if (!has_usable_name(hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, hdr->width))
|
---|
471 | return NULL;
|
---|
472 |
|
---|
473 | const uint8_t *fns = hdr->v5.file_names;
|
---|
474 | const uint8_t *fns_end = hdr->v5.file_names_end;
|
---|
475 |
|
---|
476 | const char *filename = NULL;
|
---|
477 |
|
---|
478 | for (uint8_t i = 0; i < hdr->v5.file_names_count; i++) {
|
---|
479 |
|
---|
480 | const uint8_t *fnef = hdr->v5.file_name_entry_format;
|
---|
481 | const uint8_t *fnef_end = hdr->v5.file_name_entry_format_end;
|
---|
482 |
|
---|
483 | for (uint8_t j = 0; j < hdr->v5.file_name_entry_format_count; j++) {
|
---|
484 | uint64_t type = read_uleb128(&fnef, fnef_end);
|
---|
485 | uint64_t form = read_uleb128(&fnef, fnef_end);
|
---|
486 |
|
---|
487 | if (i == file && type == DW_LNCT_path) {
|
---|
488 | if (form == DW_FORM_string) {
|
---|
489 | filename = read_string(&fns, fns_end);
|
---|
490 | continue;
|
---|
491 | }
|
---|
492 |
|
---|
493 | if (form == DW_FORM_line_strp) {
|
---|
494 | uint64_t offset = read_uint(&fns, fns_end, hdr->width);
|
---|
495 | if (offset < scs->debug_line_str_size) {
|
---|
496 | filename = scs->debug_line_str + offset;
|
---|
497 | }
|
---|
498 | continue;
|
---|
499 | }
|
---|
500 | }
|
---|
501 |
|
---|
502 | if (i == file && type == DW_LNCT_directory_index) {
|
---|
503 | if (form == DW_FORM_data1) {
|
---|
504 | *dir = read_byte(&fns, fns_end);
|
---|
505 | continue;
|
---|
506 | }
|
---|
507 |
|
---|
508 | if (form == DW_FORM_data2) {
|
---|
509 | *dir = read_uint16(&fns, fns_end);
|
---|
510 | continue;
|
---|
511 | }
|
---|
512 |
|
---|
513 | if (form == DW_FORM_udata) {
|
---|
514 | *dir = read_uleb128(&fns, fns_end);
|
---|
515 | continue;
|
---|
516 | }
|
---|
517 | }
|
---|
518 |
|
---|
519 | skip_data(form, &fns, fns_end, hdr->width);
|
---|
520 | }
|
---|
521 |
|
---|
522 | if (i == file)
|
---|
523 | break;
|
---|
524 | }
|
---|
525 |
|
---|
526 | return filename;
|
---|
527 | }
|
---|
528 |
|
---|
529 | static const char *get_file_name(debug_sections_t *scs, struct debug_line_program_header *hdr, int file, int *dir)
|
---|
530 | {
|
---|
531 | switch (hdr->version) {
|
---|
532 | case 3:
|
---|
533 | case 4:
|
---|
534 | return get_file_name_v3(hdr, file, dir);
|
---|
535 | case 5:
|
---|
536 | return get_file_name_v5(scs, hdr, file, dir);
|
---|
537 | default:
|
---|
538 | return NULL;
|
---|
539 | }
|
---|
540 | }
|
---|
541 |
|
---|
542 | static const char *get_dir_name_v3(struct debug_line_program_header *hdr, int dir)
|
---|
543 | {
|
---|
544 | if (dir == 0)
|
---|
545 | return ".";
|
---|
546 |
|
---|
547 | const uint8_t *dirs = hdr->v3.include_directories;
|
---|
548 | const uint8_t *dirs_end = hdr->v3.include_directories_end;
|
---|
549 |
|
---|
550 | for (int i = 1; i < dir; i++) {
|
---|
551 | skip_string(&dirs, dirs_end);
|
---|
552 | }
|
---|
553 |
|
---|
554 | if (dirs < dirs_end)
|
---|
555 | return (const char *) dirs;
|
---|
556 | else
|
---|
557 | return NULL;
|
---|
558 | }
|
---|
559 |
|
---|
560 | static const char *get_dir_name_v5(debug_sections_t *scs, struct debug_line_program_header *hdr, int dir)
|
---|
561 | {
|
---|
562 | // TODO: basically a copypaste of get_file_name(). Try to deduplicate it.
|
---|
563 |
|
---|
564 | if (!has_usable_name(hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, hdr->width))
|
---|
565 | return NULL;
|
---|
566 |
|
---|
567 | const uint8_t *fns = hdr->v5.directories;
|
---|
568 | const uint8_t *fns_end = hdr->v5.directories_end;
|
---|
569 |
|
---|
570 | const char *filename = NULL;
|
---|
571 |
|
---|
572 | for (uint8_t i = 0; i < hdr->v5.directories_count; i++) {
|
---|
573 |
|
---|
574 | const uint8_t *fnef = hdr->v5.directory_entry_format;
|
---|
575 | const uint8_t *fnef_end = hdr->v5.directory_entry_format_end;
|
---|
576 |
|
---|
577 | for (uint8_t j = 0; j < hdr->v5.directory_entry_format_count; j++) {
|
---|
578 | uint64_t type = read_uleb128(&fnef, fnef_end);
|
---|
579 | uint64_t form = read_uleb128(&fnef, fnef_end);
|
---|
580 |
|
---|
581 | if (i == dir && type == DW_LNCT_path) {
|
---|
582 | if (form == DW_FORM_string) {
|
---|
583 | filename = read_string(&fns, fns_end);
|
---|
584 | continue;
|
---|
585 | }
|
---|
586 |
|
---|
587 | if (form == DW_FORM_line_strp) {
|
---|
588 | uint64_t offset = read_uint(&fns, fns_end, hdr->width);
|
---|
589 | if (offset < scs->debug_line_str_size) {
|
---|
590 | filename = scs->debug_line_str + offset;
|
---|
591 | }
|
---|
592 | continue;
|
---|
593 | }
|
---|
594 | }
|
---|
595 |
|
---|
596 | skip_data(form, &fns, fns_end, hdr->width);
|
---|
597 | }
|
---|
598 |
|
---|
599 | if (i == dir)
|
---|
600 | break;
|
---|
601 | }
|
---|
602 |
|
---|
603 | return filename;
|
---|
604 | }
|
---|
605 |
|
---|
606 | static const char *get_dir_name(debug_sections_t *scs, struct debug_line_program_header *hdr, int dir)
|
---|
607 | {
|
---|
608 | switch (hdr->version) {
|
---|
609 | case 3:
|
---|
610 | case 4:
|
---|
611 | return get_dir_name_v3(hdr, dir);
|
---|
612 | case 5:
|
---|
613 | return get_dir_name_v5(scs, hdr, dir);
|
---|
614 | default:
|
---|
615 | return NULL;
|
---|
616 | }
|
---|
617 | }
|
---|
618 |
|
---|
619 | static const uint8_t *find_line_program(debug_sections_t *scs, uintptr_t addr)
|
---|
620 | {
|
---|
621 | // TODO: use .debug_aranges to find the data quickly
|
---|
622 | // This implementation just iterates through the entire .debug_line
|
---|
623 |
|
---|
624 | uintptr_t closest_addr = 0;
|
---|
625 | const uint8_t *closest_prog = NULL;
|
---|
626 |
|
---|
627 | const uint8_t *debug_line_ptr = scs->debug_line;
|
---|
628 | const uint8_t *const debug_line_end = scs->debug_line + scs->debug_line_size;
|
---|
629 |
|
---|
630 | while (debug_line_ptr < debug_line_end) {
|
---|
631 | const uint8_t *prog = debug_line_ptr;
|
---|
632 |
|
---|
633 | // Parse header
|
---|
634 | struct debug_line_program_header hdr = { };
|
---|
635 | debug_line_program_header_parse(scs, prog, debug_line_end, &hdr);
|
---|
636 | assert(hdr.unit_end > debug_line_ptr);
|
---|
637 | assert(hdr.unit_end <= debug_line_end);
|
---|
638 | debug_line_ptr = hdr.unit_end;
|
---|
639 |
|
---|
640 | struct debug_line_program lp = debug_line_program_create(
|
---|
641 | hdr.header_end, hdr.unit_end, &hdr);
|
---|
642 |
|
---|
643 | while (lp.program < lp.program_end) {
|
---|
644 | // Find the start address of every sequence
|
---|
645 |
|
---|
646 | debug_line_program_advance(&lp);
|
---|
647 | DEBUGF("<< address: %zx, line: %d, column: %d >>\n", lp.address, lp.line, lp.column);
|
---|
648 |
|
---|
649 | if (!lp.truncated) {
|
---|
650 | if (lp.address <= addr && lp.address > closest_addr) {
|
---|
651 | closest_addr = lp.address;
|
---|
652 | closest_prog = prog;
|
---|
653 | }
|
---|
654 | }
|
---|
655 |
|
---|
656 | if (!lp.end_sequence) {
|
---|
657 | debug_line_program_skip_to_sequence_end(&lp);
|
---|
658 | assert(lp.truncated || lp.end_sequence);
|
---|
659 | }
|
---|
660 | }
|
---|
661 | }
|
---|
662 |
|
---|
663 | return closest_prog;
|
---|
664 | }
|
---|
665 |
|
---|
666 | static bool get_info(const struct debug_line_program_header *hdr, uintptr_t addr, int op_index, int *file, int *line, int *column)
|
---|
667 | {
|
---|
668 | struct debug_line_program lp = debug_line_program_create(
|
---|
669 | hdr->header_end, hdr->unit_end, hdr);
|
---|
670 |
|
---|
671 | int last_file = 0;
|
---|
672 | int last_line = 0;
|
---|
673 | int last_column = 0;
|
---|
674 |
|
---|
675 | while (lp.program < lp.program_end) {
|
---|
676 | bool first = lp.end_sequence;
|
---|
677 |
|
---|
678 | debug_line_program_advance(&lp);
|
---|
679 |
|
---|
680 | if (lp.truncated)
|
---|
681 | continue;
|
---|
682 |
|
---|
683 | /*
|
---|
684 | * Return previous entry when we pass the target address, because
|
---|
685 | * the address may not be aligned perfectly on instruction boundary.
|
---|
686 | */
|
---|
687 | if (lp.address > addr || (lp.address == addr && lp.op_advance > op_index)) {
|
---|
688 | if (first) {
|
---|
689 | // First address is already too large, skip to the next sequence.
|
---|
690 | debug_line_program_skip_to_sequence_end(&lp);
|
---|
691 | continue;
|
---|
692 | }
|
---|
693 |
|
---|
694 | *file = last_file;
|
---|
695 | *line = last_line;
|
---|
696 | *column = last_column;
|
---|
697 | return true;
|
---|
698 | }
|
---|
699 |
|
---|
700 | last_file = lp.file;
|
---|
701 | last_line = lp.line;
|
---|
702 | last_column = lp.column;
|
---|
703 | }
|
---|
704 |
|
---|
705 | return false;
|
---|
706 | }
|
---|
707 |
|
---|
708 | bool debug_line_get_address_info(debug_sections_t *scs, uintptr_t addr, int op_index, const char **file_name, const char **dir_name, int *line, int *column)
|
---|
709 | {
|
---|
710 | const uint8_t *data = find_line_program(scs, addr);
|
---|
711 | if (data == NULL) {
|
---|
712 | return false;
|
---|
713 | }
|
---|
714 |
|
---|
715 | const uint8_t *const debug_line_end = scs->debug_line + scs->debug_line_size;
|
---|
716 |
|
---|
717 | struct debug_line_program_header hdr = { };
|
---|
718 | debug_line_program_header_parse(scs, data, debug_line_end, &hdr);
|
---|
719 | assert(hdr.unit_end > data);
|
---|
720 | assert(hdr.unit_end <= debug_line_end);
|
---|
721 |
|
---|
722 | int dir = -1;
|
---|
723 | int file = -1;
|
---|
724 |
|
---|
725 | if (!get_info(&hdr, addr, op_index, &file, line, column)) {
|
---|
726 | // printf("no info for 0x%zx: prog offset 0x%zx\n", addr, (void *) data - debug_line);
|
---|
727 | return false;
|
---|
728 | }
|
---|
729 |
|
---|
730 | // printf("got info for 0x%zx: file %d, line %d, column %d\n", addr, file, *line, *column);
|
---|
731 |
|
---|
732 | if (file >= 0)
|
---|
733 | *file_name = get_file_name(scs, &hdr, file, &dir);
|
---|
734 |
|
---|
735 | if (dir >= 0)
|
---|
736 | *dir_name = get_dir_name(scs, &hdr, dir);
|
---|
737 |
|
---|
738 | return true;
|
---|
739 | }
|
---|