Skip to content

Commit fc0eda9

Browse files
Kevin Freikevinfrei
authored andcommitted
Tests (w/fixes) for initial DebugInfoD LLDB integration
Summary: While adding tests for DebugInfoD integration, there were a couple issues that came up: DWP from /debuginfo for a stripped binary needs to return symbols from /executable Test Plan: Added some API tests Reviewers: gclayton, hyubo Subscribers: Tasks: T179375937 Tags: debuginfod Differential Revision: https://phabricator.intern.facebook.com/D54953389
1 parent b20360a commit fc0eda9

File tree

10 files changed

+489
-17
lines changed

10 files changed

+489
-17
lines changed

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

Lines changed: 32 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 "$(MERGE_DWOS)" "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,11 +576,25 @@ else
565576
endif
566577
else
567578
ifeq "$(SPLIT_DEBUG_SYMBOLS)" "YES"
579+
ifeq "$(SAVE_FULL_DEBUG_BINARY)" "YES"
580+
cp "$(EXE)" "$(EXE).full"
581+
endif
568582
$(OBJCOPY) --only-keep-debug "$(EXE)" "$(DSYM)"
569583
$(OBJCOPY) --strip-debug --add-gnu-debuglink="$(DSYM)" "$(EXE)" "$(EXE)"
570584
endif
585+
ifeq "$(MERGE_DWOS)" "YES"
586+
$(DWP) -o "$(DWP_NAME)" $(DWOS)
587+
endif
571588
endif
572589

590+
591+
#----------------------------------------------------------------------
592+
# Support emitting the context of the GNU build-id into a file
593+
# This needs to be used in conjunction with GEN_GNU_BUILD_ID := YES
594+
#----------------------------------------------------------------------
595+
$(EXE).uuid : $(EXE)
596+
$(OBJCOPY) --dump-section=.note.gnu.build-id=$@ $<
597+
573598
#----------------------------------------------------------------------
574599
# Make the dylib
575600
#----------------------------------------------------------------------
@@ -610,9 +635,15 @@ endif
610635
else
611636
$(LD) $(DYLIB_OBJECTS) $(LDFLAGS) -shared -o "$(DYLIB_FILENAME)"
612637
ifeq "$(SPLIT_DEBUG_SYMBOLS)" "YES"
638+
ifeq "$(SAVE_FULL_DEBUG_BINARY)" "YES"
639+
cp "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME).full"
640+
endif
613641
$(OBJCOPY) --only-keep-debug "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME).debug"
614642
$(OBJCOPY) --strip-debug --add-gnu-debuglink="$(DYLIB_FILENAME).debug" "$(DYLIB_FILENAME)" "$(DYLIB_FILENAME)"
615643
endif
644+
ifeq "$(MERGE_DWOS)" "YES"
645+
$(DWP) -o $(DYLIB_DWP_FILE) $(DYLIB_DWOS)
646+
endif
616647
endif
617648

