Make sure the interpreter module was loaded before making checks against it

This issue was introduced in https://reviews.llvm.org/D92187.
The guard I'm changing were is supposed to act when linux is loading the linker for the second time (due to differences in paths like symlinks).
This is done by checking `module_sp != m_interpreter_module.lock()` however this will be true when `m_interpreter_module` wasn't initialized, making linux unload the linker module (the most visible result here is that lldb will stop getting notified about new modules loaded by the process, because it can't set the rendezvous breakpoint again after the stepping over it once).
The `m_interpreter_module` is not getting initialize when it goes through this path: dbfdb139f7/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp (L332), which happens when lldb was able to read the address from the dynamic section of the executable.

What I'm not sure about though, is if when we go through this path if we still load the linker twice on linux. If that's the case then it means we need to somehow set the m_interpreter_module instead of the fix I provide here. I've only tested this on Android.

Differential Revision: https://reviews.llvm.org/D96637
This commit is contained in:
António Afonso 2021-02-18 20:31:55 -08:00
parent 7c706aa0d8
commit a83a825e99
6 changed files with 80 additions and 0 deletions

View File

@ -622,6 +622,10 @@ def skipUnlessDarwin(func):
"""Decorate the item to skip tests that should be skipped on any non Darwin platform."""
return skipUnlessPlatform(lldbplatformutil.getDarwinOSTriples())(func)
def skipUnlessLinux(func):
"""Decorate the item to skip tests that should be skipped on any non-Linux platform."""
return skipUnlessPlatform(["linux"])(func)
def skipUnlessTargetAndroid(func):
return unittest2.skipUnless(lldbplatformutil.target_is_android(),
"requires target to be Android")(func)

View File

@ -441,6 +441,7 @@ void DynamicLoaderPOSIXDYLD::RefreshModules() {
if (module_sp.get()) {
if (module_sp->GetObjectFile()->GetBaseAddress().GetLoadAddress(
&m_process->GetTarget()) == m_interpreter_base &&
m_interpreter_module.lock() &&
module_sp != m_interpreter_module.lock()) {
// If this is a duplicate instance of ld.so, unload it. We may end up
// with it if we load it via a different path than before (symlink

View File

@ -0,0 +1,10 @@
C_SOURCES := main.c
LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
USE_LIBDL := 1
feature:
$(MAKE) -f $(MAKEFILE_RULES) \
DYLIB_ONLY=YES DYLIB_NAME=feature DYLIB_C_SOURCES=feature.c
all: feature
include Makefile.rules

View File

@ -0,0 +1,49 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
def build_launch_and_attach(self):
self.build()
# launch
exe = self.getBuildArtifact("a.out")
popen = self.spawnSubprocess(exe)
# attach
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
listener = lldb.SBListener("my.attach.listener")
error = lldb.SBError()
process = target.AttachToProcessWithID(listener, popen.pid, error)
self.assertTrue(error.Success() and process, PROCESS_IS_VALID)
return process
def assertModuleIsLoaded(self, module_name):
feature_module = self.dbg.GetSelectedTarget().FindModule(lldb.SBFileSpec(module_name))
self.assertTrue(feature_module.IsValid(), f"Module {module_name} should be loaded")
def assertModuleIsNotLoaded(self, module_name):
feature_module = self.dbg.GetSelectedTarget().FindModule(lldb.SBFileSpec(module_name))
self.assertFalse(feature_module.IsValid(), f"Module {module_name} should not be loaded")
@skipIfRemote
@skipUnlessLinux
@no_debug_info_test
def test(self):
'''
This test makes sure that after attach lldb still gets notifications
about new modules being loaded by the process
'''
process = self.build_launch_and_attach()
thread = process.GetSelectedThread()
self.assertModuleIsNotLoaded("libfeature.so")
thread.GetSelectedFrame().EvaluateExpression("flip_to_1_to_continue = 1")
# Continue so that dlopen is called.
breakpoint = self.target().BreakpointCreateBySourceRegex(
"// break after dlopen", lldb.SBFileSpec("main.c"))
self.assertNotEqual(breakpoint.GetNumResolvedLocations(), 0)
stopped_threads = lldbutil.continue_to_breakpoint(self.process(), breakpoint)
self.assertModuleIsLoaded("libfeature.so")

View File

@ -0,0 +1 @@
extern void feature() {}

View File

@ -0,0 +1,15 @@
#include <dlfcn.h>
#include <assert.h>
#include <unistd.h>
volatile int flip_to_1_to_continue = 0;
int main() {
lldb_enable_attach();
while (! flip_to_1_to_continue) // Wait for debugger to attach
sleep(1);
// dlopen the feature
void *feature = dlopen("libfeature.so", RTLD_NOW);
assert(feature && "dlopen failed?");
return 0; // break after dlopen
}