[Lldb-commits] [lldb] [lldb] Avoid calling dyld's versions of libc functions (PR #201829)

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Fri Jun 5 05:42:29 PDT 2026


https://github.com/Teemperor created https://github.com/llvm/llvm-project/pull/201829

dyld ships with its own version of various libc functions that we are not supposed to call. This patch prevents the expression evaluator from calling them by respecting the existing list of forbidden modules.

>From 20af63f91758c18e92f3b08dd38ef7d924010d89 Mon Sep 17 00:00:00 2001
From: Raphael Isemann <rise at apple.com>
Date: Fri, 5 Jun 2026 11:24:38 +0100
Subject: [PATCH] [lldb] Avoid calling dyld's versions of libc functions

dyld ships with its own version of various libc functions that we are
not supposed to call. This patch prevents the expression evaluator from
calling them by respecting the existing list of forbidden modules.
---
 lldb/source/Expression/IRExecutionUnit.cpp    |  8 ++
 lldb/test/API/lang/c/libc_calls/Makefile      |  3 +
 .../API/lang/c/libc_calls/TestLibcCalls.py    | 75 +++++++++++++++++++
 lldb/test/API/lang/c/libc_calls/main.c        | 11 +++
 4 files changed, 97 insertions(+)
 create mode 100644 lldb/test/API/lang/c/libc_calls/Makefile
 create mode 100644 lldb/test/API/lang/c/libc_calls/TestLibcCalls.py
 create mode 100644 lldb/test/API/lang/c/libc_calls/main.c

diff --git a/lldb/source/Expression/IRExecutionUnit.cpp b/lldb/source/Expression/IRExecutionUnit.cpp
index be08c18b611e9..49edc2cdc5462 100644
--- a/lldb/source/Expression/IRExecutionUnit.cpp
+++ b/lldb/source/Expression/IRExecutionUnit.cpp
@@ -778,6 +778,14 @@ IRExecutionUnit::FindInSymbols(const std::vector<ConstString> &names,
   for (size_t i = 0; i < m_preferred_modules.GetSize(); ++i)
     non_local_images.Remove(m_preferred_modules.GetModuleAtIndex(i));
 
+  // Drop modules the platform considers off-limits to unconstrained symbol
+  // searches.
+  for (size_t i = non_local_images.GetSize(); i > 0; --i) {
+    lldb::ModuleSP module_sp = non_local_images.GetModuleAtIndex(i - 1);
+    if (target->ModuleIsExcludedForUnconstrainedSearches(module_sp))
+      non_local_images.Remove(module_sp);
+  }
+
   LoadAddressResolver resolver(*target, symbol_was_missing_weak);
 
   ModuleFunctionSearchOptions function_options;
diff --git a/lldb/test/API/lang/c/libc_calls/Makefile b/lldb/test/API/lang/c/libc_calls/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/lang/c/libc_calls/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/c/libc_calls/TestLibcCalls.py b/lldb/test/API/lang/c/libc_calls/TestLibcCalls.py
new file mode 100644
index 0000000000000..f25f70b7adb03
--- /dev/null
+++ b/lldb/test/API/lang/c/libc_calls/TestLibcCalls.py
@@ -0,0 +1,75 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+# (symbol, function-pointer type) for each libc function in the test.
+LIBC_FUNCTIONS = [
+    ("memset", "void *(*)(void *, int, unsigned long)"),
+    ("memcpy", "void *(*)(void *, const void *, unsigned long)"),
+    ("memmove", "void *(*)(void *, const void *, unsigned long)"),
+    ("memcmp", "int (*)(const void *, const void *, unsigned long)"),
+]
+
+
+class LibcCallsTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessDarwin
+    @skipIfRemote
+    def test_libc_funcs_do_not_resolve_to_dyld(self):
+        """The JIT-resolved address of each libc function must not lie in
+        the private dyld copies of the same name. Calling these functions from
+        outside of dyld is not supported."""
+
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+
+        # Check that dyld is actually loaded.
+        loaded_module_names = [
+            m.GetFileSpec().GetFilename() for m in self.target().module_iter()
+        ]
+        self.assertIn("dyld", loaded_module_names, "dyld must be loaded")
+
+        for symbol, fn_type in LIBC_FUNCTIONS:
+            with self.subTest(symbol=symbol):
+                # Casting `<symbol>` through the spelled-out function-pointer
+                # type gives clang a function type to bind to; the cast
+                # result is the JIT-resolved address baked in by FindSymbol.
+                result = self.frame().EvaluateExpression(f"(void *)({fn_type}){symbol}")
+                self.assertSuccess(result.GetError())
+                resolved_addr = result.GetValueAsUnsigned()
+                self.assertNotEqual(resolved_addr, 0)
+
+                resolved_module_name = (
+                    self.target()
+                    .ResolveLoadAddress(resolved_addr)
+                    .GetModule()
+                    .GetFileSpec()
+                    .GetFilename()
+                )
+                self.assertNotEqual(
+                    resolved_module_name, "dyld", f"Called dyld version of {symbol}"
+                )
+
+    def test_libc_calls_succeed(self):
+        """Calling memset/memcpy/memmove/memcmp from expressions must
+        execute without trapping."""
+
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+
+        # Call libc functions should work without trapping.
+        # We use a non-constant length so the compiler is not tempted to lower
+        # this to a series of read/writes.
+        self.expect_expr(
+            """__builtin_memcpy(dst, src, len64);
+            __builtin_memmove(dst, src, len64);
+            __builtin_memset(dst, 0, len64);
+            __builtin_memcmp(src, dst, len64)""",
+            result_value="0",
+        )
diff --git a/lldb/test/API/lang/c/libc_calls/main.c b/lldb/test/API/lang/c/libc_calls/main.c
new file mode 100644
index 0000000000000..be013dc14f8eb
--- /dev/null
+++ b/lldb/test/API/lang/c/libc_calls/main.c
@@ -0,0 +1,11 @@
+#include "stdio.h"
+
+char src[64];
+char dst[64];
+unsigned len64 = 64;
+
+int main(int argc, char **argv) {
+  // Call a libc function so that we know there is a real libc in our process.
+  puts("s"); // break here
+  return 0;
+}



More information about the lldb-commits mailing list