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 one is for simple / no split-dwarf scenarios. For no-split-dwarf scenarios, there are 2 variations: 1 - A stripped binary with it's corresponding unstripped binary: 2 - A stripped binary with a corresponding --only-keep-debug symbols file """ class DebugInfodTests(TestBase): # No need to try every flavor of debug inf. NO_DEBUG_INFO_TESTCASE = True @skipUnlessPlatform(["linux", "freebsd"]) def test_normal_no_symbols(self): """ Validate behavior with no symbols or symbol locator. ('baseline negative' behavior) """ test_root = self.config_test(["a.out"]) self.try_breakpoint(False) @skipUnlessPlatform(["linux", "freebsd"]) def test_normal_default(self): """ Validate behavior with symbols, but no symbol locator. ('baseline positive' behavior) """ test_root = self.config_test(["a.out", "a.out.debug"]) self.try_breakpoint(True) @skipIfCurlSupportMissing @skipUnlessPlatform(["linux", "freebsd"]) def test_debuginfod_symbols(self): """ Test behavior with the full binary available from Debuginfod as 'debuginfo' from the plug-in. """ test_root = self.config_test(["a.out"], "a.out.unstripped") self.try_breakpoint(True) @skipIfCurlSupportMissing @skipUnlessPlatform(["linux", "freebsd"]) def test_debuginfod_executable(self): """ Test behavior with the full binary available from Debuginfod as 'executable' from the plug-in. """ test_root = self.config_test(["a.out"], None, "a.out.unstripped") self.try_breakpoint(True) @skipIfCurlSupportMissing @skipUnlessPlatform(["linux", "freebsd"]) def test_debuginfod_okd_symbols(self): """ Test behavior with the 'only-keep-debug' symbols available from Debuginfod. """ test_root = self.config_test(["a.out"], "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//test/[local_files] /tmp//cache (for lldb to use as a temp cache) /tmp//buildid//executable -> /tmp//buildid//debuginfo -> Returns the /tmp/ 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() test_dir = os.path.join(self.tmp_dir, "test") os.makedirs(test_dir) self.aout = "" # Copy the files used by the test: for f in local_files: shutil.copy(self.getBuildArtifact(f), test_dir) # The first item is the binary to be used for the test if self.aout == "": self.aout = os.path.join(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"), ) # 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