-
Notifications
You must be signed in to change notification settings - Fork 341
[LLDB] Deterministic module order in Target::SetExecutableModule
#10746
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
base: stable/20240723
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1538,73 +1538,35 @@ void Target::SetExecutableModule(ModuleSP &executable_sp, | |
break; | ||
} | ||
|
||
if (executable_objfile && load_dependents) { | ||
// FileSpecList is not thread safe and needs to be synchronized. | ||
FileSpecList dependent_files; | ||
std::mutex dependent_files_mutex; | ||
|
||
// ModuleList is thread safe. | ||
ModuleList added_modules; | ||
|
||
auto GetDependentModules = [&](FileSpec dependent_file_spec) { | ||
FileSpec platform_dependent_file_spec; | ||
if (m_platform_sp) | ||
m_platform_sp->GetFileWithUUID(dependent_file_spec, nullptr, | ||
platform_dependent_file_spec); | ||
else | ||
platform_dependent_file_spec = dependent_file_spec; | ||
|
||
ModuleSpec module_spec(platform_dependent_file_spec, m_arch.GetSpec()); | ||
ModuleSP image_module_sp( | ||
GetOrCreateModule(module_spec, false /* notify */)); | ||
if (image_module_sp) { | ||
added_modules.AppendIfNeeded(image_module_sp, false); | ||
ObjectFile *objfile = image_module_sp->GetObjectFile(); | ||
if (objfile) { | ||
// Create a local copy of the dependent file list so we don't have | ||
// to lock for the whole duration of GetDependentModules. | ||
FileSpecList dependent_files_copy; | ||
{ | ||
std::lock_guard<std::mutex> guard(dependent_files_mutex); | ||
dependent_files_copy = dependent_files; | ||
} | ||
|
||
// Remember the size of the local copy so we can append only the | ||
// modules that have been added by GetDependentModules. | ||
const size_t previous_dependent_files = | ||
dependent_files_copy.GetSize(); | ||
|
||
objfile->GetDependentModules(dependent_files_copy); | ||
|
||
{ | ||
std::lock_guard<std::mutex> guard(dependent_files_mutex); | ||
for (size_t i = previous_dependent_files; | ||
i < dependent_files_copy.GetSize(); ++i) | ||
dependent_files.AppendIfUnique( | ||
dependent_files_copy.GetFileSpecAtIndex(i)); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
executable_objfile->GetDependentModules(dependent_files); | ||
if (!executable_objfile || !load_dependents) | ||
return; | ||
|
||
llvm::ThreadPoolTaskGroup task_group(Debugger::GetThreadPool()); | ||
for (uint32_t i = 0; i < dependent_files.GetSize(); i++) { | ||
// Process all currently known dependencies in parallel in the innermost | ||
// loop. This may create newly discovered dependencies to be appended to | ||
// dependent_files. We'll deal with these files during the next | ||
// iteration of the outermost loop. | ||
{ | ||
std::lock_guard<std::mutex> guard(dependent_files_mutex); | ||
for (; i < dependent_files.GetSize(); i++) | ||
task_group.async(GetDependentModules, | ||
dependent_files.GetFileSpecAtIndex(i)); | ||
} | ||
task_group.wait(); | ||
llvm::ThreadPoolTaskGroup task_group(Debugger::GetThreadPool()); | ||
ModuleList added_modules; | ||
FileSpecList dependent_files; | ||
executable_objfile->GetDependentModules(dependent_files); | ||
|
||
for (uint32_t i = 0; i < dependent_files.GetSize(); i++) { | ||
auto dependent_file_spec = dependent_files.GetFileSpecAtIndex(i); | ||
FileSpec platform_dependent_file_spec; | ||
if (m_platform_sp) | ||
m_platform_sp->GetFileWithUUID(dependent_file_spec, nullptr, | ||
platform_dependent_file_spec); | ||
else | ||
platform_dependent_file_spec = dependent_file_spec; | ||
|
||
ModuleSpec module_spec(platform_dependent_file_spec, m_arch.GetSpec()); | ||
auto image_module_sp = GetOrCreateModule( | ||
module_spec, false /* notify */, | ||
[&](auto preloadSymbols) { task_group.async(preloadSymbols); }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to gate the parallel loading of modules here based on the with the |
||
if (image_module_sp) { | ||
added_modules.AppendIfNeeded(image_module_sp, false); | ||
if (auto *objfile = image_module_sp->GetObjectFile()) | ||
objfile->GetDependentModules(dependent_files); | ||
} | ||
ModulesDidLoad(added_modules); | ||
} | ||
task_group.wait(); | ||
ModulesDidLoad(added_modules); | ||
} | ||
} | ||
|
||
|
@@ -2249,6 +2211,15 @@ bool Target::ReadPointerFromMemory(const Address &addr, Status &error, | |
|
||
ModuleSP Target::GetOrCreateModule(const ModuleSpec &module_spec, bool notify, | ||
Status *error_ptr) { | ||
return GetOrCreateModule(module_spec, notify, [](auto preloadSymbols) { | ||
preloadSymbols(); | ||
}, error_ptr); | ||
} | ||
|
||
ModuleSP Target::GetOrCreateModule( | ||
const ModuleSpec &module_spec, bool notify, | ||
llvm::function_ref<void(std::function<void()>)> scheduleSymbolsPreload, | ||
Status *error_ptr) { | ||
ModuleSP module_sp; | ||
|
||
Status error; | ||
|
@@ -2408,7 +2379,8 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &module_spec, bool notify, | |
// Preload symbols outside of any lock, so hopefully we can do this for | ||
// each library in parallel. | ||
if (GetPreloadSymbols()) | ||
module_sp->PreloadSymbols(); | ||
scheduleSymbolsPreload([module_sp] { module_sp->PreloadSymbols(); }); | ||
|
||
llvm::SmallVector<ModuleSP, 1> replaced_modules; | ||
for (ModuleSP &old_module_sp : old_modules) { | ||
if (m_images.GetIndexForModule(old_module_sp.get()) != | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
C_SOURCES := main.c | ||
LD_EXTRAS := -L. -la -lb -lc -ld -le | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ran into a few problems when running this test on linux.
I worked around the first problem by not linking
Obviously this is not going to detect a non-deterministic module order for linux. But if we change it to link to the full path of the shared objects
Then the test looks like it would work
|
||
|
||
a.out: liba libb libc libd libe | ||
|
||
include Makefile.rules | ||
|
||
liba: | ||
"$(MAKE)" -f $(MAKEFILE_RULES) \ | ||
DYLIB_ONLY=YES DYLIB_C_SOURCES=lib.c DYLIB_NAME=a | ||
|
||
libb: | ||
"$(MAKE)" -f $(MAKEFILE_RULES) \ | ||
DYLIB_ONLY=YES DYLIB_C_SOURCES=lib.c DYLIB_NAME=b | ||
|
||
libc: | ||
"$(MAKE)" -f $(MAKEFILE_RULES) \ | ||
DYLIB_ONLY=YES DYLIB_C_SOURCES=lib.c DYLIB_NAME=c | ||
|
||
libd: | ||
"$(MAKE)" -f $(MAKEFILE_RULES) \ | ||
DYLIB_ONLY=YES DYLIB_C_SOURCES=lib.c DYLIB_NAME=d | ||
|
||
libe: | ||
"$(MAKE)" -f $(MAKEFILE_RULES) \ | ||
DYLIB_ONLY=YES DYLIB_C_SOURCES=lib.c DYLIB_NAME=e |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
""" | ||
Test that module loading order is deterministic. | ||
""" | ||
|
||
import lldb | ||
from lldbsuite.test.decorators import * | ||
from lldbsuite.test.lldbtest import * | ||
from lldbsuite.test import lldbutil | ||
|
||
|
||
class DeterministicModuleOrderTestCase(TestBase): | ||
NO_DEBUG_INFO_TESTCASE = True | ||
|
||
def setUp(self): | ||
TestBase.setUp(self) | ||
|
||
def test_create_target_module_load_order(self): | ||
"""Test that module loading order is deterministic across multiple runs.""" | ||
|
||
self.build() | ||
exe = self.getBuildArtifact("a.out") | ||
|
||
# First run: Create target and save module list | ||
target = self.dbg.CreateTarget(exe) | ||
self.assertTrue(target, VALID_TARGET) | ||
|
||
first_run_modules = [] | ||
for i in range(target.GetNumModules()): | ||
module = target.GetModuleAtIndex(i) | ||
module_name = module.GetFileSpec().GetFilename() | ||
first_run_modules.append(module_name) | ||
|
||
self.dbg.DeleteTarget(target) | ||
|
||
if self.TraceOn(): | ||
print(f"First run module list: {first_run_modules}") | ||
|
||
if not first_run_modules: | ||
self.fail("No modules were found during the first run") | ||
|
||
# Subsequent runs: Create target and compare module list to first run | ||
num_additional_runs = 9 # Total of 10 runs including the first one | ||
for run in range(num_additional_runs): | ||
target = self.dbg.CreateTarget(exe) | ||
self.assertTrue(target, VALID_TARGET) | ||
current_modules = [] | ||
for i in range(target.GetNumModules()): | ||
module = target.GetModuleAtIndex(i) | ||
module_name = module.GetFileSpec().GetFilename() | ||
current_modules.append(module_name) | ||
|
||
if self.TraceOn(): | ||
print(f"Run {run+2} module list: {current_modules}") | ||
|
||
self.assertEqual( | ||
first_run_modules, | ||
current_modules, | ||
f"Module list in run {run+2} differs from the first run" | ||
) | ||
|
||
self.dbg.DeleteTarget(target) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
void dummy_func(void) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
int main() { | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a separate overload for this or could we use a default value of
nullptr
forscheduleSymbolsPreload
? It looks likellvm::function_ref
has a suitableoperator bool()
defined.I suppose it might require updating more callsites that currently pass the
Status
output pointer.