Skip to content

Commit d6713ad

Browse files
kevinfreiKevin Frei
and
Kevin Frei
authored
Debuginfod Testing & fixes: 3rd times the charm? (#87676)
I believe I've got the tests properly configured to only run on Linux x86(_64), as I don't have a Linux AArch64/Arm device to diagnose what's going wrong with the tests (I suspect there's some issue with generating `.note.gnu.build-id` sections...) The actual code fixes have now been reviewed 3 times: #79181 (moved shell tests to API tests), #85693 (Changed some of the testing infra), and #86812 (didn't get the tests configured quite right). The Debuginfod integration for symbol acquisition in LLDB now works with the `executable` and `debuginfo` Debuginfod network requests working properly for normal, `objcopy --only-keep-debug` stripped, split-dwarf, and `objcopy --only-keep-debug` stripped *plus* split-dwarf symbols/binaries. The reasons for the multiple attempts have been tests on platforms I don't have access to (Linux AArch64/Arm + MacOS x86_64). I believe I've got the tests properly disabled for everything except for Linux x86(_64) now. I've built & tested on MacOS AArch64 and Linux x86_64. --------- Co-authored-by: Kevin Frei <[email protected]>
1 parent 8004ce2 commit d6713ad

File tree

10 files changed

+515
-17
lines changed

10 files changed

+515
-17
lines changed

lldb/packages/Python/lldbsuite/test/make/Makefile.rules

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ LLDB_BASE_DIR := $(THIS_FILE_DIR)/../../../../../
5151
#
5252
# GNUWin32 uname gives "windows32" or "server version windows32" while
5353
# some versions of MSYS uname return "MSYS_NT*", but most environments
54-
# standardize on "Windows_NT", so we'll make it consistent here.
54+
# standardize on "Windows_NT", so we'll make it consistent here.
5555
# When running tests from Visual Studio, the environment variable isn't
5656
# inherited all the way down to the process spawned for make.
5757
#----------------------------------------------------------------------
@@ -210,6 +210,12 @@ else
210210
ifeq "$(SPLIT_DEBUG_SYMBOLS)" "YES"
211211
DSYM = $(EXE).debug
212212
endif
213+
214+
ifeq "$(MAKE_DWP)" "YES"
215+
MAKE_DWO := YES
216+
DWP_NAME = $(EXE).dwp
217+
DYLIB_DWP_NAME = $(DYLIB_NAME).dwp
218+
endif
213219
endif
214220

215221
LIMIT_DEBUG_INFO_FLAGS =
@@ -357,6 +363,7 @@ ifneq "$(OS)" "Darwin"
357363

358364
OBJCOPY ?= $(call replace_cc_with,objcopy)
359365
ARCHIVER ?= $(call replace_cc_with,ar)
366+
DWP ?= $(call replace_cc_with,dwp)
360367
override AR = $(ARCHIVER)
361368
endif
362369

@@ -527,6 +534,10 @@ ifneq "$(CXX)" ""
527534
endif
528535
endif
529536

537+
ifeq "$(GEN_GNU_BUILD_ID)" "YES"
538+
LDFLAGS += -Wl,--build-id
539+
endif
540+
530541
#----------------------------------------------------------------------
531542
# DYLIB_ONLY variable can be used to skip the building of a.out.
532543
# See the sections below regarding dSYM file as well as the building of
@@ -565,10 +576,17 @@ else
565576
endif
566577
else
567578
ifeq "$(SPLIT_DEBUG_SYMBOLS)" "YES"
579+
ifeq "$(SAVE_FULL_DEBUG_BINARY)" "YES"
580+
cp "$(EXE)" "$(EXE).unstripped"
581+
endif
568582
$(OBJCOPY) --only-keep-debug "$(EXE)" "$(DSYM)"
569583
$(OBJCOPY) --strip-debug --add-gnu-debuglink="$(DSYM)" "$(EXE)" "$(EXE)"
570584
endif
585+
ifeq "$(MAKE_DWP)" "YES"
586+
$(DWP) -o "$(DWP_NAME)" $(DWOS)
571587
endif
588+
endif
589+
572590

