Author: Raphael Isemann
Date: 2026-06-16T11:47:14+01:00
New Revision: cb1821d2088c8589fd0054d1bf96b10c0665c2be

URL: 
https://github.com/llvm/llvm-project/commit/cb1821d2088c8589fd0054d1bf96b10c0665c2be
DIFF: 
https://github.com/llvm/llvm-project/commit/cb1821d2088c8589fd0054d1bf96b10c0665c2be.diff

LOG: [lldb] Avoid calling dyld's versions of libc functions (#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.

Added: 
    lldb/test/API/lang/c/libc_calls/Makefile
    lldb/test/API/lang/c/libc_calls/TestLibcCalls.py
    lldb/test/API/lang/c/libc_calls/main.c

Modified: 
    lldb/source/Expression/IRExecutionUnit.cpp

Removed: 
    


################################################################################
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;
+}


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to