[Lldb-commits] [lldb] [lldb] Replay the cached error from GetLoadImageUtilityFunction (PR #226986)
Yao Qi via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 30 07:59:43 PDT 2026
https://github.com/qiyao updated https://github.com/llvm/llvm-project/pull/226986
>From 6c0f3d15c89bca38c1bd65869e07bcafb58fd0bf Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Sat, 26 Sep 2026 08:41:37 +0100
Subject: [PATCH 1/4] [lldb] Replay the cached error from
GetLoadImageUtilityFunction
`Process::LoadImage` needs a live process to load a library: on POSIX
it makes the C library's `dlopen` run inside the debuggee, and on
Windows it makes `LoadLibraryExW` run there, both through a
JIT-compiled function call that needs a process to run the call in. On
a core file (or, on Windows, a saved minidump) there is no such
process, so the load fails every time. POSIX and Windows each lose the
resulting error message, but for two different reasons.
On POSIX, only the first failure is reported correctly. Load a corefile,
and load two libraries that do not exist shows this:
```
(lldb) process load /tmp/does-not-exist-1.dylib
error: failed to load '/tmp/does-not-exist-1.dylib': dlopen error: could not make function caller: Can't make a function caller without a process.
(lldb) process load /tmp/does-not-exist-2.dylib
error: failed to load '/tmp/does-not-exist-2.dylib': (null)
```
`Process::GetLoadImageUtilityFunction` builds the `UtilityFunction`
that performs the `dlopen` call, and caches it in the `Process`
The cache is a `llvm::call_once`: only the first caller's factory
function actually runs, and every later caller just gets the same
cached pointer back, whether or not the first attempt succeeded.
Each caller had been passing its own local `Status &error`,
populated only when its own factory ran, so any caller after
the first got a `Status` nobody had written to.
On Windows the same missing-process failure loses its message on the
very first call, not just later ones. Loading a saved minidump and
calling `process load` twice shows this:
```
(lldb) process load /tmp/does-not-exist-1.dll
error: failed to load '/tmp/does-not-exist-1.dll': (null)
(lldb) process load /tmp/does-not-exist-2.dll
error: failed to load '/tmp/does-not-exist-2.dll': (null)
```
`process load` decides success or failure from the returned image
token, so it prints "failed to load" here at all; an SB API caller that
checks `error.Fail()` directly sees a false success on both calls.
This is a different bug from the POSIX one above: it is not about the
cache replaying a stale result, it is that `PlatformWindows::DoLoadImage`
never populated its `error` output parameter in the first place. Its
factory wrote the failure into a local `Status status` that nothing
ever read, instead of into the function's own `error` parameter.
Give the `Process` its own `Status` member alongside the cached
`UtilityFunction`, and have `GetLoadImageUtilityFunction` take an
`error` output parameter. The one factory call that runs stores its
result in the member; every call, whether or not it ran the factory,
copies the member back out to its caller. `Status` cannot be copied
directly because it wraps a move-only `llvm::Error`, so this uses
`Status::Clone()`, the pattern already used elsewhere in lldb to stash
an error and hand copies of it to more than one reader.
`DWARFUnit` uses the same technique to cache a load
failure: `ExtractUnitDIEIfNeeded` runs its loading step once per
unit, guarded by the member `m_has_parsed_non_skeleton_unit`, and
stores any failure in the member `m_dwo_error` via `SetDwoError`.
`SymbolFileDWARF::CalculateFrameVariableError` later reads it back
with `GetDwoError()` and clones it for its own caller.
Fix `PlatformWindows::DoLoadImage` the same way, by routing its
factory through the function's `error` parameter instead of the local
`status` that nothing read.
Add a test that saves a core file, loads it back, and calls
`SBProcess::LoadImage` twice with a path that cannot be opened. Both
calls must fail, with the same non-empty message. Add a second test,
using a checked-in Windows minidump in place of a core file, that
does the same thing against `PlatformWindows::DoLoadImage`.
---
lldb/include/lldb/Target/Process.h | 10 +++-
.../Plugins/Platform/POSIX/PlatformPOSIX.cpp | 6 ++-
.../Platform/Windows/PlatformWindows.cpp | 10 ++--
lldb/source/Target/Process.cpp | 10 ++--
.../process-load-error-postmortem/Makefile | 4 ++
.../TestProcessLoadErrorPostmortem.py | 51 +++++++++++++++++++
.../process-load-error-postmortem/main.c | 4 ++
.../wow64_minidump/TestWow64MiniDump.py | 27 ++++++++++
8 files changed, 111 insertions(+), 11 deletions(-)
create mode 100644 lldb/test/API/functionalities/load_unload/process-load-error-postmortem/Makefile
create mode 100644 lldb/test/API/functionalities/load_unload/process-load-error-postmortem/TestProcessLoadErrorPostmortem.py
create mode 100644 lldb/test/API/functionalities/load_unload/process-load-error-postmortem/main.c
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 984a4ef880800..3c6a62e43a66a 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -664,13 +664,16 @@ class Process : public std::enable_shared_from_this<Process>,
/// A function that will be called only once per-process in a
/// thread-safe way to create the UtilityFunction if it has not
/// been initialized yet.
+ /// \param[out] error
+ /// If the UtilityFunction could not be created, the reason why.
///
/// \return
/// The cached utility function or null if the platform is not the
- /// same as the target's platform.
+ /// same as the target's platform, or if it could not be created.
UtilityFunction *GetLoadImageUtilityFunction(
Platform *platform,
- llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory);
+ llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory,
+ Status &error);
/// Get the dynamic loader plug-in for this process.
///
@@ -3590,6 +3593,9 @@ void PruneThreadPlans();
std::unique_ptr<UtilityFunction> m_dlopen_utility_func_up;
llvm::once_flag m_dlopen_utility_func_flag_once;
+ /// The error from the one attempt to create m_dlopen_utility_func_up,
+ /// success or failure.
+ Status m_dlopen_utility_func_error;
/// Per process source file cache.
SourceManager::SourceFileCache m_source_file_cache;
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index 2a5a3d55ec98c..9e2921690fc2c 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -715,9 +715,11 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
// The UtilityFunction is held in the Process. Platforms don't track the
// lifespan of the Targets that use them, we can't put this in the Platform.
dlopen_utility_func = process->GetLoadImageUtilityFunction(
- this, [&]() -> std::unique_ptr<UtilityFunction> {
+ this,
+ [&]() -> std::unique_ptr<UtilityFunction> {
return MakeLoadImageUtilityFunction(exe_ctx, error);
- });
+ },
+ error);
// If we couldn't make it, the error will be in error, so we can exit here.
if (!dlopen_utility_func)
return LLDB_INVALID_IMAGE_TOKEN;
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 0e1938b080bba..2c56a077b9a88 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -229,10 +229,12 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
thread->CalculateExecutionContext(context);
Status status;
- UtilityFunction *loader =
- process->GetLoadImageUtilityFunction(this, [&]() -> std::unique_ptr<UtilityFunction> {
- return MakeLoadImageUtilityFunction(context, status);
- });
+ UtilityFunction *loader = process->GetLoadImageUtilityFunction(
+ this,
+ [&]() -> std::unique_ptr<UtilityFunction> {
+ return MakeLoadImageUtilityFunction(context, error);
+ },
+ error);
if (loader == nullptr)
return LLDB_INVALID_IMAGE_TOKEN;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index c86160ae0432d..27be64355a5b3 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6712,11 +6712,15 @@ Status Process::UpdateAutomaticSignalFiltering() {
UtilityFunction *Process::GetLoadImageUtilityFunction(
Platform *platform,
- llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory) {
+ llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory,
+ Status &error) {
if (platform != GetTarget().GetPlatform().get())
return nullptr;
- llvm::call_once(m_dlopen_utility_func_flag_once,
- [&] { m_dlopen_utility_func_up = factory(); });
+ llvm::call_once(m_dlopen_utility_func_flag_once, [&] {
+ m_dlopen_utility_func_up = factory();
+ m_dlopen_utility_func_error = error.Clone();
+ });
+ error = m_dlopen_utility_func_error.Clone();
return m_dlopen_utility_func_up.get();
}
diff --git a/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/Makefile b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/Makefile
new file mode 100644
index 0000000000000..c7256ac7e3263
--- /dev/null
+++ b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES = main.c
+MAKE_DSYM := NO
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/TestProcessLoadErrorPostmortem.py b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/TestProcessLoadErrorPostmortem.py
new file mode 100644
index 0000000000000..320953747df56
--- /dev/null
+++ b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/TestProcessLoadErrorPostmortem.py
@@ -0,0 +1,51 @@
+"""
+Test that Process::LoadImage reports a real error, not an empty/null one, on
+every call after the first, when there is no live process to call dlopen in
+(e.g. debugging a core file).
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class ProcessLoadErrorPostmortemTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ @skipIfWindows # Windows does not support "process load" the same way.
+ @skipIfRemote # save-core / core loading is local-target only here.
+ def test_load_image_error_replayed_on_core(self):
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ core = self.getBuildArtifact("process.core")
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.c")
+ )
+
+ self.runCmd("process save-core --style=stack " + core)
+ process.Kill()
+ self.dbg.DeleteTarget(target)
+
+ target = self.dbg.CreateTarget(exe)
+ process = target.LoadCore(core)
+ self.assertTrue(process.IsValid(), "Could not load the core file")
+
+ nonexistent = lldb.SBFileSpec(
+ "/NoSuchDir/NoSuchSubdir/not-a-real-library", False
+ )
+
+ messages = []
+ for _ in range(2):
+ error = lldb.SBError()
+ token = process.LoadImage(nonexistent, error)
+ self.assertEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN)
+ self.assertTrue(error.Fail())
+ message = error.GetCString()
+ self.assertIsNotNone(message)
+ self.assertNotEqual(message, "")
+ self.assertNotIn("(null)", message)
+ messages.append(message)
+
+ self.assertEqual(messages[0], messages[1])
diff --git a/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/main.c b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/main.c
new file mode 100644
index 0000000000000..fa0d6e8b5f797
--- /dev/null
+++ b/lldb/test/API/functionalities/load_unload/process-load-error-postmortem/main.c
@@ -0,0 +1,4 @@
+int main() {
+ int stack_int = 5;
+ return stack_int; // break here
+}
diff --git a/lldb/test/API/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py b/lldb/test/API/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py
index b4c19ee8fc7b1..eb4d7a2eee29e 100644
--- a/lldb/test/API/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py
+++ b/lldb/test/API/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py
@@ -65,3 +65,30 @@ def test_stack_info_in_wow64_mini_dump(self):
eip = frame.FindRegister("pc")
self.assertTrue(eip.IsValid())
self.assertEqual(pc, eip.GetValueAsUnsigned())
+
+ @skipIfLLVMTargetMissing("X86")
+ def test_load_image_error_replayed_on_wow64_mini_dump(self):
+ """Test that Process::LoadImage reports a real error, not an empty
+ one, on every call, when debugging a minidump."""
+ self.runCmd("platform select remote-windows")
+ target = self.dbg.CreateTarget("")
+ process = target.LoadCore("fizzbuzz_wow64.dmp")
+ self.assertTrue(process, PROCESS_IS_VALID)
+
+ nonexistent = lldb.SBFileSpec(
+ r"Z:\NoSuchDir\NoSuchSubdir\not-a-real-library.dll", False
+ )
+
+ messages = []
+ for _ in range(2):
+ error = lldb.SBError()
+ token = process.LoadImage(nonexistent, error)
+ self.assertEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN)
+ self.assertTrue(error.Fail())
+ message = error.GetCString()
+ self.assertIsNotNone(message)
+ self.assertNotEqual(message, "")
+ self.assertNotIn("(null)", message)
+ messages.append(message)
+
+ self.assertEqual(messages[0], messages[1])
>From 2566538a18c26f39ef402285095d7c44bfdf0db5 Mon Sep 17 00:00:00 2001
From: Yao Qi <qiyaoltc at gmail.com>
Date: Mon, 28 Sep 2026 15:55:10 +0100
Subject: [PATCH 2/4] Apply batched suggestions from code review
Co-authored-by: Charles Zablit <c_zablit at apple.com>
---
lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 2c56a077b9a88..a8d22cf2a235b 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -228,7 +228,6 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
ExecutionContext context;
thread->CalculateExecutionContext(context);
- Status status;
UtilityFunction *loader = process->GetLoadImageUtilityFunction(
this,
[&]() -> std::unique_ptr<UtilityFunction> {
@@ -255,6 +254,7 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
name.emplace_back(L'\0');
/* Inject name paramter into inferior */
+ Status status;
lldb::addr_t injected_name =
process->AllocateMemory(name.size() * sizeof(llvm::UTF16),
ePermissionsReadable | ePermissionsWritable,
>From 8a73fc604ddf21698c15bc0f86b79fb5a84e57d8 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Tue, 29 Sep 2026 20:16:18 +0100
Subject: [PATCH 3/4] [lldb] Return llvm::Expected from
GetLoadImageUtilityFunction
`GetLoadImageUtilityFunction` returned a raw `UtilityFunction *` plus a
`Status &error` output parameter. Change it to return
`llvm::Expected<UtilityFunction &>`, so callers no longer need the null
check.
---
lldb/include/lldb/Target/Process.h | 14 ++---
.../Plugins/Platform/POSIX/PlatformPOSIX.cpp | 56 ++++++++-----------
.../Plugins/Platform/POSIX/PlatformPOSIX.h | 5 +-
.../Platform/Windows/PlatformWindows.cpp | 37 +++++-------
.../Platform/Windows/PlatformWindows.h | 5 +-
lldb/source/Target/Process.cpp | 23 +++++---
6 files changed, 62 insertions(+), 78 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 3c6a62e43a66a..a4ec250a6a9b2 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -664,16 +664,14 @@ class Process : public std::enable_shared_from_this<Process>,
/// A function that will be called only once per-process in a
/// thread-safe way to create the UtilityFunction if it has not
/// been initialized yet.
- /// \param[out] error
- /// If the UtilityFunction could not be created, the reason why.
///
/// \return
- /// The cached utility function or null if the platform is not the
- /// same as the target's platform, or if it could not be created.
- UtilityFunction *GetLoadImageUtilityFunction(
+ /// The cached utility function, or an Error if the platform is not
+ /// the same as the target's platform, or if it could not be created.
+ llvm::Expected<UtilityFunction &> GetLoadImageUtilityFunction(
Platform *platform,
- llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory,
- Status &error);
+ llvm::function_ref<llvm::Expected<std::unique_ptr<UtilityFunction>>()>
+ factory);
/// Get the dynamic loader plug-in for this process.
///
@@ -3594,7 +3592,7 @@ void PruneThreadPlans();
std::unique_ptr<UtilityFunction> m_dlopen_utility_func_up;
llvm::once_flag m_dlopen_utility_func_flag_once;
/// The error from the one attempt to create m_dlopen_utility_func_up,
- /// success or failure.
+ /// set only if that attempt failed.
Status m_dlopen_utility_func_error;
/// Per process source file cache.
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index 9e2921690fc2c..b52d4da817f49 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -553,9 +553,8 @@ Status PlatformPOSIX::EvaluateLibdlExpression(
return Status();
}
-std::unique_ptr<UtilityFunction>
-PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
- Status &error) {
+llvm::Expected<std::unique_ptr<UtilityFunction>>
+PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx) {
// Remember to prepend this with the prefix from
// GetLibdlFunctionDeclarations. The returned values are all in
// __lldb_dlopen_result for consistency. The wrapper returns a void * but
@@ -630,23 +629,21 @@ PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
std::move(expr), dlopen_wrapper_name, eLanguageTypeC_plus_plus, exe_ctx);
if (!utility_fn_or_error) {
std::string error_str = llvm::toString(utility_fn_or_error.takeError());
- error = Status::FromErrorStringWithFormat(
+ return llvm::createStringError(
"dlopen error: could not create utility function: %s",
error_str.c_str());
- return nullptr;
}
std::unique_ptr<UtilityFunction> dlopen_utility_func_up =
std::move(*utility_fn_or_error);
Value value;
ValueList arguments;
- FunctionCaller *do_dlopen_function = nullptr;
// Fetch the clang types we will need:
TypeSystemClangSP scratch_ts_sp =
ScratchTypeSystemClang::GetForTarget(process->GetTarget());
if (!scratch_ts_sp)
- return nullptr;
+ return llvm::createStringError("dlopen error: no scratch type system");
CompilerType clang_void_pointer_type =
scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType();
@@ -663,24 +660,18 @@ PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
arguments.PushValue(value);
arguments.PushValue(value);
arguments.PushValue(value);
-
- do_dlopen_function = dlopen_utility_func_up->MakeFunctionCaller(
+
+ dlopen_utility_func_up->MakeFunctionCaller(
clang_void_pointer_type, arguments, exe_ctx.GetThreadSP(), utility_error);
- if (utility_error.Fail()) {
- error = Status::FromErrorStringWithFormat(
+ if (utility_error.Fail())
+ return llvm::createStringError(
"dlopen error: could not make function caller: %s",
utility_error.AsCString());
- return nullptr;
- }
-
- do_dlopen_function = dlopen_utility_func_up->GetFunctionCaller();
- if (!do_dlopen_function) {
- error =
- Status::FromErrorString("dlopen error: could not get function caller.");
- return nullptr;
- }
-
- // We made a good utility function, so cache it in the process:
+
+ if (!dlopen_utility_func_up->GetFunctionCaller())
+ return llvm::createStringError(
+ "dlopen error: could not get function caller.");
+
return dlopen_utility_func_up;
}
@@ -708,23 +699,20 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
thread_sp->CalculateExecutionContext(exe_ctx);
Status utility_error;
- UtilityFunction *dlopen_utility_func;
ValueList arguments;
- FunctionCaller *do_dlopen_function = nullptr;
// The UtilityFunction is held in the Process. Platforms don't track the
// lifespan of the Targets that use them, we can't put this in the Platform.
- dlopen_utility_func = process->GetLoadImageUtilityFunction(
- this,
- [&]() -> std::unique_ptr<UtilityFunction> {
- return MakeLoadImageUtilityFunction(exe_ctx, error);
- },
- error);
- // If we couldn't make it, the error will be in error, so we can exit here.
- if (!dlopen_utility_func)
+ llvm::Expected<UtilityFunction &> dlopen_utility_func_or_err =
+ process->GetLoadImageUtilityFunction(
+ this, [&]() { return MakeLoadImageUtilityFunction(exe_ctx); });
+ if (!dlopen_utility_func_or_err) {
+ error = Status::FromError(dlopen_utility_func_or_err.takeError());
return LLDB_INVALID_IMAGE_TOKEN;
-
- do_dlopen_function = dlopen_utility_func->GetFunctionCaller();
+ }
+
+ FunctionCaller *do_dlopen_function =
+ dlopen_utility_func_or_err->GetFunctionCaller();
if (!do_dlopen_function) {
error =
Status::FromErrorString("dlopen error: could not get function caller.");
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.h b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.h
index 0a02ecd279cfe..6c27a36b3b1e3 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.h
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.h
@@ -86,9 +86,8 @@ class PlatformPOSIX : public lldb_private::RemoteAwarePlatform {
llvm::StringRef expr_prefix,
lldb::ValueObjectSP &result_valobj_sp);
- std::unique_ptr<lldb_private::UtilityFunction>
- MakeLoadImageUtilityFunction(lldb_private::ExecutionContext &exe_ctx,
- lldb_private::Status &error);
+ llvm::Expected<std::unique_ptr<lldb_private::UtilityFunction>>
+ MakeLoadImageUtilityFunction(lldb_private::ExecutionContext &exe_ctx);
virtual
llvm::StringRef GetLibdlFunctionDeclarations(lldb_private::Process *process);
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index a8d22cf2a235b..2e205fec0d8eb 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -228,16 +228,15 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
ExecutionContext context;
thread->CalculateExecutionContext(context);
- UtilityFunction *loader = process->GetLoadImageUtilityFunction(
- this,
- [&]() -> std::unique_ptr<UtilityFunction> {
- return MakeLoadImageUtilityFunction(context, error);
- },
- error);
- if (loader == nullptr)
+ llvm::Expected<UtilityFunction &> loader_or_err =
+ process->GetLoadImageUtilityFunction(
+ this, [&]() { return MakeLoadImageUtilityFunction(context); });
+ if (!loader_or_err) {
+ error = Status::FromError(loader_or_err.takeError());
return LLDB_INVALID_IMAGE_TOKEN;
+ }
- FunctionCaller *invocation = loader->GetFunctionCaller();
+ FunctionCaller *invocation = loader_or_err->GetFunctionCaller();
if (!invocation) {
error = Status::FromErrorString(
"LoadLibrary error: could not get function caller");
@@ -740,9 +739,8 @@ PlatformWindows::GetSoftwareBreakpointTrapOpcode(Target &target,
}
}
-std::unique_ptr<UtilityFunction>
-PlatformWindows::MakeLoadImageUtilityFunction(ExecutionContext &context,
- Status &status) {
+llvm::Expected<std::unique_ptr<UtilityFunction>>
+PlatformWindows::MakeLoadImageUtilityFunction(ExecutionContext &context) {
// FIXME(compnerd) `-fdeclspec` is not passed to the clang instance?
static constexpr const char kLoaderDecls[] = R"(
extern "C" {
@@ -857,16 +855,15 @@ void * __lldb_LoadLibraryHelper(const wchar_t *name, const wchar_t *paths,
context);
if (!function) {
std::string error = llvm::toString(function.takeError());
- status = Status::FromErrorStringWithFormat(
+ return llvm::createStringError(
"LoadLibrary error: could not create utility function: %s",
error.c_str());
- return nullptr;
}
TypeSystemClangSP scratch_ts_sp =
ScratchTypeSystemClang::GetForTarget(target);
if (!scratch_ts_sp)
- return nullptr;
+ return llvm::createStringError("LoadLibrary error: no scratch type system");
CompilerType VoidPtrTy =
scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType();
@@ -889,18 +886,14 @@ void * __lldb_LoadLibraryHelper(const wchar_t *name, const wchar_t *paths,
std::unique_ptr<UtilityFunction> utility{std::move(*function)};
utility->MakeFunctionCaller(VoidPtrTy, parameters, context.GetThreadSP(),
error);
- if (error.Fail()) {
- status = Status::FromErrorStringWithFormat(
+ if (error.Fail())
+ return llvm::createStringError(
"LoadLibrary error: could not create function caller: %s",
error.AsCString());
- return nullptr;
- }
- if (!utility->GetFunctionCaller()) {
- status = Status::FromErrorString(
+ if (!utility->GetFunctionCaller())
+ return llvm::createStringError(
"LoadLibrary error: could not get function caller");
- return nullptr;
- }
return utility;
}
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.h b/lldb/source/Plugins/Platform/Windows/PlatformWindows.h
index 6f8c20a8456a2..392ca75f64664 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.h
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.h
@@ -85,9 +85,8 @@ class PlatformWindows : public RemoteAwarePlatform {
std::vector<ArchSpec> m_supported_architectures;
private:
- std::unique_ptr<lldb_private::UtilityFunction>
- MakeLoadImageUtilityFunction(lldb_private::ExecutionContext &context,
- lldb_private::Status &status);
+ llvm::Expected<std::unique_ptr<lldb_private::UtilityFunction>>
+ MakeLoadImageUtilityFunction(lldb_private::ExecutionContext &context);
lldb_private::Status EvaluateLoaderExpression(lldb_private::Process *process,
const char *expression,
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 27be64355a5b3..b74c4afe7566c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6710,18 +6710,25 @@ Status Process::UpdateAutomaticSignalFiltering() {
return Status();
}
-UtilityFunction *Process::GetLoadImageUtilityFunction(
+llvm::Expected<UtilityFunction &> Process::GetLoadImageUtilityFunction(
Platform *platform,
- llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory,
- Status &error) {
+ llvm::function_ref<llvm::Expected<std::unique_ptr<UtilityFunction>>()>
+ factory) {
if (platform != GetTarget().GetPlatform().get())
- return nullptr;
+ return llvm::createStringError(
+ "the platform requesting the load-image utility function is not "
+ "the target's platform");
llvm::call_once(m_dlopen_utility_func_flag_once, [&] {
- m_dlopen_utility_func_up = factory();
- m_dlopen_utility_func_error = error.Clone();
+ llvm::Expected<std::unique_ptr<UtilityFunction>> factory_result = factory();
+ if (factory_result)
+ m_dlopen_utility_func_up = std::move(*factory_result);
+ else
+ m_dlopen_utility_func_error =
+ Status::FromError(factory_result.takeError());
});
- error = m_dlopen_utility_func_error.Clone();
- return m_dlopen_utility_func_up.get();
+ if (m_dlopen_utility_func_up)
+ return *m_dlopen_utility_func_up;
+ return m_dlopen_utility_func_error.ToError();
}
llvm::Expected<TraceSupportedResponse> Process::TraceSupported() {
>From a29068e984142c39ef2970bc07cddaf83454ef86 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Wed, 30 Sep 2026 15:58:28 +0100
Subject: [PATCH 4/4] Update doc
---
lldb/include/lldb/Target/Process.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index a4ec250a6a9b2..bf4977516b61f 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -658,6 +658,9 @@ class Process : public std::enable_shared_from_this<Process>,
/// Get the cached UtilityFunction that assists in loading binary images
/// into the process.
///
+ /// Loading images at runtime is optional: an Error here means this
+ /// Process does not support it, not that something has gone wrong.
+ ///
/// \param[in] platform
/// The platform fetching the UtilityFunction.
/// \param[in] factory
More information about the lldb-commits
mailing list