618649
#----------------------------------------------------------------------

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

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4377,26 +4377,40 @@ const std::shared_ptr<SymbolFileDWARFDwo> &SymbolFileDWARF::GetDwpSymbolFile() {
43774377
FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths();
43784378
ModuleSpec module_spec;
43794379
module_spec.GetFileSpec() = m_objfile_sp->GetFileSpec();
4380+
FileSpec dwp_filespec;
43804381
for (const auto &symfile : symfiles.files()) {
43814382
module_spec.GetSymbolFileSpec() =
43824383
FileSpec(symfile.GetPath() + ".dwp", symfile.GetPathStyle());
43834384
LLDB_LOG(log, "Searching for DWP using: \"{0}\"",
43844385
module_spec.GetSymbolFileSpec());
4385-
FileSpec dwp_filespec =
4386+
dwp_filespec =
43864387
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
43874388
if (FileSystem::Instance().Exists(dwp_filespec)) {
4388-
LLDB_LOG(log, "Found DWP file: \"{0}\"", dwp_filespec);
4389-
DataBufferSP dwp_file_data_sp;
4390-
lldb::offset_t dwp_file_data_offset = 0;
4391-
ObjectFileSP dwp_obj_file = ObjectFile::FindPlugin(
4392-
GetObjectFile()->GetModule(), &dwp_filespec, 0,
4393-
FileSystem::Instance().GetByteSize(dwp_filespec), dwp_file_data_sp,
4394-
dwp_file_data_offset);
4395-
if (dwp_obj_file) {
4396-
m_dwp_symfile = std::make_shared<SymbolFileDWARFDwo>(
4397-
*this, dwp_obj_file, DIERef::k_file_index_mask);
4398-
break;
4399-
}
4389+
break;
4390+
}
4391+
}
4392+
if (!FileSystem::Instance().Exists(dwp_filespec)) {
4393+
LLDB_LOG(log, "No DWP file found locally");
4394+
// Fill in the UUID for the module we're trying to match for, so we can
4395+
// find the correct DWP file, as the Debuginfod plugin uses *only* this
4396+
// data to correctly match the DWP file with the binary.
4397+
module_spec.GetUUID() = m_objfile_sp->GetUUID();
4398+
dwp_filespec =
4399+
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
4400+
// Set it back so it's not outliving the m_objfile_sp shared pointer.
4401+
module_spec.GetUUID() = {};
4402+
}
4403+
if (FileSystem::Instance().Exists(dwp_filespec)) {
4404+
LLDB_LOG(log, "Found DWP file: \"{0}\"", dwp_filespec);
4405+
DataBufferSP dwp_file_data_sp;
4406+
lldb::offset_t dwp_file_data_offset = 0;
4407+
ObjectFileSP dwp_obj_file = ObjectFile::FindPlugin(
4408+
GetObjectFile()->GetModule(), &dwp_filespec, 0,
4409+
FileSystem::Instance().GetByteSize(dwp_filespec), dwp_file_data_sp,
4410+
dwp_file_data_offset);
4411+
if (dwp_obj_file) {
4412+
m_dwp_symfile = std::make_shared<SymbolFileDWARFDwo>(
4413+
*this, dwp_obj_file, DIERef::k_file_index_mask);
44004414
}
44014415
}
44024416
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: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,27 @@ 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+
if (!ObjectFileELF::classof(dwp_obj_file.get()))
57+
return false;
58+
// The presence of a debug_cu_index section is the key identifying feature of
59+
// a DWP file. Make sure we don't fill in the section list on dwp_obj_file
60+
// (by calling GetSectionList(false)) as this is invoked before we may have
61+
// all the symbol files collected and available.
62+
if (!dwp_obj_file || !dwp_obj_file->GetSectionList(false)->FindSectionByType(
63+
eSectionTypeDWARFDebugCuIndex, false))
64+
return false;
65+
return true;
66+
}
67+
4768
// CreateInstance
4869
//
4970
// Platforms can register a callback to use when creating symbol vendors to
@@ -87,8 +108,15 @@ SymbolVendorELF::CreateInstance(const lldb::ModuleSP &module_sp,
87108
FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths();
88109
FileSpec dsym_fspec =
89110
PluginManager::LocateExecutableSymbolFile(module_spec, search_paths);
90-
if (!dsym_fspec)
91-
return nullptr;
111+
if (!dsym_fspec || IsDwpSymbolFile(module_sp, dsym_fspec)) {
112+
// If we have a stripped binary or if we got a DWP file, we should prefer
113+
// symbols in the executable acquired through a plugin.
114+
ModuleSpec unstripped_spec =
115+
PluginManager::LocateExecutableObjectFile(module_spec);
116+
if (!unstripped_spec)
117+
return nullptr;
118+
dsym_fspec = unstripped_spec.GetFileSpec();
119+
}
92120

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