[lldb] Add --stack option to `target symbols add` command
authorJonas Devlieghere <jonas@devlieghere.com>
Wed, 22 Sep 2021 05:08:48 +0000 (22:08 -0700)
committerJonas Devlieghere <jonas@devlieghere.com>
Wed, 22 Sep 2021 06:08:14 +0000 (23:08 -0700)
Currently you can ask the target symbols add command to locate the debug
symbols for the current frame. This patch add an options to do that for
the whole call stack.

Differential revision: https://reviews.llvm.org/D110011

lldb/source/Commands/CommandObjectTarget.cpp
lldb/test/API/macosx/add-dsym/TestAddDsymDownload.py [new file with mode: 0644]

index 92aa8fc..439ac45 100644 (file)
@@ -3962,8 +3962,12 @@ public:
             "name."),
         m_current_frame_option(
             LLDB_OPT_SET_2, false, "frame", 'F',
-            "Locate the debug symbols for the currently selected frame.",
-            false, true)
+            "Locate the debug symbols for the currently selected frame.", false,
+            true),
+        m_current_stack_option(LLDB_OPT_SET_2, false, "stack", 'S',
+                               "Locate the debug symbols for every frame in "
+                               "the current call stack.",
+                               false, true)
 
   {
     m_option_group.Append(&m_uuid_option_group, LLDB_OPT_SET_ALL,
@@ -3971,6 +3975,8 @@ public:
     m_option_group.Append(&m_file_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
     m_option_group.Append(&m_current_frame_option, LLDB_OPT_SET_2,
                           LLDB_OPT_SET_2);
+    m_option_group.Append(&m_current_stack_option, LLDB_OPT_SET_2,
+                          LLDB_OPT_SET_2);
     m_option_group.Finalize();
   }
 
@@ -4247,6 +4253,63 @@ protected:
     return true;
   }
 
+  bool AddSymbolsForStack(CommandReturnObject &result, bool &flush) {
+    assert(m_current_stack_option.GetOptionValue().OptionWasSet());
+
+    Process *process = m_exe_ctx.GetProcessPtr();
+    if (!process) {
+      result.AppendError(
+          "a process must exist in order to use the --stack option");
+      return false;
+    }
+
+    const StateType process_state = process->GetState();
+    if (!StateIsStoppedState(process_state, true)) {
+      result.AppendErrorWithFormat("process is not stopped: %s",
+                                   StateAsCString(process_state));
+      return false;
+    }
+
+    Thread *thread = m_exe_ctx.GetThreadPtr();
+    if (!thread) {
+      result.AppendError("invalid current thread");
+      return false;
+    }
+
+    bool symbols_found = false;
+    uint32_t frame_count = thread->GetStackFrameCount();
+    for (uint32_t i = 0; i < frame_count; ++i) {
+      lldb::StackFrameSP frame_sp = thread->GetStackFrameAtIndex(i);
+
+      ModuleSP frame_module_sp(
+          frame_sp->GetSymbolContext(eSymbolContextModule).module_sp);
+      if (!frame_module_sp)
+        continue;
+
+      ModuleSpec module_spec;
+      module_spec.GetUUID() = frame_module_sp->GetUUID();
+
+      if (FileSystem::Instance().Exists(
+              frame_module_sp->GetPlatformFileSpec())) {
+        module_spec.GetArchitecture() = frame_module_sp->GetArchitecture();
+        module_spec.GetFileSpec() = frame_module_sp->GetPlatformFileSpec();
+      }
+
+      bool current_frame_flush = false;
+      if (DownloadObjectAndSymbolFile(module_spec, result, current_frame_flush))
+        symbols_found = true;
+      flush |= current_frame_flush;
+    }
+
+    if (!symbols_found) {
+      result.AppendError(
+          "unable to find debug symbols in the current call stack");
+      return false;
+    }
+
+    return true;
+  }
+
   bool DoExecute(Args &args, CommandReturnObject &result) override {
     Target *target = m_exe_ctx.GetTargetPtr();
     result.SetStatus(eReturnStatusFailed);
@@ -4257,6 +4320,8 @@ protected:
     const bool file_option_set = m_file_option.GetOptionValue().OptionWasSet();
     const bool frame_option_set =
         m_current_frame_option.GetOptionValue().OptionWasSet();
+    const bool stack_option_set =
+        m_current_stack_option.GetOptionValue().OptionWasSet();
     const size_t argc = args.GetArgumentCount();
 
     if (argc == 0) {
@@ -4266,6 +4331,8 @@ protected:
         AddSymbolsForFile(result, flush);
       else if (frame_option_set)
         AddSymbolsForFrame(result, flush);
+      else if (stack_option_set)
+        AddSymbolsForStack(result, flush);
       else
         result.AppendError("one or more symbol file paths must be specified, "
                            "or options must be specified");
@@ -4335,6 +4402,7 @@ protected:
   OptionGroupUUID m_uuid_option_group;
   OptionGroupFile m_file_option;
   OptionGroupBoolean m_current_frame_option;
+  OptionGroupBoolean m_current_stack_option;
 };
 
 #pragma mark CommandObjectTargetSymbols
diff --git a/lldb/test/API/macosx/add-dsym/TestAddDsymDownload.py b/lldb/test/API/macosx/add-dsym/TestAddDsymDownload.py
new file mode 100644 (file)
index 0000000..c83d923
--- /dev/null
@@ -0,0 +1,98 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+@skipUnlessDarwin
+class AddDsymDownload(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+    dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
+
+    def get_uuid(self):
+        dwarfdump_cmd_output = subprocess.check_output(
+            ('/usr/bin/dwarfdump --uuid "%s"' % self.exe),
+            shell=True).decode("utf-8")
+        for line in dwarfdump_cmd_output.splitlines():
+            match = self.dwarfdump_uuid_regex.search(line)
+            if match:
+                return match.group(1)
+        return None
+
+    def create_dsym_for_uuid(self):
+        shell_cmds = [
+            '#! /bin/sh', '# the last argument is the uuid',
+            'while [ $# -gt 1 ]', 'do', '  shift', 'done', 'ret=0',
+            'echo "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>"',
+            'echo "<!DOCTYPE plist PUBLIC \\"-//Apple//DTD PLIST 1.0//EN\\" \\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\">"',
+            'echo "<plist version=\\"1.0\\">"', '',
+            'if [ "$1" != "%s" ]' % (self.uuid), 'then',
+            '  echo "<key>DBGError</key><string>not found</string>"',
+            '  echo "</plist>"', '  exit 1', 'fi',
+            '  uuid=%s' % self.uuid,
+            '  bin=%s' % self.exe,
+            '  dsym=%s' % self.dsym, 'echo "<dict><key>$uuid</key><dict>"', '',
+            'echo "<key>DBGDSYMPath</key><string>$dsym</string>"',
+            'echo "<key>DBGSymbolRichExecutable</key><string>$bin</string>"',
+            'echo "</dict></dict></plist>"', 'exit $ret'
+        ]
+
+        with open(self.dsym_for_uuid, "w") as writer:
+            for l in shell_cmds:
+                writer.write(l + '\n')
+
+        os.chmod(self.dsym_for_uuid, 0o755)
+
+    def setUp(self):
+        TestBase.setUp(self)
+        self.source = 'main.c'
+        self.exe = self.getBuildArtifact("a.out")
+        self.dsym = os.path.join(
+            self.getBuildDir(),
+            "hide.app/Contents/a.out.dSYM/Contents/Resources/DWARF/",
+            os.path.basename(self.exe))
+        self.dsym_for_uuid = self.getBuildArtifact("dsym-for-uuid.sh")
+
+        self.buildDefault(dictionary={'MAKE_DSYM': 'YES'})
+        self.assertTrue(os.path.exists(self.exe))
+        self.assertTrue(os.path.exists(self.dsym))
+
+        self.uuid = self.get_uuid()
+        self.assertNotEqual(self.uuid, None, "Could not get uuid for a.out")
+
+        self.create_dsym_for_uuid()
+
+        os.environ['LLDB_APPLE_DSYMFORUUID_EXECUTABLE'] = self.dsym_for_uuid
+        self.addTearDownHook(
+            lambda: os.environ.pop('LLDB_APPLE_DSYMFORUUID_EXECUTABLE', None))
+
+    def do_test(self, command):
+        self.target = self.dbg.CreateTarget(self.exe)
+        self.assertTrue(self.target, VALID_TARGET)
+
+        main_bp = self.target.BreakpointCreateByName("main", "a.out")
+        self.assertTrue(main_bp, VALID_BREAKPOINT)
+
+        self.process = self.target.LaunchSimple(
+            None, None, self.get_process_working_directory())
+        self.assertTrue(self.process, PROCESS_IS_VALID)
+
+        # The stop reason of the thread should be breakpoint.
+        self.assertEquals(self.process.GetState(), lldb.eStateStopped,
+                          STOPPED_DUE_TO_BREAKPOINT)
+
+        self.runCmd(command)
+        self.expect("frame select", substrs=['a.out`main at main.c'])
+
+    @no_debug_info_test
+    def test_frame(self):
+        self.do_test("add-dsym --frame")
+
+    @no_debug_info_test
+    def test_uuid(self):
+        self.do_test("add-dsym --uuid {}".format(self.uuid))
+
+    @no_debug_info_test
+    def test_stack(self):
+        self.do_test("add-dsym --stack")