Skip to content

[lldb-dap] Handle stack frames without a module #136777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 25, 2025

Conversation

eronnen
Copy link
Contributor

@eronnen eronnen commented Apr 22, 2025

  • Fix error in lldb-dap when the stack trace contains a frame without a module by simply showing the first 32 assembly instructions after the PC
  • Adds a test with a simple example that triggers this case

@llvmbot
Copy link
Member

llvmbot commented Apr 22, 2025

@llvm/pr-subscribers-lldb

Author: Ely Ronnen (eronnen)

Changes
  • Fix error in lldb-dap when the stack trace contains a frame without a module by simply showing the first 32 assembly instructions after the PC

Full diff: https://github.com/llvm/llvm-project/pull/136777.diff

5 Files Affected:

  • (added) lldb/test/API/tools/lldb-dap/stackTraceMissingModule/Makefile (+2)
  • (added) lldb/test/API/tools/lldb-dap/stackTraceMissingModule/TestDAP_stackTraceMissingModule.py (+54)
  • (added) lldb/test/API/tools/lldb-dap/stackTraceMissingModule/main.c (+37)
  • (modified) lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp (+12-2)
  • (modified) lldb/tools/lldb-dap/JSONUtils.cpp (+10)
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/Makefile b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/Makefile
new file mode 100644
index 0000000000000..c9319d6e6888a
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/TestDAP_stackTraceMissingModule.py b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/TestDAP_stackTraceMissingModule.py
new file mode 100644
index 0000000000000..1eb00f9935a22
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/TestDAP_stackTraceMissingModule.py
@@ -0,0 +1,54 @@
+"""
+Test lldb-dap stack trace when module is missing
+"""
+
+from lldbsuite.test.decorators import skipUnlessPlatform
+from lldbsuite.test.lldbtest import line_number
+import lldbdap_testcase
+import re
+
+
+class TestDAP_stackTraceMissingModule(lldbdap_testcase.DAPTestCaseBase):
+    @skipUnlessPlatform(["linux"])
+    def test_missingModule(self):
+        """
+        Test that the stack frame without a module still has assembly source.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, commandEscapePrefix="")
+
+        source = "main.c"
+        self.set_source_breakpoints(
+            source,
+            [line_number(source, "// Break here")],
+        )
+        self.continue_to_next_stop()
+
+        # Evaluate expr -- func
+        expr_result = self.dap_server.request_evaluate(
+            expression="expr -f pointer -- func",
+            context="repl",
+        )
+
+        expr_result_address = re.search(
+            r"0x[0-9a-fA-F]+", expr_result["body"]["result"]
+        )
+        self.assertIsNotNone(
+            expr_result_address, "Failed to get address of dynamic allocated func"
+        )
+        func_address = expr_result_address.group(0)
+
+        self.dap_server.request_evaluate(
+            expression=f"breakpoint set --address {func_address}",
+            context="repl",
+        )
+
+        self.continue_to_next_stop()
+
+        frame_without_module = self.get_stackFrames()[0]
+
+        self.assertIn("line", frame_without_module, "Line number missing.")
+        self.assertIn("column", frame_without_module, "Column number missing.")
+        self.assertIn("source", frame_without_module, "Source location missing.")
+        source = frame_without_module["source"]
+        self.assertIn("sourceReference", source, "Source reference missing.")
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/main.c b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/main.c
new file mode 100644
index 0000000000000..d706231fbd5c2
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceMissingModule/main.c
@@ -0,0 +1,37 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+extern uint8_t __start_target_section[];
+extern uint8_t __stop_target_section[];
+
+__attribute__((used, section("target_section"))) int target_function(void) {
+  return 42;
+}
+
+typedef int (*target_function_t)(void);
+
+int main(void) {
+  size_t target_function_size = __stop_target_section - __start_target_section;
+  size_t page_size = sysconf(_SC_PAGESIZE);
+  size_t page_aligned_size =
+      (target_function_size + page_size - 1) & ~(page_size - 1);
+
+  void *executable_memory =
+      mmap(NULL, page_aligned_size, PROT_READ | PROT_WRITE | PROT_EXEC,
+           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (executable_memory == MAP_FAILED) {
+    perror("mmap");
+    return 1;
+  }
+
+  memcpy(executable_memory, __start_target_section, target_function_size);
+
+  target_function_t func = (target_function_t)executable_memory;
+  int result = func(); // Break here
+  printf("Result from target function: %d\n", result);
+
+  return 0;
+}
diff --git a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
index 1a7a13d9f267a..99c3692f15283 100644
--- a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp
@@ -15,6 +15,7 @@
 #include "lldb/API/SBInstructionList.h"
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBStream.h"
+#include "lldb/API/SBSymbol.h"
 #include "lldb/API/SBTarget.h"
 #include "lldb/API/SBThread.h"
 #include "llvm/Support/Error.h"
@@ -41,9 +42,18 @@ SourceRequestHandler::Run(const protocol::SourceArguments &args) const {
   if (!frame.IsValid())
     return llvm::make_error<DAPError>("source not found");
 
-  lldb::SBInstructionList insts = frame.GetSymbol().GetInstructions(dap.target);
   lldb::SBStream stream;
-  insts.GetDescription(stream);
+  lldb::SBSymbol symbol = frame.GetSymbol();
+
+  if (symbol.IsValid()) {
+    lldb::SBInstructionList insts = symbol.GetInstructions(dap.target);
+    insts.GetDescription(stream);
+  } else {
+    // No valid symbol, just return the disassembly
+    lldb::SBInstructionList insts =
+        dap.target.ReadInstructions(frame.GetPCAddress(), 32);
+    insts.GetDescription(stream);
+  }
 
   return protocol::SourceResponseBody{/*content=*/stream.GetData(),
                                       /*mimeType=*/"text/x-lldb.disassembly"};
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 33f10c93d2ada..30d277abce11e 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -783,6 +783,16 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
     // Line numbers are 1-based.
     object.try_emplace("line", inst_line + 1);
     object.try_emplace("column", 1);
+  } else {
+    // No valid line entry or symbol
+    llvm::json::Object source;
+    EmplaceSafeString(source, "name", frame_name);
+    source.try_emplace("sourceReference", MakeDAPFrameID(frame));
+    EmplaceSafeString(source, "presentationHint", "deemphasize");
+    object.try_emplace("source", std::move(source));
+
+    object.try_emplace("line", 1);
+    object.try_emplace("column", 1);
   }
 
   const auto pc = frame.GetPC();

@eronnen eronnen force-pushed the lldb-dap-handle-frames-without-module branch from 93764e3 to e513345 Compare April 23, 2025 23:49
Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test. LGTM modulo comments.

lldb::SBInstructionList insts = symbol.GetInstructions(dap.target);
insts.GetDescription(stream, exe_ctx);
} else {
// No valid symbol, just return the disassembly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// No valid symbol, just return the disassembly
// No valid symbol, just return the disassembly.

When writing comments, write them as English prose, using proper capitalization, punctuation, etc.

@@ -783,6 +783,16 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
// Line numbers are 1-based.
object.try_emplace("line", inst_line + 1);
object.try_emplace("column", 1);
} else {
// No valid line entry or symbol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// No valid line entry or symbol
// No valid line entry or symbol.

Comment on lines +794 to +795
object.try_emplace("line", 1);
object.try_emplace("column", 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
object.try_emplace("line", 1);
object.try_emplace("column", 1);
object.try_emplace("line", LLDB_INVALID_LINE_NUMBER);
object.try_emplace("column", LLDB_INVALID_LINE_NUMBER);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it still needs to be 1, this way it will be possible to debug the code in the IDE:

Screencast.From.2025-04-24.08-48-53.webm

The only annoying thing is that each step removes the previous assembly lines, but I couldn't think of a straightforward way to tackle it because the frame changes too in every step

@JDevlieghere JDevlieghere merged commit 51dc0cc into llvm:main Apr 25, 2025
10 checks passed
jyli0116 pushed a commit to jyli0116/llvm-project that referenced this pull request Apr 28, 2025
* Fix error in lldb-dap when the stack trace contains a frame without a
module by simply showing the first 32 assembly instructions after the PC.
* Adds a test with a simple example that triggers this case.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
* Fix error in lldb-dap when the stack trace contains a frame without a
module by simply showing the first 32 assembly instructions after the PC.
* Adds a test with a simple example that triggers this case.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
* Fix error in lldb-dap when the stack trace contains a frame without a
module by simply showing the first 32 assembly instructions after the PC.
* Adds a test with a simple example that triggers this case.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
* Fix error in lldb-dap when the stack trace contains a frame without a
module by simply showing the first 32 assembly instructions after the PC.
* Adds a test with a simple example that triggers this case.
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
* Fix error in lldb-dap when the stack trace contains a frame without a
module by simply showing the first 32 assembly instructions after the PC.
* Adds a test with a simple example that triggers this case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants