diff options
Diffstat (limited to 'gdb/dwarf2/line-program.c')
-rw-r--r-- | gdb/dwarf2/line-program.c | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/gdb/dwarf2/line-program.c b/gdb/dwarf2/line-program.c new file mode 100644 index 0000000..c30f70d --- /dev/null +++ b/gdb/dwarf2/line-program.c @@ -0,0 +1,720 @@ +/* DWARF 2 debugging format support for GDB. + + Copyright (C) 1994-2025 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "dwarf2/line-program.h" +#include "dwarf2/cu.h" +#include "dwarf2/line-header.h" +#include "dwarf2/read.h" +#include "buildsym.h" +#include "complaints.h" +#include "filenames.h" +#include "gdbarch.h" + +static void +dwarf2_debug_line_missing_file_complaint () +{ + complaint (_(".debug_line section has line data without a file")); +} + +static void +dwarf2_debug_line_missing_end_sequence_complaint () +{ + complaint (_(".debug_line section has line " + "program sequence without an end")); +} + +/* State machine to track the state of the line number program. */ + +class lnp_state_machine +{ +public: + /* Initialize a machine state for the start of a line number + program. */ + lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch, line_header *lh); + + file_entry *current_file () + { + /* lh->file_names is 0-based, but the file name numbers in the + statement program are 1-based. */ + return m_line_header->file_name_at (m_file); + } + + /* Record the line in the state machine. END_SEQUENCE is true if + we're processing the end of a sequence. */ + void record_line (bool end_sequence); + + /* Check ADDRESS is -1, -2, or zero and less than UNRELOCATED_LOWPC, and if + true nop-out rest of the lines in this sequence. */ + void check_line_address (struct dwarf2_cu *cu, + const gdb_byte *line_ptr, + unrelocated_addr unrelocated_lowpc, + unrelocated_addr address); + + void handle_set_discriminator (unsigned int discriminator) + { + m_discriminator = discriminator; + m_line_has_non_zero_discriminator |= discriminator != 0; + } + + /* Handle DW_LNE_set_address. */ + void handle_set_address (unrelocated_addr address) + { + m_op_index = 0; + m_address + = (unrelocated_addr) gdbarch_adjust_dwarf2_line (m_gdbarch, + (CORE_ADDR) address, + false); + } + + /* Handle DW_LNS_advance_pc. */ + void handle_advance_pc (CORE_ADDR adjust); + + /* Handle a special opcode. */ + void handle_special_opcode (unsigned char op_code); + + /* Handle DW_LNS_advance_line. */ + void handle_advance_line (int line_delta) + { + advance_line (line_delta); + } + + /* Handle DW_LNS_set_file. */ + void handle_set_file (file_name_index file); + + /* Handle DW_LNS_negate_stmt. */ + void handle_negate_stmt () + { + m_flags ^= LEF_IS_STMT; + } + + /* Handle DW_LNS_const_add_pc. */ + void handle_const_add_pc (); + + /* Handle DW_LNS_fixed_advance_pc. */ + void handle_fixed_advance_pc (CORE_ADDR addr_adj) + { + addr_adj = gdbarch_adjust_dwarf2_line (m_gdbarch, addr_adj, true); + m_address = (unrelocated_addr) ((CORE_ADDR) m_address + addr_adj); + m_op_index = 0; + } + + /* Handle DW_LNS_copy. */ + void handle_copy () + { + record_line (false); + m_discriminator = 0; + m_flags &= ~LEF_PROLOGUE_END; + m_flags &= ~LEF_EPILOGUE_BEGIN; + } + + /* Handle DW_LNE_end_sequence. */ + void handle_end_sequence () + { + m_currently_recording_lines = true; + } + + /* Handle DW_LNS_set_prologue_end. */ + void handle_set_prologue_end () + { + m_flags |= LEF_PROLOGUE_END; + } + + void handle_set_epilogue_begin () + { + m_flags |= LEF_EPILOGUE_BEGIN; + } + +private: + /* Advance the line by LINE_DELTA. */ + void advance_line (int line_delta) + { + m_line += line_delta; + + if (line_delta != 0) + m_line_has_non_zero_discriminator = m_discriminator != 0; + } + + bool record_line_p (); + void finish_line (); + void record_line_1 (unsigned int line, linetable_entry_flags flags); + + struct dwarf2_cu *m_cu; + + /* The builder associated with the CU. */ + buildsym_compunit *m_builder; + + gdbarch *m_gdbarch; + + /* The line number header. */ + line_header *m_line_header; + + /* These are part of the standard DWARF line number state machine, + and initialized according to the DWARF spec. */ + + unsigned char m_op_index = 0; + /* The line table index of the current file. */ + file_name_index m_file = 1; + unsigned int m_line = 1; + + /* These are initialized in the constructor. */ + + unrelocated_addr m_address; + linetable_entry_flags m_flags; + unsigned int m_discriminator = 0; + + /* Additional bits of state we need to track. */ + + /* The last file a line number was recorded for. */ + struct subfile *m_last_subfile = NULL; + + /* The address of the last line entry. */ + unrelocated_addr m_last_address; + + /* Set to true when a previous line at the same address (using + m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false + when a line entry at a new address (m_address different to + m_last_address) is processed. */ + bool m_stmt_at_address = false; + + /* When true, record the lines we decode. */ + bool m_currently_recording_lines = true; + + /* The last line number that was recorded, used to coalesce + consecutive entries for the same line. This can happen, for + example, when discriminators are present. PR 17276. */ + unsigned int m_last_line = 0; + bool m_line_has_non_zero_discriminator = false; +}; + +void +lnp_state_machine::handle_advance_pc (CORE_ADDR adjust) +{ + CORE_ADDR addr_adj = (((m_op_index + adjust) + / m_line_header->maximum_ops_per_instruction) + * m_line_header->minimum_instruction_length); + addr_adj = gdbarch_adjust_dwarf2_line (m_gdbarch, addr_adj, true); + m_address = (unrelocated_addr) ((CORE_ADDR) m_address + addr_adj); + m_op_index = ((m_op_index + adjust) + % m_line_header->maximum_ops_per_instruction); +} + +void +lnp_state_machine::handle_special_opcode (unsigned char op_code) +{ + unsigned char adj_opcode = op_code - m_line_header->opcode_base; + unsigned char adj_opcode_d = adj_opcode / m_line_header->line_range; + unsigned char adj_opcode_r = adj_opcode % m_line_header->line_range; + CORE_ADDR addr_adj = (((m_op_index + adj_opcode_d) + / m_line_header->maximum_ops_per_instruction) + * m_line_header->minimum_instruction_length); + addr_adj = gdbarch_adjust_dwarf2_line (m_gdbarch, addr_adj, true); + m_address = (unrelocated_addr) ((CORE_ADDR) m_address + addr_adj); + m_op_index = ((m_op_index + adj_opcode_d) + % m_line_header->maximum_ops_per_instruction); + + int line_delta = m_line_header->line_base + adj_opcode_r; + advance_line (line_delta); + record_line (false); + m_discriminator = 0; + m_flags &= ~LEF_PROLOGUE_END; + m_flags &= ~LEF_EPILOGUE_BEGIN; +} + +void +lnp_state_machine::handle_set_file (file_name_index file) +{ + m_file = file; + + const file_entry *fe = current_file (); + if (fe == NULL) + dwarf2_debug_line_missing_file_complaint (); + else + { + m_line_has_non_zero_discriminator = m_discriminator != 0; + dwarf2_start_subfile (m_cu, *fe, *m_line_header); + } +} + +void +lnp_state_machine::handle_const_add_pc () +{ + CORE_ADDR adjust + = (255 - m_line_header->opcode_base) / m_line_header->line_range; + + CORE_ADDR addr_adj + = (((m_op_index + adjust) + / m_line_header->maximum_ops_per_instruction) + * m_line_header->minimum_instruction_length); + + addr_adj = gdbarch_adjust_dwarf2_line (m_gdbarch, addr_adj, true); + m_address = (unrelocated_addr) ((CORE_ADDR) m_address + addr_adj); + m_op_index = ((m_op_index + adjust) + % m_line_header->maximum_ops_per_instruction); +} + +/* Return true if we should add LINE to the line number table. + LINE is the line to add, LAST_LINE is the last line that was added, + LAST_SUBFILE is the subfile for LAST_LINE. + LINE_HAS_NON_ZERO_DISCRIMINATOR is non-zero if LINE has ever + had a non-zero discriminator. + + We have to be careful in the presence of discriminators. + E.g., for this line: + + for (i = 0; i < 100000; i++); + + clang can emit four line number entries for that one line, + each with a different discriminator. + See gdb.dwarf2/dw2-single-line-discriminators.exp for an example. + + However, we want gdb to coalesce all four entries into one. + Otherwise the user could stepi into the middle of the line and + gdb would get confused about whether the pc really was in the + middle of the line. + + Things are further complicated by the fact that two consecutive + line number entries for the same line is a heuristic used by gcc + to denote the end of the prologue. So we can't just discard duplicate + entries, we have to be selective about it. The heuristic we use is + that we only collapse consecutive entries for the same line if at least + one of those entries has a non-zero discriminator. PR 17276. + + Note: Addresses in the line number state machine can never go backwards + within one sequence, thus this coalescing is ok. */ + +bool +lnp_state_machine::record_line_p () +{ + if (m_builder->get_current_subfile () != m_last_subfile) + return true; + if (m_line != m_last_line) + return true; + /* Same line for the same file that we've seen already. + As a last check, for pr 17276, only record the line if the line + has never had a non-zero discriminator. */ + if (!m_line_has_non_zero_discriminator) + return true; + return false; +} + +/* Use the CU's builder to record line number LINE with the given + flags. */ + +void +lnp_state_machine::record_line_1 (unsigned int line, + linetable_entry_flags flags) +{ + if (m_currently_recording_lines) + { + unrelocated_addr addr + = unrelocated_addr (gdbarch_addr_bits_remove (m_gdbarch, + (CORE_ADDR) m_address)); + + if (dwarf_line_debug) + gdb_printf (gdb_stdlog, "Recording line %u, file %s, address %s\n", + m_line, lbasename (m_last_subfile->name.c_str ()), + paddress (m_gdbarch, (CORE_ADDR) addr)); + + m_builder->record_line (m_last_subfile, line, addr, flags); + } +} + +/* Subroutine of dwarf_decode_lines_1 to simplify it. + Mark the end of a set of line number records. */ + +void +lnp_state_machine::finish_line () +{ + if (m_last_subfile == nullptr) + return; + + if (dwarf_line_debug) + { + gdb_printf (gdb_stdlog, + "Finishing current line, file %s, address %s\n", + lbasename (m_last_subfile->name.c_str ()), + paddress (m_gdbarch, (CORE_ADDR) m_address)); + } + + record_line_1 (0, LEF_IS_STMT); +} + +void +lnp_state_machine::record_line (bool end_sequence) +{ + if (dwarf_line_debug) + { + gdb_printf (gdb_stdlog, + "Processing actual line %u: file %u," + " address %s, is_stmt %u, prologue_end %u," + " epilogue_begin %u, discrim %u%s\n", + m_line, m_file, + paddress (m_gdbarch, (CORE_ADDR) m_address), + (m_flags & LEF_IS_STMT) != 0, + (m_flags & LEF_PROLOGUE_END) != 0, + (m_flags & LEF_EPILOGUE_BEGIN) != 0, + m_discriminator, + (end_sequence ? "\t(end sequence)" : "")); + } + + file_entry *fe = current_file (); + + if (fe == NULL) + dwarf2_debug_line_missing_file_complaint (); + /* For now we ignore lines not starting on an instruction boundary. + But not when processing end_sequence for compatibility with the + previous version of the code. */ + else if (m_op_index == 0 || end_sequence) + { + /* When we switch files we insert an end maker in the first file, + switch to the second file and add a new line entry. The + problem is that the end marker inserted in the first file will + discard any previous line entries at the same address. If the + line entries in the first file are marked as is-stmt, while + the new line in the second file is non-stmt, then this means + the end marker will discard is-stmt lines so we can have a + non-stmt line. This means that there are less addresses at + which the user can insert a breakpoint. + + To improve this we track the last address in m_last_address, + and whether we have seen an is-stmt at this address. Then + when switching files, if we have seen a stmt at the current + address, and we are switching to create a non-stmt line, then + discard the new line. */ + bool file_changed = m_last_subfile != m_builder->get_current_subfile (); + bool ignore_this_line + = ((file_changed && !end_sequence && m_last_address == m_address + && ((m_flags & LEF_IS_STMT) == 0) + && m_stmt_at_address) + || (!end_sequence && m_line == 0)); + + if ((file_changed && !ignore_this_line) || end_sequence) + finish_line (); + + if (!end_sequence && !ignore_this_line) + { + linetable_entry_flags lte_flags = m_flags; + if (m_cu->producer_is_codewarrior ()) + lte_flags |= LEF_IS_STMT; + + if (record_line_p ()) + { + m_last_subfile = m_builder->get_current_subfile (); + record_line_1 (m_line, lte_flags); + m_last_line = m_line; + } + } + } + + /* Track whether we have seen any IS_STMT true at m_address in case we + have multiple line table entries all at m_address. */ + if (m_last_address != m_address) + { + m_stmt_at_address = false; + m_last_address = m_address; + } + m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0; +} + +lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch, + line_header *lh) + : m_cu (cu), + m_builder (cu->get_builder ()), + m_gdbarch (arch), + m_line_header (lh), + /* Call `gdbarch_adjust_dwarf2_line' on the initial 0 address as + if there was a line entry for it so that the backend has a + chance to adjust it and also record it in case it needs it. + This is currently used by MIPS code, + cf. `mips_adjust_dwarf2_line'. */ + m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)), + m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0), + m_last_address (m_address) +{ +} + +void +lnp_state_machine::check_line_address (struct dwarf2_cu *cu, + const gdb_byte *line_ptr, + unrelocated_addr unrelocated_lowpc, + unrelocated_addr address) +{ + /* Linkers resolve a symbolic relocation referencing a GC'd function to 0, + -1 or -2 (-2 is used by certain lld versions, see + https://github.com/llvm/llvm-project/commit/e618ccbf431f6730edb6d1467a127c3a52fd57f7). + If ADDRESS is 0, ignoring the opcode will err if the text section is + located at 0x0. In this case, additionally check that if + ADDRESS < UNRELOCATED_LOWPC. */ + + if ((address == (unrelocated_addr) 0 && address < unrelocated_lowpc) + || address == (unrelocated_addr) -1 + || address == (unrelocated_addr) -2) + { + /* This line table is for a function which has been + GCd by the linker. Ignore it. PR gdb/12528 */ + + struct objfile *objfile = cu->per_objfile->objfile; + long line_offset = line_ptr - get_debug_line_section (cu)->buffer; + + complaint (_(".debug_line address at offset 0x%lx is 0 [in module %s]"), + line_offset, objfile_name (objfile)); + m_currently_recording_lines = false; + /* Note: m_currently_recording_lines is left as false until we see + DW_LNE_end_sequence. */ + } +} + +/* Subroutine of dwarf_decode_lines to simplify it. + Process the line number information in LH. */ + +static void +dwarf_decode_lines_1 (struct line_header *lh, struct dwarf2_cu *cu, + unrelocated_addr lowpc) +{ + const gdb_byte *line_ptr, *extended_end; + const gdb_byte *line_end; + unsigned int bytes_read, extended_len; + unsigned char op_code, extended_op; + struct objfile *objfile = cu->per_objfile->objfile; + bfd *abfd = objfile->obfd.get (); + struct gdbarch *gdbarch = objfile->arch (); + + line_ptr = lh->statement_program_start; + line_end = lh->statement_program_end; + + /* Read the statement sequences until there's nothing left. */ + while (line_ptr < line_end) + { + /* The DWARF line number program state machine. Reset the state + machine at the start of each sequence. */ + lnp_state_machine state_machine (cu, gdbarch, lh); + bool end_sequence = false; + + /* Start a subfile for the current file of the state + machine. */ + const file_entry *fe = state_machine.current_file (); + + if (fe != NULL) + dwarf2_start_subfile (cu, *fe, *lh); + + /* Decode the table. */ + while (line_ptr < line_end && !end_sequence) + { + op_code = read_1_byte (abfd, line_ptr); + line_ptr += 1; + + if (op_code >= lh->opcode_base) + { + /* Special opcode. */ + state_machine.handle_special_opcode (op_code); + } + else switch (op_code) + { + case DW_LNS_extended_op: + extended_len = read_unsigned_leb128 (abfd, line_ptr, + &bytes_read); + line_ptr += bytes_read; + extended_end = line_ptr + extended_len; + extended_op = read_1_byte (abfd, line_ptr); + line_ptr += 1; + if (DW_LNE_lo_user <= extended_op + && extended_op <= DW_LNE_hi_user) + { + /* Vendor extension, ignore. */ + line_ptr = extended_end; + break; + } + switch (extended_op) + { + case DW_LNE_end_sequence: + state_machine.handle_end_sequence (); + end_sequence = true; + break; + case DW_LNE_set_address: + { + unrelocated_addr address + = cu->header.read_address (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + + state_machine.check_line_address (cu, line_ptr, lowpc, + address); + state_machine.handle_set_address (address); + } + break; + case DW_LNE_define_file: + { + const char *cur_file; + unsigned int mod_time, length; + dir_index dindex; + + cur_file = read_direct_string (abfd, line_ptr, + &bytes_read); + line_ptr += bytes_read; + dindex = (dir_index) + read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + mod_time = + read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + length = + read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + lh->add_file_name (cur_file, dindex, mod_time, length); + } + break; + case DW_LNE_set_discriminator: + { + /* The discriminator is not interesting to the + debugger; just ignore it. We still need to + check its value though: + if there are consecutive entries for the same + (non-prologue) line we want to coalesce them. + PR 17276. */ + unsigned int discr + = read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + + state_machine.handle_set_discriminator (discr); + } + break; + default: + complaint (_("mangled .debug_line section")); + return; + } + /* Make sure that we parsed the extended op correctly. If e.g. + we expected a different address size than the producer used, + we may have read the wrong number of bytes. */ + if (line_ptr != extended_end) + { + complaint (_("mangled .debug_line section")); + return; + } + break; + case DW_LNS_copy: + state_machine.handle_copy (); + break; + case DW_LNS_advance_pc: + { + CORE_ADDR adjust + = read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + + state_machine.handle_advance_pc (adjust); + } + break; + case DW_LNS_advance_line: + { + int line_delta + = read_signed_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + + state_machine.handle_advance_line (line_delta); + } + break; + case DW_LNS_set_file: + { + file_name_index file + = (file_name_index) read_unsigned_leb128 (abfd, line_ptr, + &bytes_read); + line_ptr += bytes_read; + + state_machine.handle_set_file (file); + } + break; + case DW_LNS_set_column: + (void) read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + break; + case DW_LNS_negate_stmt: + state_machine.handle_negate_stmt (); + break; + case DW_LNS_set_basic_block: + break; + /* Add to the address register of the state machine the + address increment value corresponding to special opcode + 255. I.e., this value is scaled by the minimum + instruction length since special opcode 255 would have + scaled the increment. */ + case DW_LNS_const_add_pc: + state_machine.handle_const_add_pc (); + break; + case DW_LNS_fixed_advance_pc: + { + CORE_ADDR addr_adj = read_2_bytes (abfd, line_ptr); + line_ptr += 2; + + state_machine.handle_fixed_advance_pc (addr_adj); + } + break; + case DW_LNS_set_prologue_end: + state_machine.handle_set_prologue_end (); + break; + case DW_LNS_set_epilogue_begin: + state_machine.handle_set_epilogue_begin (); + break; + default: + { + /* Unknown standard opcode, ignore it. */ + int i; + + for (i = 0; i < lh->standard_opcode_lengths[op_code]; i++) + { + (void) read_unsigned_leb128 (abfd, line_ptr, &bytes_read); + line_ptr += bytes_read; + } + } + } + } + + if (!end_sequence) + dwarf2_debug_line_missing_end_sequence_complaint (); + + /* We got a DW_LNE_end_sequence (or we ran off the end of the buffer, + in which case we still finish recording the last line). */ + state_machine.record_line (true); + } +} + +/* See dwarf2/line-program.h. */ + +void +dwarf_decode_lines (struct line_header *lh, struct dwarf2_cu *cu, + unrelocated_addr lowpc, bool decode_mapping) +{ + if (decode_mapping) + dwarf_decode_lines_1 (lh, cu, lowpc); + + /* Make sure a symtab is created for every file, even files + which contain only variables (i.e. no code with associated + line numbers). */ + buildsym_compunit *builder = cu->get_builder (); + struct compunit_symtab *cust = builder->get_compunit_symtab (); + + for (auto &fe : lh->file_names ()) + { + dwarf2_start_subfile (cu, fe, *lh); + subfile *sf = builder->get_current_subfile (); + + if (sf->symtab == nullptr) + sf->symtab = allocate_symtab (cust, sf->name.c_str (), + sf->name_for_id.c_str ()); + + fe.symtab = sf->symtab; + } +} |