573591
#----------------------------------------------------------------------
574592
# Make the dylib
@@ -610,9 +628,15 @@ endif
610628
else
611629
$(LD) $(DYLIB_OBJECTS) $(LDFLAGS) -shared -o "$(DYLIB_FILENAME)"
612630
ifeq "$(SPLIT_DEBUG_SYMBOLS)" "YES"
631+
ifeq "$(SAVE_FULL_DEBUG_BINARY)" "YES"
632+
cp "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME).unstripped"
633+
endif
613634
$(OBJCOPY) --only-keep-debug "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME).debug"
614635
$(OBJCOPY) --strip-debug --add-gnu-debuglink="$(DYLIB_FILENAME).debug" "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME)"
615636
endif
637+
ifeq "$(MAKE_DWP)" "YES"
638+
$(DWP) -o $(DYLIB_DWP_FILE) $(DYLIB_DWOS)
639+
endif
616640
endif
617641

618642
#----------------------------------------------------------------------

lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4378,26 +4378,38 @@ const std::shared_ptr<SymbolFileDWARFDwo> &SymbolFileDWARF::GetDwpSymbolFile() {
43784378
FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths();
43794379
ModuleSpec module_spec;
43804380
module_spec.GetFileSpec() = m_objfile_sp->GetFileSpec();
4381+
FileSpec dwp_filespec;
43814382
for (const auto &symfile : symfiles.files()) {
43824383
module_spec.GetSymbolFileSpec() =
43834384
FileSpec(symfile.GetPath() + ".dwp", symfile.GetPathStyle());
43844385
LLDB_LOG(log, "Searching for DWP using: \"{0}\"",
43854386
module_spec.GetSymbolFileSpec());
4386-
FileSpec dwp_filespec =
4387+
dwp_filespec =
43874388
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
43884389
if (FileSystem::Instance().Exists(dwp_filespec)) {
4389-
LLDB_LOG(log, "Found DWP file: \"{0}\"", dwp_filespec);
4390-
DataBufferSP dwp_file_data_sp;
4391-
lldb::offset_t dwp_file_data_offset = 0;
4392-
ObjectFileSP dwp_obj_file = ObjectFile::FindPlugin(
4393-
GetObjectFile()->GetModule(), &dwp_filespec, 0,
4394-
FileSystem::Instance().GetByteSize(dwp_filespec), dwp_file_data_sp,
4395-
dwp_file_data_offset);
4396-
if (dwp_obj_file) {
4397-
m_dwp_symfile = std::make_shared<SymbolFileDWARFDwo>(
4398-
*this, dwp_obj_file, DIERef::k_file_index_mask);
4399-
break;
4400-
}
4390+
break;
4391+
}
4392+
}
4393+
if (!FileSystem::Instance().Exists(dwp_filespec)) {
4394+
LLDB_LOG(log, "No DWP file found locally");
4395+
// Fill in the UUID for the module we're trying to match for, so we can
4396+
// find the correct DWP file, as the Debuginfod plugin uses *only* this
4397+
// data to correctly match the DWP file with the binary.
4398+
module_spec.GetUUID() = m_objfile_sp->GetUUID();
4399+
dwp_filespec =
4400+
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
4401+
}
4402+
if (FileSystem::Instance().Exists(dwp_filespec)) {
4403+
LLDB_LOG(log, "Found DWP file: \"{0}\"", dwp_filespec);
4404+
DataBufferSP dwp_file_data_sp;
4405+
lldb::offset_t dwp_file_data_offset = 0;
4406+
ObjectFileSP dwp_obj_file = ObjectFile::FindPlugin(
4407+
GetObjectFile()->GetModule(), &dwp_filespec, 0,
4408+
FileSystem::Instance().GetByteSize(dwp_filespec), dwp_file_data_sp,
4409+
dwp_file_data_offset);
4410+
if (dwp_obj_file) {
4411+
m_dwp_symfile = std::make_shared<SymbolFileDWARFDwo>(
4412+
*this, dwp_obj_file, DIERef::k_file_index_mask);
44014413
}
44024414
}
44034415
if (!m_dwp_symfile) {

lldb/source/Plugins/SymbolLocator/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# Order matters here: the first symbol locator prevents further searching.
2+
# For DWARF binaries that are both stripped and split, the Default plugin
3+
# will return the stripped binary when asked for the ObjectFile, which then
4+
# prevents an unstripped binary from being requested from the Debuginfod
5+
# provider.
6+
add_subdirectory(Debuginfod)
17
add_subdirectory(Default)
28
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
39
add_subdirectory(DebugSymbols)
410
endif()
5-
add_subdirectory(Debuginfod)

lldb/source/Plugins/SymbolVendor/ELF/SymbolVendorELF.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ llvm::StringRef SymbolVendorELF::GetPluginDescriptionStatic() {
4444
"executables.";
4545
}
4646

47+
// If this is needed elsewhere, it can be exported/moved.
48+
static bool IsDwpSymbolFile(const lldb::ModuleSP &module_sp,
49+
const FileSpec &file_spec) {
50+
DataBufferSP dwp_file_data_sp;
51+
lldb::offset_t dwp_file_data_offset = 0;
52+
// Try to create an ObjectFile from the file_spec.
53+
ObjectFileSP dwp_obj_file = ObjectFile::FindPlugin(
54+
module_sp, &file_spec, 0, FileSystem::Instance().GetByteSize(file_spec),
55+
dwp_file_data_sp, dwp_file_data_offset);
56+
// The presence of a debug_cu_index section is the key identifying feature of
57+
// a DWP file. Make sure we don't fill in the section list on dwp_obj_file
58+
// (by calling GetSectionList(false)) as this function could be called before
59+
// we may have all the symbol files collected and available.
60+
return dwp_obj_file && ObjectFileELF::classof(dwp_obj_file.get()) &&
61+
dwp_obj_file->GetSectionList(false)->FindSectionByType(
62+
eSectionTypeDWARFDebugCuIndex, false);
63+
}
64+
4765
// CreateInstance
4866
//
4967
// Platforms can register a callback to use when creating symbol vendors to
@@ -87,8 +105,15 @@ SymbolVendorELF::CreateInstance(const lldb::ModuleSP &module_sp,
87105
FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths();
88106
FileSpec dsym_fspec =
89107
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
90-
if (!dsym_fspec)
91-
return nullptr;
108+
if (!dsym_fspec || IsDwpSymbolFile(module_sp, dsym_fspec)) {
109+
// If we have a stripped binary or if we got a DWP file, we should prefer
110+
// symbols in the executable acquired through a plugin.
111+
ModuleSpec unstripped_spec =
112+
PluginManager::LocateExecutableObjectFile(module_spec);
113+
if (!unstripped_spec)
114+
return nullptr;
115+
dsym_fspec = unstripped_spec.GetFileSpec();
116+
}
92117

93118
DataBufferSP dsym_file_data_sp;
94119
lldb::offset_t dsym_file_data_offset = 0;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
C_SOURCES := main.c
2+
3+
# For normal (non DWP) Debuginfod tests, we need:
4+
5+
# * The full binary: a.out.unstripped
6+
# Produced by Makefile.rules with SAVE_FULL_DEBUG_BINARY set to YES and
7+
# SPLIT_DEBUG_SYMBOLS set to YES
8+
9+
# * The stripped binary (a.out)
10+
# Produced by Makefile.rules with SPLIT_DEBUG_SYMBOLS set to YES
11+
12+
# * The 'only-keep-debug' binary (a.out.debug)
13+
# Produced below
14+
15+
SPLIT_DEBUG_SYMBOLS := YES
16+
SAVE_FULL_DEBUG_BINARY := YES
17+
GEN_GNU_BUILD_ID := YES
18+
19+
include Makefile.rules
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import os
2+
import shutil
3+
import tempfile
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
import lldbsuite.test.lldbutil as lldbutil
8+
from lldbsuite.test.lldbtest import *
9+
10+
11+
"""
12+
Test support for the DebugInfoD network symbol acquisition protocol.
13+
This one is for simple / no split-dwarf scenarios.
14+
15+
For no-split-dwarf scenarios, there are 2 variations:
16+
1 - A stripped binary with it's corresponding unstripped binary:
17+
2 - A stripped binary with a corresponding --only-keep-debug symbols file
18+
"""
19+
20+
21+
# It looks like Linux-AArch64 doesn't support build-id's on the LLDB builtbots
22+
class DebugInfodTests(TestBase):
23+
# No need to try every flavor of debug inf.
24+
NO_DEBUG_INFO_TESTCASE = True
25+
26+
@skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
27+
def test_normal_no_symbols(self):
28+
"""
29+
Validate behavior with no symbols or symbol locator.
30+
('baseline negative' behavior)
31+
"""
32+
test_root = self.config_test(["a.out"])
33+
self.try_breakpoint(False)
34+
35+
@skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
36+
def test_normal_default(self):
37+
"""
38+
Validate behavior with symbols, but no symbol locator.
39+
('baseline positive' behavior)
40+
"""
41+
test_root = self.config_test(["a.out", "a.out.debug"])
42+
self.try_breakpoint(True)
43+
44+
@skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
45+
def test_debuginfod_symbols(self):
46+
"""
47+
Test behavior with the full binary available from Debuginfod as
48+
'debuginfo' from the plug-in.
49+
"""
50+
test_root = self.config_test(["a.out"], "a.out.unstripped")
51+
self.try_breakpoint(True)
52+
53+
@skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
54+
def test_debuginfod_executable(self):
55+
"""
56+
Test behavior with the full binary available from Debuginfod as
57+
'executable' from the plug-in.
58+
"""
59+
test_root = self.config_test(["a.out"], None, "a.out.unstripped")
60+
self.try_breakpoint(True)
61+
62+
@skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
63+
def test_debuginfod_okd_symbols(self):
64+
"""
65+
Test behavior with the 'only-keep-debug' symbols available from Debuginfod.
66+
"""
67+
test_root = self.config_test(["a.out"], "a.out.debug")
68+
self.try_breakpoint(True)
69+
70+
def try_breakpoint(self, should_have_loc):
71+
"""
72+
This function creates a target from self.aout, sets a function-name
73+
breakpoint, and checks to see if we have a file/line location,
74+
as a way to validate that the symbols have been loaded.
75+
should_have_loc specifies if we're testing that symbols have or
76+
haven't been loaded.
77+
"""
78+
target = self.dbg.CreateTarget(self.aout)
79+
self.assertTrue(target and target.IsValid(), "Target is valid")
80+
81+
bp = target.BreakpointCreateByName("func")
82+
self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid")
83+
self.assertEqual(bp.GetNumLocations(), 1)
84+
85+
loc = bp.GetLocationAtIndex(0)
86+
self.assertTrue(loc and loc.IsValid(), "Location is valid")
87+
addr = loc.GetAddress()
88+
self.assertTrue(addr and addr.IsValid(), "Loc address is valid")
89+
line_entry = addr.GetLineEntry()
90+
self.assertEqual(
91+
should_have_loc,
92+
line_entry != None and line_entry.IsValid(),
93+
"Loc line entry is valid",
94+
)
95+
if should_have_loc:
96+
self.assertEqual(line_entry.GetLine(), 4)
97+
self.assertEqual(
98+
line_entry.GetFileSpec().GetFilename(),
99+
self.main_source_file.GetFilename(),
100+
)
101+
self.dbg.DeleteTarget(target)
102+
shutil.rmtree(self.tmp_dir)
103+
104+
def config_test(self, local_files, debuginfo=None, executable=None):
105+
"""
106+
Set up a test with local_files[] copied to a different location
107+
so that we control which files are, or are not, found in the file system.
108+
Also, create a stand-alone file-system 'hosted' debuginfod server with the
109+
provided debuginfo and executable files (if they exist)
110+
111+
Make the filesystem look like:
112+
113+
/tmp/<tmpdir>/test/[local_files]
114+
115+
/tmp/<tmpdir>/cache (for lldb to use as a temp cache)
116+
117+
/tmp/<tmpdir>/buildid/<uuid>/executable -> <executable>
118+
/tmp/<tmpdir>/buildid/<uuid>/debuginfo -> <debuginfo>
119+
Returns the /tmp/<tmpdir> path
120+
"""
121+
122+
self.build()
123+
124+
uuid = self.getUUID("a.out")
125+
if not uuid:
126+
self.fail("Could not get UUID for a.out")
127+
return
128+
self.main_source_file = lldb.SBFileSpec("main.c")
129+
self.tmp_dir = tempfile.mkdtemp()
130+
test_dir = os.path.join(self.tmp_dir, "test")
131+
os.makedirs(test_dir)
132+
133+
self.aout = ""
134+
# Copy the files used by the test:
135+
for f in local_files:
136+
shutil.copy(self.getBuildArtifact(f), test_dir)
137+
# The first item is the binary to be used for the test
138+
if self.aout == "":
139+
self.aout = os.path.join(test_dir, f)
140+
141+
use_debuginfod = debuginfo != None or executable != None
142+
143+
# Populated the 'file://... mocked' Debuginfod server:
144+
if use_debuginfod:
145+
os.makedirs(os.path.join(self.tmp_dir, "cache"))
146+
uuid_dir = os.path.join(self.tmp_dir, "buildid", uuid)
147+
os.makedirs(uuid_dir)
148+
if debuginfo:
149+
shutil.copy(
150+
self.getBuildArtifact(debuginfo),
151+
os.path.join(uuid_dir, "debuginfo"),
152+
)
153+
if executable:
154+
shutil.copy(
155+
self.getBuildArtifact(executable),
156+
os.path.join(uuid_dir, "executable"),
157+
)
158+
159+
# Configure LLDB for the test:
160+
self.runCmd(
161+
"settings set symbols.enable-external-lookup %s"
162+
% str(use_debuginfod).lower()
163+
)
164+
self.runCmd("settings clear plugin.symbol-locator.debuginfod.server-urls")
165+
if use_debuginfod:
166+
self.runCmd(
167+
"settings set plugin.symbol-locator.debuginfod.cache-path %s/cache"
168+
% self.tmp_dir
169+
)
170+
self.runCmd(
171+
"settings insert-before plugin.symbol-locator.debuginfod.server-urls 0 file://%s"
172+
% self.tmp_dir
173+
)
174+
175+
def getUUID(self, filename):
176+
try:
177+
target = self.dbg.CreateTarget(self.getBuildArtifact(filename))
178+
module = target.GetModuleAtIndex(0)
179+
uuid = module.GetUUIDString().replace("-", "").lower()
180+
self.dbg.DeleteTarget(target)
181+
return uuid if len(uuid) == 40 else None
182+
except:
183+
return None
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This is a dump little pair of test files
2+
3+
int func(int argc, const char *argv[]) {
4+
return (argc + 1) * (argv[argc][0] + 2);
5+
}
6+
7+
int main(int argc, const char *argv[]) { return func(0, argv); }

0 commit comments

Comments
 (0)