Author: Jonas Devlieghere Date: 2026-06-17T23:46:56Z New Revision: dd069b691dd365d447a87cd606b315ec445c7ae4
URL: https://github.com/llvm/llvm-project/commit/dd069b691dd365d447a87cd606b315ec445c7ae4 DIFF: https://github.com/llvm/llvm-project/commit/dd069b691dd365d447a87cd606b315ec445c7ae4.diff LOG: [lldb] Skip the prologue when a function's entry has no line row (#204480) Function::GetPrologueByteSize computed the prologue only when a line table row contained the function's entry address (low_pc). When no row covers low_pc it returned 0, leaving a name breakpoint sitting on the function's entry address. For WebAssembly the entry address is the function's locals-declaration byte rather than an instruction, so the line table has no row there and the breakpoint is never hit. When low_pc has no covering row, fall back to the first line row that begins within the function's range and run the existing prologue logic on it. For functions whose entry is already covered (all normally compiled native code) this branch is not taken, so behavior is remains unchanged. This PR adds a hand (Claude) crafted regression test with a function whose entry address is not covered by a line row. Added: lldb/test/Shell/SymbolFile/DWARF/x86/prologue-entry-not-covered.s Modified: lldb/source/Symbol/Function.cpp Removed: ################################################################################ diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp index 9b9dd3fbc0c65..359e9369c07c2 100644 --- a/lldb/source/Symbol/Function.cpp +++ b/lldb/source/Symbol/Function.cpp @@ -590,8 +590,39 @@ uint32_t Function::GetPrologueByteSize() { if (line_table) { LineEntry first_line_entry; uint32_t first_line_entry_idx = UINT32_MAX; - if (line_table->FindLineEntryByAddress(GetAddress(), first_line_entry, - &first_line_entry_idx)) { + bool found_first_line_entry = line_table->FindLineEntryByAddress( + GetAddress(), first_line_entry, &first_line_entry_idx); + + // When the entry point isn't covered (e.g. WebAssembly), fall back to the + // first line entry that begins within the function so the prologue is + // still skipped to a real instruction instead of leaving the breakpoint + // on the (unexecutable) entry address. + if (!found_first_line_entry) { + AddressRange entry_range; + if (m_block.GetRangeContainingAddress(m_address, entry_range)) { + const addr_t func_start_addr = m_address.GetFileAddress(); + const addr_t func_end_addr = + entry_range.GetBaseAddress().GetFileAddress() + + entry_range.GetByteSize(); + const uint32_t line_table_size = line_table->GetSize(); + for (uint32_t idx = 0; idx < line_table_size; ++idx) { + LineEntry line_entry; + bool success = line_table->GetLineEntryAtIndex(idx, line_entry); + assert(success && "idx is within the line table size"); + UNUSED_IF_ASSERT_DISABLED(success); + const addr_t entry_addr = + line_entry.range.GetBaseAddress().GetFileAddress(); + if (entry_addr >= func_start_addr && entry_addr < func_end_addr) { + first_line_entry = line_entry; + first_line_entry_idx = idx; + found_first_line_entry = true; + break; + } + } + } + } + + if (found_first_line_entry) { // Make sure the first line entry isn't already the end of the prologue addr_t prologue_end_file_addr = LLDB_INVALID_ADDRESS; addr_t line_zero_end_file_addr = LLDB_INVALID_ADDRESS; diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/prologue-entry-not-covered.s b/lldb/test/Shell/SymbolFile/DWARF/x86/prologue-entry-not-covered.s new file mode 100644 index 0000000000000..5991c9573ee64 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/x86/prologue-entry-not-covered.s @@ -0,0 +1,101 @@ +# A function whose entry point (low_pc) is not covered by any line table row. +# The first instruction is emitted before the first .loc, so the first line +# table entry begins at low_pc+1, leaving the entry address uncovered. This +# mirrors the WebAssembly case where the function entry points at the locals +# declaration (which has no line entry) rather than at an executable +# instruction. +# +# Function::GetPrologueByteSize() must fall back to the first line entry that +# begins within the function and skip the prologue to a real instruction, +# instead of leaving the breakpoint sitting on the (unexecutable) entry +# address. + +# RUN: split-file %s %t +# RUN: llvm-mc -triple x86_64-pc-linux -filetype=obj %t/input.s -o %t/input.o +# RUN: %lldb %t/input.o -s %t/commands -o exit | FileCheck %s + +#--- commands + +# With prologue skipping (the default), the breakpoint must move past the +# uncovered entry address (0x0) to the prologue_end line entry at 0x2. +breakpoint set --name foo +# CHECK-LABEL: breakpoint set --name foo +# CHECK: Breakpoint 1: where = input.o`foo + 2 at {{.*}}:2, address = 0x0000000000000002 + +# Without prologue skipping the breakpoint sits on the function entry (0x0). +# The entry address is not covered by a line entry, so no "at <file>:<line>" +# location is printed. +breakpoint set --name foo --skip-prologue false +# CHECK-LABEL: breakpoint set --name foo --skip-prologue false +# CHECK: Breakpoint 2: where = input.o`foo, address = 0x0000000000000000 + +#--- input.s + .text + .file 0 "." "-" + + .type foo,@function +foo: + # No .loc here: the entry instruction is deliberately left uncovered by + # the line table, so the first line entry begins at low_pc + 1. + nop + .loc 0 1 + nop + .loc 0 2 prologue_end + nop + retq +.Lfoo_end: + .size foo, .Lfoo_end-foo + + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 37 # DW_AT_producer + .byte 8 # DW_FORM_string + .byte 19 # DW_AT_language + .byte 5 # DW_FORM_data2 + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 1 # DW_FORM_addr + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 0 # DW_CHILDREN_no + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 1 # DW_FORM_addr + .byte 3 # DW_AT_name + .byte 8 # DW_FORM_string + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + + .section .debug_info,"",@progbits +.Lcu_begin0: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 5 # DWARF version number + .byte 1 # DWARF Unit Type + .byte 8 # Address Size (in bytes) + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 1 # Abbrev [1] DW_TAG_compile_unit + .asciz "Hand-written DWARF" # DW_AT_producer + .short 29 # DW_AT_language + .quad foo # DW_AT_low_pc + .quad .Lfoo_end # DW_AT_high_pc + .long .Lline_table_start0 # DW_AT_stmt_list + .byte 2 # Abbrev [2] DW_TAG_subprogram + .quad foo # DW_AT_low_pc + .quad .Lfoo_end # DW_AT_high_pc + .asciz "foo" # DW_AT_name + .byte 0 # End Of Children Mark +.Ldebug_info_end0: + + .section ".note.GNU-stack","",@progbits + .section .debug_line,"",@progbits +.Lline_table_start0: _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
