@walter-erquinigo found the the [PR with testing and a fix for DebugInfoD](https://github.com/llvm/llvm-project/pull/98344) caused an issue when working with stripped binaries. The issue is that when you're working with split-dwarf, there are *3* possible files: The stripped binary the user is debugging, the "only-keep-debug" *or* unstripped binary, plus the `.dwp` file. The debuginfod plugin should provide the unstripped/OKD binary. However, if the debuginfod plugin fails, the default symbol locator plugin will just return the stripped binary, which doesn't help. So, to address that, the SymbolVendorELF code checks to see if the SymbolLocator's ExecutableObjectFile request returned the same file, and bails if that's the case. You can see the specific diff as the second commit in the PR. I'm investigating adding a test: I can't quite get a simple repro, and I'm unwilling to make any additional changes to Makefile.rules to this diff, for Pavlovian reasons.
197 lines
7.3 KiB
Python
197 lines
7.3 KiB
Python
"""
|
|
Test support for the DebugInfoD network symbol acquisition protocol.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
import lldbsuite.test.lldbutil as lldbutil
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
|
|
"""
|
|
Test support for the DebugInfoD network symbol acquisition protocol.
|
|
This file is for split-dwarf (dwp) scenarios.
|
|
|
|
1 - A split binary target with it's corresponding DWP file
|
|
2 - A stripped, split binary target with an unstripped binary and a DWP file
|
|
3 - A stripped, split binary target with an --only-keep-debug symbols file and a DWP file
|
|
"""
|
|
|
|
|
|
class DebugInfodDWPTests(TestBase):
|
|
# No need to try every flavor of debug inf.
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_normal_stripped(self):
|
|
"""
|
|
Validate behavior with a stripped binary, no symbols or symbol locator.
|
|
"""
|
|
self.config_test(["a.out"])
|
|
self.try_breakpoint(False)
|
|
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_normal_stripped_split_with_dwp(self):
|
|
"""
|
|
Validate behavior with symbols, but no symbol locator.
|
|
"""
|
|
self.config_test(["a.out", "a.out.debug", "a.out.dwp"])
|
|
self.try_breakpoint(True)
|
|
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_normal_stripped_only_dwp(self):
|
|
"""
|
|
Validate behavior *with* dwp symbols only, but missing other symbols,
|
|
but no symbol locator. This shouldn't work: without the other symbols
|
|
DWO's appear mostly useless.
|
|
"""
|
|
self.config_test(["a.out", "a.out.dwp"])
|
|
self.try_breakpoint(False)
|
|
|
|
@skipIfCurlSupportMissing
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_debuginfod_dwp_from_service(self):
|
|
"""
|
|
Test behavior with the unstripped binary, and DWP from the service.
|
|
"""
|
|
self.config_test(["a.out.debug"], "a.out.dwp")
|
|
self.try_breakpoint(True)
|
|
|
|
@skipIfCurlSupportMissing
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_debuginfod_both_symfiles_from_service(self):
|
|
"""
|
|
Test behavior with a stripped binary, with the unstripped binary and
|
|
dwp symbols from Debuginfod.
|
|
"""
|
|
self.config_test(["a.out"], "a.out.dwp", "a.out.unstripped")
|
|
self.try_breakpoint(True)
|
|
|
|
@skipIfCurlSupportMissing
|
|
@skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
|
|
def test_debuginfod_both_okd_symfiles_from_service(self):
|
|
"""
|
|
Test behavior with both the only-keep-debug symbols and the dwp symbols
|
|
from Debuginfod.
|
|
"""
|
|
self.config_test(["a.out"], "a.out.dwp", "a.out.debug")
|
|
self.try_breakpoint(True)
|
|
|
|
def try_breakpoint(self, should_have_loc):
|
|
"""
|
|
This function creates a target from self.aout, sets a function-name
|
|
breakpoint, and checks to see if we have a file/line location,
|
|
as a way to validate that the symbols have been loaded.
|
|
should_have_loc specifies if we're testing that symbols have or
|
|
haven't been loaded.
|
|
"""
|
|
target = self.dbg.CreateTarget(self.aout)
|
|
self.assertTrue(target and target.IsValid(), "Target is valid")
|
|
|
|
bp = target.BreakpointCreateByName("func")
|
|
self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid")
|
|
self.assertEqual(bp.GetNumLocations(), 1)
|
|
|
|
loc = bp.GetLocationAtIndex(0)
|
|
self.assertTrue(loc and loc.IsValid(), "Location is valid")
|
|
addr = loc.GetAddress()
|
|
self.assertTrue(addr and addr.IsValid(), "Loc address is valid")
|
|
line_entry = addr.GetLineEntry()
|
|
self.assertEqual(
|
|
should_have_loc,
|
|
line_entry != None and line_entry.IsValid(),
|
|
"Loc line entry is valid",
|
|
)
|
|
if should_have_loc:
|
|
self.assertEqual(line_entry.GetLine(), 4)
|
|
self.assertEqual(
|
|
line_entry.GetFileSpec().GetFilename(),
|
|
self.main_source_file.GetFilename(),
|
|
)
|
|
self.dbg.DeleteTarget(target)
|
|
shutil.rmtree(self.tmp_dir)
|
|
|
|
def config_test(self, local_files, debuginfo=None, executable=None):
|
|
"""
|
|
Set up a test with local_files[] copied to a different location
|
|
so that we control which files are, or are not, found in the file system.
|
|
Also, create a stand-alone file-system 'hosted' debuginfod server with the
|
|
provided debuginfo and executable files (if they exist)
|
|
|
|
Make the filesystem look like:
|
|
|
|
/tmp/<tmpdir>/test/[local_files]
|
|
|
|
/tmp/<tmpdir>/cache (for lldb to use as a temp cache)
|
|
|
|
/tmp/<tmpdir>/buildid/<uuid>/executable -> <executable>
|
|
/tmp/<tmpdir>/buildid/<uuid>/debuginfo -> <debuginfo>
|
|
Returns the /tmp/<tmpdir> path
|
|
"""
|
|
|
|
self.build()
|
|
|
|
uuid = self.getUUID("a.out")
|
|
if not uuid:
|
|
self.fail("Could not get UUID for a.out")
|
|
return
|
|
self.main_source_file = lldb.SBFileSpec("main.c")
|
|
self.tmp_dir = tempfile.mkdtemp()
|
|
self.test_dir = os.path.join(self.tmp_dir, "test")
|
|
os.makedirs(self.test_dir)
|
|
|
|
self.aout = ""
|
|
# Copy the files used by the test:
|
|
for f in local_files:
|
|
shutil.copy(self.getBuildArtifact(f), self.test_dir)
|
|
if self.aout == "":
|
|
self.aout = os.path.join(self.test_dir, f)
|
|
|
|
use_debuginfod = debuginfo != None or executable != None
|
|
|
|
# Populated the 'file://... mocked' Debuginfod server:
|
|
if use_debuginfod:
|
|
os.makedirs(os.path.join(self.tmp_dir, "cache"))
|
|
uuid_dir = os.path.join(self.tmp_dir, "buildid", uuid)
|
|
os.makedirs(uuid_dir)
|
|
if debuginfo:
|
|
shutil.copy(
|
|
self.getBuildArtifact(debuginfo),
|
|
os.path.join(uuid_dir, "debuginfo"),
|
|
)
|
|
if executable:
|
|
shutil.copy(
|
|
self.getBuildArtifact(executable),
|
|
os.path.join(uuid_dir, "executable"),
|
|
)
|
|
os.remove(self.getBuildArtifact("main.dwo"))
|
|
# Configure LLDB for the test:
|
|
self.runCmd(
|
|
"settings set symbols.enable-external-lookup %s"
|
|
% str(use_debuginfod).lower()
|
|
)
|
|
self.runCmd("settings clear plugin.symbol-locator.debuginfod.server-urls")
|
|
if use_debuginfod:
|
|
self.runCmd(
|
|
"settings set plugin.symbol-locator.debuginfod.cache-path %s/cache"
|
|
% self.tmp_dir
|
|
)
|
|
self.runCmd(
|
|
"settings insert-before plugin.symbol-locator.debuginfod.server-urls 0 file://%s"
|
|
% self.tmp_dir
|
|
)
|
|
|
|
def getUUID(self, filename):
|
|
try:
|
|
spec = lldb.SBModuleSpec()
|
|
spec.SetFileSpec(lldb.SBFileSpec(self.getBuildArtifact(filename)))
|
|
module = lldb.SBModule(spec)
|
|
uuid = module.GetUUIDString().replace("-", "").lower()
|
|
# Don't want lldb's fake 32 bit CRC's for this one
|
|
return uuid if len(uuid) > 8 else None
|
|
except:
|
|
return None
|