1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/debug/dwarf_line_no.h"
6
7 #include "partition_alloc/pointers/raw_ref.h"
8
9 #ifdef USE_SYMBOLIZE
10 #include <algorithm>
11 #include <charconv>
12 #include <cstdint>
13 #include <limits>
14
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include "base/debug/buffered_dwarf_reader.h"
20 #include "base/third_party/symbolize/symbolize.h"
21 #include "partition_alloc/pointers/raw_ptr.h"
22
23 namespace base {
24 namespace debug {
25
26 namespace {
27
28 constexpr uint64_t kMaxOffset = std::numeric_limits<uint64_t>::max();
29
30 // These numbers are suitable for most compilation units for chrome and
31 // content_shell. If a compilation unit has bigger number of directories or
32 // filenames, the additional directories/filenames will be ignored, and the
33 // stack frames pointing to these directories/filenames will not get line
34 // numbers. We can't set these numbers too big because they affect the size of
35 // ProgramInfo which is allocated in the stack.
36 constexpr int kMaxDirectories = 128;
37 constexpr size_t kMaxFilenames = 512;
38
39 // DWARF-4 line number program header, section 6.2.4
40 struct ProgramInfo {
41 uint64_t header_length;
42 uint64_t start_offset;
43 uint64_t end_offset;
44 uint8_t minimum_instruction_length;
45 uint8_t maximum_operations_per_instruction;
46 uint8_t default_is_stmt;
47 int8_t line_base;
48 uint8_t line_range;
49 uint8_t opcode_base;
50 uint8_t standard_opcode_lengths[256];
51 uint8_t include_directories_table_offset;
52 uint8_t file_names_table_offset;
53
54 // Store the directories as offsets.
55 int num_directories = 1;
56 uint64_t directory_offsets[kMaxDirectories];
57 uint64_t directory_sizes[kMaxDirectories];
58
59 // Store the file number table offsets.
60 mutable unsigned int num_filenames = 1;
61 mutable uint64_t filename_offsets[kMaxFilenames];
62 mutable uint8_t filename_dirs[kMaxFilenames];
63
OpcodeToAdvancebase::debug::__anon7dac112c0111::ProgramInfo64 unsigned int OpcodeToAdvance(uint8_t adjusted_opcode) const {
65 // Special opcodes advance line numbers by an amount based on line_range
66 // and opcode_base. This calculation is taken from 6.2.5.1.
67 return static_cast<unsigned int>(adjusted_opcode) / line_range;
68 }
69 };
70
71 // DWARF-4 line number program registers, section 6.2.2
72 struct LineNumberRegisters {
73 // During the line number program evaluation, some instructions perform a
74 // "commit" which is when the registers have finished calculating a new row in
75 // the line-number table. This callback is executed and can be viewed as a
76 // iterator over all rows in the line number table.
77 class OnCommit {
78 public:
79 virtual void Do(LineNumberRegisters* registers) = 0;
80 };
81
82 raw_ptr<OnCommit> on_commit;
LineNumberRegistersbase::debug::__anon7dac112c0111::LineNumberRegisters83 LineNumberRegisters(ProgramInfo info, OnCommit* on_commit)
84 : on_commit(on_commit), is_stmt(info.default_is_stmt) {}
85
86 // Current program counter.
87 uintptr_t address = 0;
88
89 // For VLIW architectures, the index of the operation in the VLIW instruction.
90 unsigned int op_index = 0;
91
92 // Identifies the source file relating to the address in the DWARF File name
93 // table.
94 uint64_t file = 0;
95
96 // Identifies the line number. Starts at 1. Can become 0 if instruction does
97 // not match any line in the file.
98 uint64_t line = 1;
99
100 // Identifies the column within the source line. Starts at 1 though "0"
101 // also means "left edge" of the line.
102 uint64_t column = 0;
103
104 // Boolean determining if this is a recommended spot for a breakpoint.
105 // Should be initialized by the program header.
106 bool is_stmt = false;
107
108 // Indicates start of a basic block.
109 bool basic_block = false;
110
111 // Indicates first byte after a sequence of machine instructions.
112 bool end_sequence = false;
113
114 // Indicates this may be where execution should stop if trying to break for
115 // entering a function.
116 bool prologue_end = false;
117
118 // Indicates this may be where execution should stop if trying to break for
119 // exiting a function.
120 bool epilogue_begin = false;
121
122 // Identifier for the instruction set of the current address.
123 uint64_t isa = 0;
124
125 // Identifies which block the current instruction belongs to.
126 uint64_t discriminator = 0;
127
128 // Values from the previously committed line. See OnCommit interface for more
129 // details. This conceptually should be a copy of the whole
130 // LineNumberRegisters but since only 4 pieces of data are needed, hacking
131 // it inline was easier.
132 uintptr_t last_address = 0;
133 uint64_t last_file = 0;
134 uint64_t last_line = 0;
135 uint64_t last_column = 0;
136
137 // This is the magical calculation for decompressing the line-number
138 // information. The `program_info` provides the parameters for the formula
139 // and the `op_advance` is the input value. See DWARF-4 sections 6.2.5.1 for
140 // the formula.
OpAdvancebase::debug::__anon7dac112c0111::LineNumberRegisters141 void OpAdvance(const ProgramInfo* program_info, uint64_t op_advance) {
142 address += program_info->minimum_instruction_length *
143 ((op_index + op_advance) /
144 program_info->maximum_operations_per_instruction);
145
146 op_index = (op_index + op_advance) %
147 program_info->maximum_operations_per_instruction;
148 }
149
150 // Committing a line means the calculation has landed on a stable set of
151 // values that represent an actual entry in the line number table.
CommitLinebase::debug::__anon7dac112c0111::LineNumberRegisters152 void CommitLine() {
153 on_commit->Do(this);
154
155 // Inlined or compiler generator code may have line number 0 which isn't
156 // useful to the user. Better to go up one line number.
157 if (line != 0) {
158 last_address = address;
159 last_file = file;
160 last_column = column;
161 last_line = line;
162 }
163 }
164 };
165
166 struct LineNumberInfo {
167 uint64_t pc = 0;
168 uint64_t line = 0;
169 uint64_t column = 0;
170
171 // Offsets here are to the file table and directory table arrays inside the
172 // ProgramInfo.
173 uint64_t module_dir_offset = 0;
174 uint64_t dir_size = 0;
175 uint64_t module_filename_offset = 0;
176 };
177
178 // Evaluates a Line Number Program as defined by the rules in section 6.2.5.
EvaluateLineNumberProgram(const int fd,LineNumberInfo * info,uint64_t base_address,uint64_t start,const ProgramInfo & program_info)179 void EvaluateLineNumberProgram(const int fd,
180 LineNumberInfo* info,
181 uint64_t base_address,
182 uint64_t start,
183 const ProgramInfo& program_info) {
184 BufferedDwarfReader reader(fd, start);
185 uint64_t module_relative_pc = info->pc - base_address;
186
187 // Helper that records the line-number table entry corresponding with the
188 // `module_relative_pc`. This is the thing that actually finds the line
189 // number for an address.
190 struct OnCommitImpl : public LineNumberRegisters::OnCommit {
191 private:
192 raw_ptr<LineNumberInfo> info;
193 uint64_t module_relative_pc;
194 const raw_ref<const ProgramInfo> program_info;
195
196 public:
197 OnCommitImpl(LineNumberInfo* info,
198 uint64_t module_relative_pc,
199 const ProgramInfo& program_info)
200 : info(info),
201 module_relative_pc(module_relative_pc),
202 program_info(program_info) {}
203
204 void Do(LineNumberRegisters* registers) override {
205 // When a line is committed, the program counter needs to check if it is
206 // in the [last_address, cur_addres) range. If yes, then the line pertains
207 // to the program counter.
208 if (registers->last_address == 0) {
209 // This is the first table entry so by definition, nothing is in its
210 // range.
211 return;
212 }
213
214 // If module_relative_pc is out of range, skip.
215 if (module_relative_pc < registers->last_address ||
216 module_relative_pc >= registers->address)
217 return;
218
219 if (registers->last_file < program_info->num_filenames) {
220 info->line = registers->last_line;
221 info->column = registers->last_column;
222
223 // Since DW_AT_name in the compile_unit is optional, it may be empty. If
224 // it is, guess that the file in entry 1 is the name. This does not
225 // follow spec, but seems to be common behavior. See the following LLVM
226 // bug for more info: https://reviews.llvm.org/D11003
227 if (registers->last_file == 0 &&
228 program_info->filename_offsets[0] == 0 &&
229 1 < program_info->num_filenames) {
230 program_info->filename_offsets[0] = program_info->filename_offsets[1];
231 program_info->filename_dirs[0] = program_info->filename_dirs[1];
232 }
233
234 if (registers->last_file < kMaxFilenames) {
235 info->module_filename_offset =
236 program_info->filename_offsets[registers->last_file];
237
238 uint8_t dir = program_info->filename_dirs[registers->last_file];
239 info->module_dir_offset = program_info->directory_offsets[dir];
240 info->dir_size = program_info->directory_sizes[dir];
241 }
242 }
243 }
244 } on_commit(info, module_relative_pc, program_info);
245
246 LineNumberRegisters registers(program_info, &on_commit);
247
248 // Special opcode range is [program_info.opcode_base, 255].
249 // Lines can be max incremented by [line_base + line range - 1].
250 // opcode = (desired line increment - line_base) + (line_range * operation
251 // advance) + opcode_base.
252 uint8_t opcode;
253 while (reader.position() < program_info.end_offset && info->line == 0) {
254 if (!reader.ReadInt8(opcode))
255 return;
256
257 // It's SPECIAL OPCODE TIME!. They're so special that they make up the
258 // vast majority of the opcodes and are the first thing described in the
259 // documentation.
260 //
261 // See DWARF-4 spec 6.2.5.1.
262 if (opcode >= program_info.opcode_base) {
263 uint8_t adjusted_opcode = opcode - program_info.opcode_base;
264 registers.OpAdvance(&program_info,
265 program_info.OpcodeToAdvance(adjusted_opcode));
266 const int line_adjust =
267 program_info.line_base + (adjusted_opcode % program_info.line_range);
268 if (line_adjust < 0) {
269 if (static_cast<uint64_t>(-line_adjust) > registers.line)
270 return;
271 registers.line -= static_cast<uint64_t>(-line_adjust);
272 } else {
273 registers.line += static_cast<uint64_t>(line_adjust);
274 }
275 registers.basic_block = false;
276 registers.prologue_end = false;
277 registers.epilogue_begin = false;
278 registers.discriminator = 0;
279 registers.CommitLine();
280 } else {
281 // Standard opcodes
282 switch (opcode) {
283 case 0: {
284 // Extended opcode.
285 uint64_t extended_opcode;
286 uint64_t extended_opcode_length;
287 if (!reader.ReadLeb128(extended_opcode_length))
288 return;
289 uint64_t next_opcode = reader.position() + extended_opcode_length;
290 if (!reader.ReadLeb128(extended_opcode))
291 return;
292 switch (extended_opcode) {
293 case 1: {
294 // DW_LNE_end_sequence
295 registers.end_sequence = true;
296 registers.CommitLine();
297 registers = LineNumberRegisters(program_info, &on_commit);
298 break;
299 }
300
301 case 2: {
302 // DW_LNE_set_address
303 uint32_t value;
304 if (!reader.ReadInt32(value))
305 return;
306 registers.address = value;
307 registers.op_index = 0;
308 break;
309 }
310
311 case 3: {
312 // DW_LNE_define_file
313 //
314 // This should only get used if the filename table itself is null.
315 // Record the module offset for the string and then drop the data.
316 uint64_t filename_offset = reader.position();
317 reader.ReadCString(program_info.end_offset, nullptr, 0);
318
319 // dir index
320 uint64_t value;
321 if (!reader.ReadLeb128(value))
322 return;
323 size_t cur_filename = program_info.num_filenames;
324 if (cur_filename < kMaxFilenames && value < kMaxDirectories) {
325 ++program_info.num_filenames;
326 // Store the offset from the start of file and skip the data to
327 // save memory.
328 program_info.filename_offsets[cur_filename] = filename_offset;
329 program_info.filename_dirs[cur_filename] =
330 static_cast<uint8_t>(value);
331 }
332
333 // modification time
334 if (!reader.ReadLeb128(value))
335 return;
336
337 // source file length
338 if (!reader.ReadLeb128(value))
339 return;
340 break;
341 }
342
343 case 4: {
344 // DW_LNE_set_discriminator
345 uint64_t value;
346 if (!reader.ReadLeb128(value))
347 return;
348 registers.discriminator = value;
349 break;
350 }
351
352 default:
353 abort();
354 }
355
356 // Skip any padding bytes in extended opcode.
357 reader.set_position(next_opcode);
358 break;
359 }
360
361 case 1: {
362 // DW_LNS_copy. This commits the registers to the line number table.
363 registers.CommitLine();
364 registers.discriminator = 0;
365 registers.basic_block = false;
366 registers.prologue_end = false;
367 registers.epilogue_begin = false;
368 break;
369 }
370
371 case 2: {
372 // DW_LNS_advance_pc
373 uint64_t op_advance;
374 if (!reader.ReadLeb128(op_advance))
375 return;
376 registers.OpAdvance(&program_info, op_advance);
377 break;
378 }
379
380 case 3: {
381 // DW_LNS_advance_line
382 int64_t line_advance;
383 if (!reader.ReadLeb128(line_advance))
384 return;
385 if (line_advance < 0) {
386 if (static_cast<uint64_t>(-line_advance) > registers.line)
387 return;
388 registers.line -= static_cast<uint64_t>(-line_advance);
389 } else {
390 registers.line += static_cast<uint64_t>(line_advance);
391 }
392 break;
393 }
394
395 case 4: {
396 // DW_LNS_set_file
397 uint64_t value;
398 if (!reader.ReadLeb128(value))
399 return;
400 registers.file = value;
401 break;
402 }
403
404 case 5: {
405 // DW_LNS_set_column
406 uint64_t value;
407 if (!reader.ReadLeb128(value))
408 return;
409 registers.column = value;
410 break;
411 }
412
413 case 6:
414 // DW_LNS_negate_stmt
415 registers.is_stmt = !registers.is_stmt;
416 break;
417
418 case 7:
419 // DW_LNS_set_basic_block
420 registers.basic_block = true;
421 break;
422
423 case 8:
424 // DW_LNS_const_add_pc
425 registers.OpAdvance(
426 &program_info,
427 program_info.OpcodeToAdvance(255 - program_info.opcode_base));
428 break;
429
430 case 9: {
431 // DW_LNS_fixed_advance_pc
432 uint16_t value;
433 if (!reader.ReadInt16(value))
434 return;
435 registers.address += value;
436 registers.op_index = 0;
437 break;
438 }
439
440 case 10:
441 // DW_LNS_set_prologue_end
442 registers.prologue_end = true;
443 break;
444
445 case 11:
446 // DW_LNS_set_epilogue_begin
447 registers.epilogue_begin = true;
448 break;
449
450 case 12: {
451 // DW_LNS_set_isa
452 uint64_t value;
453 if (!reader.ReadLeb128(value))
454 return;
455 registers.isa = value;
456 break;
457 }
458
459 default:
460 abort();
461 }
462 }
463 }
464 }
465
466 // Parses a 32-bit DWARF-4 line number program header per section 6.2.4.
467 // `cu_name_offset` is the module offset for the 0th entry of the file table.
ParseDwarf4ProgramInfo(BufferedDwarfReader * reader,bool is_64bit,uint64_t cu_name_offset,ProgramInfo * program_info)468 bool ParseDwarf4ProgramInfo(BufferedDwarfReader* reader,
469 bool is_64bit,
470 uint64_t cu_name_offset,
471 ProgramInfo* program_info) {
472 if (!reader->ReadOffset(is_64bit, program_info->header_length))
473 return false;
474 program_info->start_offset = reader->position() + program_info->header_length;
475
476 if (!reader->ReadInt8(program_info->minimum_instruction_length) ||
477 !reader->ReadInt8(program_info->maximum_operations_per_instruction) ||
478 !reader->ReadInt8(program_info->default_is_stmt) ||
479 !reader->ReadInt8(program_info->line_base) ||
480 !reader->ReadInt8(program_info->line_range) ||
481 !reader->ReadInt8(program_info->opcode_base)) {
482 return false;
483 }
484
485 for (int i = 0; i < (program_info->opcode_base - 1); i++) {
486 if (!reader->ReadInt8(program_info->standard_opcode_lengths[i]))
487 return false;
488 }
489
490 // Table ends with a single null line. This basically means search for 2
491 // contiguous empty bytes.
492 uint8_t last = 0, cur = 0;
493 for (;;) {
494 // Read a byte.
495 last = cur;
496 if (!reader->ReadInt8(cur))
497 return false;
498
499 if (last == 0 && cur == 0) {
500 // We're at the last entry where it's a double null.
501 break;
502 }
503
504 // Read in all of the filename.
505 int cur_dir = program_info->num_directories;
506 if (cur_dir < kMaxDirectories) {
507 ++program_info->num_directories;
508 // "-1" is because we have already read the first byte above.
509 program_info->directory_offsets[cur_dir] = reader->position() - 1;
510 program_info->directory_sizes[cur_dir] = 1;
511 }
512 do {
513 if (!reader->ReadInt8(cur))
514 return false;
515 if (cur_dir < kMaxDirectories)
516 ++program_info->directory_sizes[cur_dir];
517 } while (cur != '\0');
518 }
519
520 // Read filename table line-by-line.
521 last = 0;
522 cur = 0;
523 for (;;) {
524 // Read a byte.
525 last = cur;
526 if (!reader->ReadInt8(cur))
527 return false;
528
529 if (last == 0 && cur == 0) {
530 // We're at the last entry where it's a double null.
531 break;
532 }
533
534 // Read in all of the filename. "-1" is because we have already read the
535 // first byte of the filename above.
536 uint64_t filename_offset = reader->position() - 1;
537 do {
538 if (!reader->ReadInt8(cur))
539 return false;
540 } while (cur != '\0');
541
542 uint64_t value;
543
544 // Dir index
545 if (!reader->ReadLeb128(value))
546 return false;
547 size_t cur_filename = program_info->num_filenames;
548 if (cur_filename < kMaxFilenames && value < kMaxDirectories) {
549 ++program_info->num_filenames;
550 program_info->filename_offsets[cur_filename] = filename_offset;
551 program_info->filename_dirs[cur_filename] = static_cast<uint8_t>(value);
552 }
553
554 // Modification time
555 if (!reader->ReadLeb128(value))
556 return false;
557
558 // Bytes in file.
559 if (!reader->ReadLeb128(value))
560 return false;
561 }
562
563 // Set up the 0th filename.
564 program_info->filename_offsets[0] = cu_name_offset;
565 program_info->filename_dirs[0] = 0;
566 program_info->directory_offsets[0] = 0;
567
568 return true;
569 }
570
571 // Returns the offset of the next byte to read.
572 // `program_info.program_end` is guaranteed to be initlialized to either
573 // `kMaxOffset` if the program length could not be processed, or to
574 // the byte after the end of this program.
ReadProgramInfo(const int fd,uint64_t start,uint64_t cu_name_offset,ProgramInfo * program_info)575 bool ReadProgramInfo(const int fd,
576 uint64_t start,
577 uint64_t cu_name_offset,
578 ProgramInfo* program_info) {
579 BufferedDwarfReader reader(fd, start);
580 program_info->end_offset = kMaxOffset;
581
582 // Note that 64-bit dwarf does NOT imply a 64-bit binary and vice-versa. In
583 // fact many 64-bit binaries use 32-bit dwarf encoding.
584 bool is_64bit = false;
585 uint64_t data_length;
586 if (!reader.ReadInitialLength(is_64bit, data_length)) {
587 return false;
588 }
589
590 // Set the program end. This allows the search to recover by skipping an
591 // unparsable program.
592 program_info->end_offset = reader.position() + data_length;
593
594 uint16_t version;
595 if (!reader.ReadInt16(version)) {
596 return false;
597 }
598
599 if (version == 4) {
600 return ParseDwarf4ProgramInfo(&reader, is_64bit, cu_name_offset,
601 program_info);
602 }
603
604 // Currently does not support other DWARF versions.
605 return false;
606 }
607
608 // Attempts to find line-number info for all of |info|. Returns the number of
609 // entries that do not have info yet.
GetLineNumbersInProgram(const int fd,LineNumberInfo * info,uint64_t base_address,uint64_t start,uint64_t cu_name_offset)610 uint64_t GetLineNumbersInProgram(const int fd,
611 LineNumberInfo* info,
612 uint64_t base_address,
613 uint64_t start,
614 uint64_t cu_name_offset) {
615 // Open the program.
616 ProgramInfo program_info;
617 if (ReadProgramInfo(fd, start, cu_name_offset, &program_info)) {
618 EvaluateLineNumberProgram(fd, info, base_address, program_info.start_offset,
619 program_info);
620 }
621
622 return program_info.end_offset;
623 }
624
625 // Scans the .debug_abbrev entry until it finds the Attribute List matching the
626 // `wanted_abbreviation_code`. This is called when parsing a DIE in .debug_info.
AdvancedReaderToAttributeList(BufferedDwarfReader & reader,uint64_t table_end,uint64_t wanted_abbreviation_code,uint64_t & tag,bool & has_children)627 bool AdvancedReaderToAttributeList(BufferedDwarfReader& reader,
628 uint64_t table_end,
629 uint64_t wanted_abbreviation_code,
630 uint64_t& tag,
631 bool& has_children) {
632 // Abbreviation Table entries are:
633 // LEB128 - abbreviation code
634 // LEB128 - the entry's tag
635 // 1 byte - DW_CHILDREN_yes or DW_CHILDREN_no for if entry has children.
636 // [LEB128, LEB128] -- repeated set of attribute + form values in LEB128
637 // [0, 0] -- null entry terminating list is 2 LEB128 0s.
638 while (reader.position() < table_end) {
639 uint64_t abbreviation_code;
640 if (!reader.ReadLeb128(abbreviation_code)) {
641 return false;
642 }
643
644 if (!reader.ReadLeb128(tag)) {
645 return false;
646 }
647
648 uint8_t raw_has_children;
649 if (!reader.ReadInt8(raw_has_children)) {
650 return false;
651 }
652 if (raw_has_children == 0) {
653 has_children = false;
654 } else if (raw_has_children == 1) {
655 has_children = true;
656 } else {
657 return false;
658 }
659
660 if (abbreviation_code == wanted_abbreviation_code) {
661 return true;
662 }
663
664 // Incorrect Abbreviation entry. Skip all of its attributes.
665 uint64_t attr;
666 uint64_t form;
667 do {
668 if (!reader.ReadLeb128(attr) || !reader.ReadLeb128(form)) {
669 return false;
670 }
671 } while (attr != 0 || form != 0);
672 }
673
674 return false;
675 }
676
677 // This reads through a .debug_info compile unit entry to try and extract
678 // the `cu_name_offset` as well as the `debug_line_offset` (offset into the
679 // .debug_lines table` corresponding to `pc`.
680 //
681 // The .debug_info sections are a packed set of bytes whose format is defined
682 // by a corresponding .debug_abbrev entry. Basically .debug_abbrev describes
683 // a struct and .debug_info has a header that tells which struct it is followed
684 // by a bunch of bytes.
685 //
686 // The control flow is to find the .debug_abbrev entry for each .debug_info
687 // entry, then walk through the .debug_abbrev entry to parse the bytes of the
688 // .debug_info entry. A successful parse calculates the address range that the
689 // .debug_info entry covers. When that is retrieved, `pc` can be compared to
690 // the range and a corresponding .debug_info can be found.
691 //
692 // The `debug_info_start` be the start of the whole .debug_info section or an
693 // offset into the section if it was known ahead of time (perhaps by consulting
694 // .debug_aranges).
695 //
696 // To fully interpret this data, the .debug_ranges and .debug_str sections
697 // also need to be interpreted.
GetCompileUnitName(int fd,uint64_t debug_info_start,uint64_t debug_info_end,uint64_t pc,uint64_t module_base_address,uint64_t * debug_line_offset,uint64_t * cu_name_offset)698 bool GetCompileUnitName(int fd,
699 uint64_t debug_info_start,
700 uint64_t debug_info_end,
701 uint64_t pc,
702 uint64_t module_base_address,
703 uint64_t* debug_line_offset,
704 uint64_t* cu_name_offset) {
705 // Ensure defined `cu_name_offset` in case DW_AT_name is missing.
706 *cu_name_offset = 0;
707
708 // Open .debug_info and .debug_abbrev as both are needed to find the
709 // DW_AT_name for the DW_TAG_compile_unit or DW_TAG_partial_unit
710 // corresponding to the given address.
711
712 ElfW(Shdr) debug_abbrev;
713 constexpr static char kDebugAbbrevSectionName[] = ".debug_abbrev";
714 if (!google::GetSectionHeaderByName(fd, kDebugAbbrevSectionName,
715 sizeof(kDebugAbbrevSectionName),
716 &debug_abbrev)) {
717 return false;
718 }
719 uint64_t debug_abbrev_end = debug_abbrev.sh_offset + debug_abbrev.sh_size;
720
721 ElfW(Shdr) debug_str;
722 constexpr static char kDebugStrSectionName[] = ".debug_str";
723 if (!google::GetSectionHeaderByName(
724 fd, kDebugStrSectionName, sizeof(kDebugStrSectionName), &debug_str)) {
725 return false;
726 }
727 uint64_t debug_str_end = debug_str.sh_offset + debug_str.sh_size;
728
729 ElfW(Shdr) debug_ranges;
730 constexpr static char kDebugRangesSectionName[] = ".debug_ranges";
731 if (!google::GetSectionHeaderByName(fd, kDebugRangesSectionName,
732 sizeof(kDebugRangesSectionName),
733 &debug_ranges)) {
734 return false;
735 }
736 uint64_t debug_ranges_end = debug_ranges.sh_offset + debug_ranges.sh_size;
737
738 // Iterate Compile Units.
739 uint64_t next_compilation_unit = kMaxOffset;
740 for (BufferedDwarfReader reader(fd, debug_info_start);
741 reader.position() < debug_info_end;
742 reader.set_position(next_compilation_unit)) {
743 bool is_64bit;
744 uint64_t length;
745 uint16_t dwarf_version;
746 uint64_t abbrev_offset;
747 uint8_t address_size;
748 if (!reader.ReadCommonHeader(is_64bit, length, dwarf_version, abbrev_offset,
749 address_size, next_compilation_unit)) {
750 return false;
751 }
752
753 // Compilation Unit Header parsed. Now read the first tag which is either a
754 // DW_TAG_compile_unit or DW_TAG_partial_unit. The entry type is designated
755 // by a LEB128 number that needs to be cross-referenced in the abbreviations
756 // table to understand the format of the rest of the entry.
757 uint64_t abbreviation_code;
758 if (!reader.ReadLeb128(abbreviation_code)) {
759 return false;
760 }
761
762 // Find entry in the abbreviation table.
763 BufferedDwarfReader abbrev_reader(fd,
764 debug_abbrev.sh_offset + abbrev_offset);
765 uint64_t tag;
766 bool has_children;
767 AdvancedReaderToAttributeList(abbrev_reader, debug_abbrev_end,
768 abbreviation_code, tag, has_children);
769
770 // Ignore if it has children.
771 static constexpr int kDW_TAG_compile_unit = 0x11;
772 static constexpr int kDW_TAG_partial_unit = 0x3c;
773 if (tag != kDW_TAG_compile_unit && tag != kDW_TAG_partial_unit) {
774 return false;
775 }
776
777 // Use table to parse the name, high, and low attributes.
778 static constexpr int kDW_AT_name = 0x3; // string
779 static constexpr int kDW_AT_stmt_list = 0x10; // lineptr
780 static constexpr int kDW_AT_low_pc = 0x11; // address
781 static constexpr int kDW_AT_high_pc = 0x12; // address, constant
782 static constexpr int kDW_AT_ranges = 0x55; // rangelistptr
783 uint64_t attr;
784 uint64_t form;
785 uint64_t low_pc = 0;
786 uint64_t high_pc = 0;
787 bool high_pc_is_offset = false;
788 bool is_found_in_range = false;
789 do {
790 if (!abbrev_reader.ReadLeb128(attr)) {
791 return false;
792 }
793 if (!abbrev_reader.ReadLeb128(form)) {
794 return false;
795 }
796 // Table from 7.5.4, Figure 20.
797 enum Form {
798 kDW_FORM_addr = 0x01,
799 kDW_FORM_block2 = 0x03,
800 kDW_FORM_block4 = 0x04,
801 kDW_FORM_data2 = 0x05,
802 kDW_FORM_data4 = 0x06,
803 kDW_FORM_data8 = 0x07,
804 kDW_FORM_string = 0x08,
805 kDW_FORM_block = 0x09,
806 kDW_FORM_block1 = 0x0a,
807 kDW_FORM_data1 = 0x0b,
808 kDW_FORM_flag = 0x0c,
809 kDW_FORM_sdata = 0x0d,
810 kDW_FORM_strp = 0x0e,
811 kDW_FORM_udata = 0x0f,
812 kDW_FORM_ref_addr = 0x10,
813 kDW_FORM_ref1 = 0x11,
814 kDW_FORM_ref2 = 0x12,
815 kDW_FORM_ref4 = 0x13,
816 kDW_FORM_ref8 = 0x14,
817 kDW_FORM_ref_udata = 0x15,
818 kDW_FORM_ref_indrect = 0x16,
819 kDW_FORM_sec_offset = 0x17,
820 kDW_FORM_exprloc = 0x18,
821 kDW_FORM_flag_present = 0x19,
822 kDW_FORM_ref_sig8 = 0x20,
823 };
824
825 switch (form) {
826 case kDW_FORM_string: {
827 // Read the value into if necessary `out`
828 if (attr == kDW_AT_name) {
829 *cu_name_offset = reader.position();
830 }
831 if (!reader.ReadCString(debug_info_end, nullptr, 0)) {
832 return false;
833 }
834 } break;
835
836 case kDW_FORM_strp: {
837 uint64_t strp_offset;
838 if (!reader.ReadOffset(is_64bit, strp_offset)) {
839 return false;
840 }
841
842 if (attr == kDW_AT_name) {
843 uint64_t pos = debug_str.sh_offset + strp_offset;
844 if (pos >= debug_str_end) {
845 return false;
846 }
847 *cu_name_offset = pos;
848 }
849 } break;
850
851 case kDW_FORM_addr: {
852 uint64_t address;
853 if (!reader.ReadAddress(address_size, address)) {
854 return false;
855 }
856
857 if (attr == kDW_AT_low_pc) {
858 low_pc = address;
859 } else if (attr == kDW_AT_high_pc) {
860 high_pc_is_offset = false;
861 high_pc = address;
862 }
863 } break;
864
865 case kDW_FORM_data1: {
866 uint8_t data;
867 if (!reader.ReadInt8(data)) {
868 return false;
869 }
870 if (attr == kDW_AT_high_pc) {
871 high_pc_is_offset = true;
872 high_pc = data;
873 }
874 } break;
875
876 case kDW_FORM_data2: {
877 uint16_t data;
878 if (!reader.ReadInt16(data)) {
879 return false;
880 }
881 if (attr == kDW_AT_high_pc) {
882 high_pc_is_offset = true;
883 high_pc = data;
884 }
885 } break;
886
887 case kDW_FORM_data4: {
888 uint32_t data;
889 if (!reader.ReadInt32(data)) {
890 return false;
891 }
892 if (attr == kDW_AT_high_pc) {
893 high_pc_is_offset = true;
894 high_pc = data;
895 }
896 } break;
897
898 case kDW_FORM_data8: {
899 uint64_t data;
900 if (!reader.ReadInt64(data)) {
901 return false;
902 }
903 if (attr == kDW_AT_high_pc) {
904 high_pc_is_offset = true;
905 high_pc = data;
906 }
907 } break;
908
909 case kDW_FORM_sdata: {
910 int64_t data;
911 if (!reader.ReadLeb128(data)) {
912 return false;
913 }
914 if (attr == kDW_AT_high_pc) {
915 high_pc_is_offset = true;
916 high_pc = static_cast<uint64_t>(data);
917 }
918 } break;
919
920 case kDW_FORM_udata: {
921 uint64_t data;
922 if (!reader.ReadLeb128(data)) {
923 return false;
924 }
925 if (attr == kDW_AT_high_pc) {
926 high_pc_is_offset = true;
927 high_pc = data;
928 }
929 } break;
930
931 case kDW_FORM_ref_addr:
932 case kDW_FORM_sec_offset: {
933 uint64_t value;
934 if (!reader.ReadOffset(is_64bit, value)) {
935 return false;
936 }
937
938 if (attr == kDW_AT_ranges) {
939 uint64_t current_base_address = module_base_address;
940 BufferedDwarfReader ranges_reader(fd,
941 debug_ranges.sh_offset + value);
942
943 while (ranges_reader.position() < debug_ranges_end) {
944 // Ranges are 2 addresses in size.
945 uint64_t range_start;
946 uint64_t range_end;
947 if (!ranges_reader.ReadAddress(address_size, range_start)) {
948 return false;
949 }
950 if (!ranges_reader.ReadAddress(address_size, range_end)) {
951 return false;
952 }
953 uint64_t relative_pc = pc - current_base_address;
954
955 if (range_start == 0 && range_end == 0) {
956 if (!is_found_in_range) {
957 // Time to go to the next iteration.
958 goto next_cu;
959 }
960 break;
961 } else if (((address_size == 4) &&
962 (range_start == 0xffffffffUL)) ||
963 ((address_size == 8) &&
964 (range_start == 0xffffffffffffffffULL))) {
965 // Check if this is a new base add value. 2.17.3
966 current_base_address = range_end;
967 } else {
968 if (relative_pc >= range_start && relative_pc < range_end) {
969 is_found_in_range = true;
970 break;
971 }
972 }
973 }
974 } else if (attr == kDW_AT_stmt_list) {
975 *debug_line_offset = value;
976 }
977 } break;
978
979 case kDW_FORM_flag:
980 case kDW_FORM_ref1:
981 case kDW_FORM_block1: {
982 uint8_t dummy;
983 if (!reader.ReadInt8(dummy)) {
984 return false;
985 }
986 } break;
987
988 case kDW_FORM_ref2:
989 case kDW_FORM_block2: {
990 uint16_t dummy;
991 if (!reader.ReadInt16(dummy)) {
992 return false;
993 }
994 } break;
995
996 case kDW_FORM_ref4:
997 case kDW_FORM_block4: {
998 uint32_t dummy;
999 if (!reader.ReadInt32(dummy)) {
1000 return false;
1001 }
1002 } break;
1003
1004 case kDW_FORM_ref8: {
1005 uint64_t dummy;
1006 if (!reader.ReadInt64(dummy)) {
1007 return false;
1008 }
1009 } break;
1010
1011 case kDW_FORM_ref_udata:
1012 case kDW_FORM_block: {
1013 uint64_t dummy;
1014 if (!reader.ReadLeb128(dummy)) {
1015 return false;
1016 }
1017 } break;
1018
1019 case kDW_FORM_exprloc: {
1020 uint64_t value;
1021 if (!reader.ReadLeb128(value)) {
1022 return false;
1023 }
1024 reader.set_position(reader.position() + value);
1025 } break;
1026 }
1027 } while (attr != 0 || form != 0);
1028
1029 // Because attributes can be in any order, most of the computations (minus
1030 // checking range list entries) cannot happen until everything is parsed for
1031 // the one .debug_info entry. Do the analysis here.
1032 if (is_found_in_range) {
1033 // Well formed compile_unit and partial_unit tags either have a
1034 // DT_AT_ranges entry or an DT_AT_low_pc entiry. If is_found_in_range
1035 // matched as true, then this entry matches the given pc.
1036 return true;
1037 }
1038
1039 // If high_pc_is_offset is 0, it was never found in the DIE. This indicates
1040 // a single address entry. Only look at the low_pc.
1041 {
1042 uint64_t module_relative_pc = pc - module_base_address;
1043 if (high_pc == 0 && module_relative_pc != low_pc) {
1044 goto next_cu;
1045 }
1046
1047 // Otherwise this is a contiguous range DIE. Normalize the meaning of the
1048 // high_pc field and check if it contains the pc.
1049 if (high_pc_is_offset) {
1050 high_pc = low_pc + high_pc;
1051 high_pc_is_offset = false;
1052 }
1053
1054 if (module_relative_pc >= low_pc && module_relative_pc < high_pc) {
1055 return true;
1056 }
1057 }
1058
1059 // Not found.
1060 next_cu:;
1061 }
1062 return false;
1063 }
1064
1065 // Thin wrapper over `GetCompileUnitName` that opens the .debug_info section.
ReadCompileUnit(int fd,uint64_t pc,uint64_t cu_offset,uint64_t base_address,uint64_t * debug_line_offset,uint64_t * cu_name_offset)1066 bool ReadCompileUnit(int fd,
1067 uint64_t pc,
1068 uint64_t cu_offset,
1069 uint64_t base_address,
1070 uint64_t* debug_line_offset,
1071 uint64_t* cu_name_offset) {
1072 if (cu_offset == 0) {
1073 return false;
1074 }
1075
1076 ElfW(Shdr) debug_info;
1077 constexpr static char kDebugInfoSectionName[] = ".debug_info";
1078 if (!google::GetSectionHeaderByName(fd, kDebugInfoSectionName,
1079 sizeof(kDebugInfoSectionName),
1080 &debug_info)) {
1081 return false;
1082 }
1083 uint64_t debug_info_end = debug_info.sh_offset + debug_info.sh_size;
1084
1085 return GetCompileUnitName(fd, debug_info.sh_offset + cu_offset,
1086 debug_info_end, pc, base_address, debug_line_offset,
1087 cu_name_offset);
1088 }
1089
1090 // Takes the information from `info` and renders the data located in the
1091 // object file `fd` into `out`. The format looks like:
1092 //
1093 // [../path/to/foo.cc:10:40]
1094 //
1095 // which would indicate line 10 column 40 in ../path/to/foo.cc
SerializeLineNumberInfoToString(int fd,const LineNumberInfo & info,char * out,size_t out_size)1096 void SerializeLineNumberInfoToString(int fd,
1097 const LineNumberInfo& info,
1098 char* out,
1099 size_t out_size) {
1100 size_t out_pos = 0;
1101 if (info.module_filename_offset) {
1102 BufferedDwarfReader reader(fd, info.module_dir_offset);
1103 if (info.module_dir_offset != 0) {
1104 out_pos +=
1105 reader.ReadCString(kMaxOffset, out + out_pos, out_size - out_pos);
1106 out[out_pos - 1] = '/';
1107 }
1108
1109 reader.set_position(info.module_filename_offset);
1110 out_pos +=
1111 reader.ReadCString(kMaxOffset, out + out_pos, out_size - out_pos);
1112 } else {
1113 out[out_pos++] = '\0';
1114 }
1115
1116 out[out_pos - 1] = ':';
1117 auto result = std::to_chars(out + out_pos, out + out_size,
1118 static_cast<intptr_t>(info.line));
1119 if (result.ec != std::errc()) {
1120 out[out_pos - 1] = '\0';
1121 return;
1122 }
1123 out_pos = static_cast<size_t>(result.ptr - out);
1124
1125 out[out_pos++] = ':';
1126 result = std::to_chars(out + out_pos, out + out_size,
1127 static_cast<intptr_t>(info.column));
1128 if (result.ec != std::errc()) {
1129 out[out_pos - 1] = '\0';
1130 return;
1131 }
1132 out_pos = static_cast<size_t>(result.ptr - out);
1133
1134 out[out_pos++] = '\0';
1135 }
1136
1137 // Reads the Line Number info for a compile unit.
GetLineNumberInfoFromObject(int fd,uint64_t pc,uint64_t cu_offset,uint64_t base_address,char * out,size_t out_size)1138 bool GetLineNumberInfoFromObject(int fd,
1139 uint64_t pc,
1140 uint64_t cu_offset,
1141 uint64_t base_address,
1142 char* out,
1143 size_t out_size) {
1144 uint64_t cu_name_offset;
1145 uint64_t debug_line_offset;
1146 if (!ReadCompileUnit(fd, pc, cu_offset, base_address, &debug_line_offset,
1147 &cu_name_offset)) {
1148 return false;
1149 }
1150
1151 ElfW(Shdr) debug_line;
1152 constexpr static char kDebugLineSectionName[] = ".debug_line";
1153 if (!google::GetSectionHeaderByName(fd, kDebugLineSectionName,
1154 sizeof(kDebugLineSectionName),
1155 &debug_line)) {
1156 return false;
1157 }
1158
1159 LineNumberInfo info;
1160 info.pc = pc;
1161 uint64_t line_info_program_offset = debug_line.sh_offset + debug_line_offset;
1162 GetLineNumbersInProgram(fd, &info, base_address, line_info_program_offset,
1163 cu_name_offset);
1164
1165 if (info.line == 0) {
1166 // No matching line number or filename found.
1167 return false;
1168 }
1169
1170 SerializeLineNumberInfoToString(fd, info, out, out_size);
1171
1172 return true;
1173 }
1174
1175 struct FrameInfo {
1176 raw_ptr<uint64_t> cu_offset;
1177 uintptr_t pc;
1178 };
1179
1180 // Returns the number of frames still missing info.
1181 //
1182 // The aranges table is a mapping of ranges to compilation units. Given an array
1183 // of `frame_info`, this finds the compile units for each of the frames doing
1184 // only one pass over the table. It does not preserve the order of `frame_info`.
1185 //
1186 // The main benefit of this function is preserving the single pass through the
1187 // table which is important for performance.
ProcessFlatArangeSet(BufferedDwarfReader * reader,uint64_t next_set,uint8_t address_size,uint64_t base_address,uint64_t cu_offset,FrameInfo * frame_info,size_t num_frames)1188 size_t ProcessFlatArangeSet(BufferedDwarfReader* reader,
1189 uint64_t next_set,
1190 uint8_t address_size,
1191 uint64_t base_address,
1192 uint64_t cu_offset,
1193 FrameInfo* frame_info,
1194 size_t num_frames) {
1195 size_t unsorted_start = 0;
1196 while (unsorted_start < num_frames && reader->position() < next_set) {
1197 uint64_t start;
1198 uint64_t length;
1199 if (!reader->ReadAddress(address_size, start)) {
1200 break;
1201 }
1202 if (!reader->ReadAddress(address_size, length)) {
1203 break;
1204 }
1205 uint64_t end = start + length;
1206 for (size_t i = unsorted_start; i < num_frames; ++i) {
1207 uint64_t module_relative_pc = frame_info[i].pc - base_address;
1208 if (start <= module_relative_pc && module_relative_pc < end) {
1209 *frame_info[i].cu_offset = cu_offset;
1210 if (i != unsorted_start) {
1211 // Move to sorted section.
1212 std::swap(frame_info[i], frame_info[unsorted_start]);
1213 }
1214 unsorted_start++;
1215 }
1216 }
1217 }
1218
1219 return unsorted_start;
1220 }
1221
1222 // This is a pre-step that uses the .debug_aranges table to find all the compile
1223 // units for a given set of frames. This allows code to avoid iterating over
1224 // all compile units at a later step in the symbolization process.
PopulateCompileUnitOffsets(int fd,FrameInfo * frame_info,size_t num_frames,uint64_t base_address)1225 void PopulateCompileUnitOffsets(int fd,
1226 FrameInfo* frame_info,
1227 size_t num_frames,
1228 uint64_t base_address) {
1229 ElfW(Shdr) debug_aranges;
1230 constexpr static char kDebugArangesSectionName[] = ".debug_aranges";
1231 if (!google::GetSectionHeaderByName(fd, kDebugArangesSectionName,
1232 sizeof(kDebugArangesSectionName),
1233 &debug_aranges)) {
1234 return;
1235 }
1236 uint64_t debug_aranges_end = debug_aranges.sh_offset + debug_aranges.sh_size;
1237 uint64_t next_arange_set = kMaxOffset;
1238 size_t unsorted_start = 0;
1239 for (BufferedDwarfReader reader(fd, debug_aranges.sh_offset);
1240 unsorted_start < num_frames && reader.position() < debug_aranges_end;
1241 reader.set_position(next_arange_set)) {
1242 bool is_64bit;
1243 uint64_t length;
1244 uint16_t arange_version;
1245 uint64_t debug_info_offset;
1246 uint8_t address_size;
1247 if (!reader.ReadCommonHeader(is_64bit, length, arange_version,
1248 debug_info_offset, address_size,
1249 next_arange_set)) {
1250 return;
1251 }
1252
1253 uint8_t segment_size;
1254 if (!reader.ReadInt8(segment_size)) {
1255 return;
1256 }
1257
1258 if (segment_size != 0) {
1259 // Only flat namespaces are supported.
1260 return;
1261 }
1262
1263 // The tuple list is aligned, to a multiple of the tuple-size after the
1264 // section sstart. Because this code only supports flat address spaces, this
1265 // means 2*address_size.
1266 while (((reader.position() - debug_aranges.sh_offset) %
1267 (2 * address_size)) != 0) {
1268 uint8_t dummy;
1269 if (!reader.ReadInt8(dummy)) {
1270 return;
1271 }
1272 }
1273 unsorted_start += ProcessFlatArangeSet(
1274 &reader, next_arange_set, address_size, base_address, debug_info_offset,
1275 &frame_info[unsorted_start], num_frames - unsorted_start);
1276 }
1277 }
1278
1279 } // namespace
1280
GetDwarfSourceLineNumber(const void * pc,uint64_t cu_offset,char * out,size_t out_size)1281 bool GetDwarfSourceLineNumber(const void* pc,
1282 uint64_t cu_offset,
1283 char* out,
1284 size_t out_size) {
1285 uint64_t pc0 = reinterpret_cast<uint64_t>(pc);
1286 uint64_t object_start_address = 0;
1287 uint64_t object_base_address = 0;
1288
1289 google::FileDescriptor object_fd(google::FileDescriptor(
1290 google::OpenObjectFileContainingPcAndGetStartAddress(
1291 pc0, object_start_address, object_base_address, nullptr, 0)));
1292
1293 if (!object_fd.get()) {
1294 return false;
1295 }
1296
1297 if (!GetLineNumberInfoFromObject(object_fd.get(), pc0, cu_offset,
1298 object_base_address, out, out_size)) {
1299 return false;
1300 }
1301
1302 return true;
1303 }
1304
GetDwarfCompileUnitOffsets(const void * const * trace,uint64_t * cu_offsets,size_t num_frames)1305 void GetDwarfCompileUnitOffsets(const void* const* trace,
1306 uint64_t* cu_offsets,
1307 size_t num_frames) {
1308 // LINT.IfChange(max_stack_frames)
1309 FrameInfo frame_info[250] = {};
1310 // LINT.ThenChange(stack_trace.h:max_stack_frames)
1311 for (size_t i = 0; i < num_frames; i++) {
1312 // The `cu_offset` also encodes the original sort order.
1313 frame_info[i].cu_offset = &cu_offsets[i];
1314 frame_info[i].pc = reinterpret_cast<uintptr_t>(trace[i]);
1315 }
1316 auto pc_comparator = [](const FrameInfo& lhs, const FrameInfo& rhs) {
1317 return lhs.pc < rhs.pc;
1318 };
1319
1320 // Use heapsort to avoid recursion in a signal handler.
1321 std::make_heap(&frame_info[0], &frame_info[num_frames - 1], pc_comparator);
1322 std::sort_heap(&frame_info[0], &frame_info[num_frames - 1], pc_comparator);
1323
1324 // Walk the frame_info one compilation unit at a time.
1325 for (size_t cur_frame = 0; cur_frame < num_frames; ++cur_frame) {
1326 uint64_t object_start_address = 0;
1327 uint64_t object_base_address = 0;
1328 google::FileDescriptor object_fd(google::FileDescriptor(
1329 google::OpenObjectFileContainingPcAndGetStartAddress(
1330 frame_info[cur_frame].pc, object_start_address, object_base_address,
1331 nullptr, 0)));
1332
1333 // Some stack frames may not have a corresponding object file, e.g. a call
1334 // frame inside the Linux kernel's vdso. Just skip over these stack frames,
1335 // as this is done on a best-effort basis.
1336 if (object_fd.get() < 0) {
1337 continue;
1338 }
1339
1340 // TODO(https://crbug.com/1335630): Consider exposing the end address so a
1341 // range of frames can be bulk-populated. This was originally implemented,
1342 // but line number symbolization is currently broken by default (and also
1343 // broken in sandboxed processes). The various issues will be addressed
1344 // incrementally in follow-up patches, and the optimization here restored if
1345 // needed.
1346
1347 PopulateCompileUnitOffsets(object_fd.get(), &frame_info[cur_frame], 1,
1348 object_base_address);
1349 }
1350 }
1351
1352 } // namespace debug
1353 } // namespace base
1354
1355 #else // USE_SYMBOLIZE
1356
1357 #include <cstring>
1358
1359 namespace base {
1360 namespace debug {
1361
GetDwarfSourceLineNumber(const void * pc,uint64_t cu_offset,char * out,size_t out_size)1362 bool GetDwarfSourceLineNumber(const void* pc,
1363 uint64_t cu_offset,
1364 char* out,
1365 size_t out_size) {
1366 return false;
1367 }
1368
GetDwarfCompileUnitOffsets(const void * const * trace,uint64_t * cu_offsets,size_t num_frames)1369 void GetDwarfCompileUnitOffsets(const void* const* trace,
1370 uint64_t* cu_offsets,
1371 size_t num_frames) {
1372 // Provide defined values even in the stub.
1373 memset(cu_offsets, 0, sizeof(cu_offsets) * num_frames);
1374 }
1375
1376 } // namespace debug
1377 } // namespace base
1378
1379 #endif
1380