[Lldb-commits] [lldb] [lldb] Create a GetStartLineEntry helper (PR #211264)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 22 06:51:21 PDT 2026
https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/211264
A WebAssembly function's entry address points at the locals-declaration header, which carries no line information, so the first line table row begins past the entry point.
We already handled this in GetPrologueByteSize (dd069b691dd3), but there are other places that need the same support (GetStartLineSourceInfo, GetFunctionStartLineEntry). Rather than duplicating the logic, create a shared helper.
>From e3300fd81b22ddc30d3fdc992375252d4590263a Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 22 Jul 2026 06:49:38 -0700
Subject: [PATCH] [lldb] Create a GetStartLineEntry helper
A WebAssembly function's entry address points at the locals-declaration
header, which carries no line information, so the first line table row
begins past the entry point.
We already handled this in GetPrologueByteSize (dd069b691dd3), but there
are other places that need the same support (GetStartLineSourceInfo,
GetFunctionStartLineEntry). Rather than duplicating the logic, create a
shared helper.
---
lldb/include/lldb/Symbol/Function.h | 16 ++++
lldb/source/Symbol/Function.cpp | 77 ++++++++-------
lldb/source/Symbol/SymbolContext.cpp | 2 +-
.../source-list-function-entry-not-covered.s | 95 +++++++++++++++++++
4 files changed, 153 insertions(+), 37 deletions(-)
create mode 100644 lldb/test/Shell/SymbolFile/DWARF/x86/source-list-function-entry-not-covered.s
diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h
index 7b707b4d72540..137ea551bef24 100644
--- a/lldb/include/lldb/Symbol/Function.h
+++ b/lldb/include/lldb/Symbol/Function.h
@@ -14,6 +14,7 @@
#include "lldb/Core/Mangled.h"
#include "lldb/Expression/DWARFExpressionList.h"
#include "lldb/Symbol/Block.h"
+#include "lldb/Symbol/LineEntry.h"
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/ArrayRef.h"
@@ -448,6 +449,21 @@ class Function : public UserID, public SymbolContextScope {
void GetStartLineSourceInfo(SupportFileNSP &source_file_sp,
uint32_t &line_no);
+ /// Get the line table entry for the function's entry point.
+ ///
+ /// When the entry address is not covered by a line row, this returns the
+ /// first line entry that begins within the function's range instead.
+ ///
+ /// \param[out] line_entry
+ /// The resulting line entry.
+ ///
+ /// \param[out] index
+ /// If non-null, set to the index of the line entry in the line table.
+ ///
+ /// \return
+ /// True if a line entry was found, false otherwise.
+ bool GetStartLineEntry(LineEntry &line_entry, uint32_t *index = nullptr);
+
using SourceRange = Range<uint32_t, uint32_t>;
/// Find the file and line number range of the function.
llvm::Expected<std::pair<SupportFileNSP, SourceRange>> GetSourceInfo();
diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp
index 17c7e9bb36698..c5cbd5b33a875 100644
--- a/lldb/source/Symbol/Function.cpp
+++ b/lldb/source/Symbol/Function.cpp
@@ -269,6 +269,44 @@ Function::Function(CompileUnit *comp_unit, lldb::user_id_t func_uid,
Function::~Function() = default;
+bool Function::GetStartLineEntry(LineEntry &line_entry, uint32_t *index) {
+ LineTable *line_table = m_comp_unit ? m_comp_unit->GetLineTable() : nullptr;
+ if (line_table == nullptr)
+ return false;
+
+ uint32_t line_entry_idx = UINT32_MAX;
+ if (line_table->FindLineEntryByAddress(GetAddress(), line_entry,
+ &line_entry_idx)) {
+ if (index)
+ *index = line_entry_idx;
+ return true;
+ }
+
+ // The entry point is not covered by a line row. Fall back to the first line
+ // entry that begins within the function.
+ AddressRange entry_range;
+ if (!m_block.GetRangeContainingAddress(m_address, entry_range))
+ return false;
+ 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 entry;
+ bool success = line_table->GetLineEntryAtIndex(idx, entry);
+ assert(success && "idx is within the line table size");
+ UNUSED_IF_ASSERT_DISABLED(success);
+ const addr_t entry_addr = entry.range.GetBaseAddress().GetFileAddress();
+ if (entry_addr >= func_start_addr && entry_addr < func_end_addr) {
+ line_entry = entry;
+ if (index)
+ *index = idx;
+ return true;
+ }
+ }
+ return false;
+}
+
void Function::GetStartLineSourceInfo(SupportFileNSP &source_file_sp,
uint32_t &line_no) {
line_no = 0;
@@ -285,12 +323,8 @@ void Function::GetStartLineSourceInfo(SupportFileNSP &source_file_sp,
std::make_shared<SupportFile>(m_type->GetDeclaration().GetFile());
line_no = m_type->GetDeclaration().GetLine();
} else {
- LineTable *line_table = m_comp_unit->GetLineTable();
- if (line_table == nullptr)
- return;
-
LineEntry line_entry;
- if (line_table->FindLineEntryByAddress(GetAddress(), line_entry, nullptr)) {
+ if (GetStartLineEntry(line_entry)) {
line_no = line_entry.line;
source_file_sp = line_entry.file_sp;
}
@@ -577,37 +611,8 @@ uint32_t Function::GetPrologueByteSize() {
if (line_table) {
LineEntry first_line_entry;
uint32_t first_line_entry_idx = UINT32_MAX;
- 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;
- }
- }
- }
- }
+ bool found_first_line_entry =
+ GetStartLineEntry(first_line_entry, &first_line_entry_idx);
if (found_first_line_entry) {
// Make sure the first line entry isn't already the end of the prologue
diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp
index 8e8bf639b13f4..cbc26086fc8f6 100644
--- a/lldb/source/Symbol/SymbolContext.cpp
+++ b/lldb/source/Symbol/SymbolContext.cpp
@@ -675,7 +675,7 @@ LineEntry SymbolContext::GetFunctionStartLineEntry() const {
}
if (function) {
- if (function->GetAddress().CalculateSymbolContextLineEntry(line_entry))
+ if (function->GetStartLineEntry(line_entry))
return line_entry;
}
return LineEntry();
diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/source-list-function-entry-not-covered.s b/lldb/test/Shell/SymbolFile/DWARF/x86/source-list-function-entry-not-covered.s
new file mode 100644
index 0000000000000..ff4c790906f8f
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/x86/source-list-function-entry-not-covered.s
@@ -0,0 +1,95 @@
+# 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
+# entry begins at low_pc+1. This mirrors WebAssembly, where a function begins
+# with a locals declaration that carries no line information.
+#
+# Function::GetStartLineEntry must fall back to the first line entry within the
+# function so "source list --name" can find the function's source instead of
+# reporting no line information.
+
+# RUN: split-file %s %t
+# RUN: llvm-mc -triple x86_64-pc-linux -filetype=obj %t/input.s -o %t/input.o
+# RUN: cd %t && %lldb input.o -o "source list --name foo" -o exit | FileCheck %s
+
+# CHECK-LABEL: source list --name foo
+# CHECK: File: inlined.c
+# CHECK: void foo()
+# CHECK: source list marker
+
+#--- inlined.c
+void stop();
+void foo() {
+ // This is the source list marker.
+ stop();
+}
+
+#--- input.s
+ .text
+ .file 0 "." "inlined.c"
+
+ .type foo, at 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 2
+ nop
+ .loc 0 4 prologue_end
+ nop
+ retq
+.Lfoo_end:
+ .size foo, .Lfoo_end-foo
+
+ .section .debug_abbrev,"", at 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,"", at 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","", at progbits
+ .section .debug_line,"", at progbits
+.Lline_table_start0:
More information about the lldb-commits
mailing list