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

Reply via email to