blob: ab5791c614af53a2d77e7e6a22abba52fa2d17c7 [file] [log] [blame]
"""
Test that breakpoint by symbol name works correctly with dynamic libs.
"""
from __future__ import print_function
import os
import time
import re
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
class LoadUnloadTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
self.setup_test()
# Invoke the default build rule.
self.build()
# Find the line number to break for main.cpp.
self.line = line_number(
'main.cpp',
'// Set break point at this line for test_lldb_process_load_and_unload_commands().')
self.line_d_function = line_number(
'd.cpp', '// Find this line number within d_dunction().')
def setup_test(self):
lldbutil.mkdir_p(self.getBuildArtifact("hidden"))
if not self.platformIsDarwin():
if not lldb.remote_platform and "LD_LIBRARY_PATH" in os.environ:
self.runCmd(
"settings set target.env-vars " +
self.dylibPath +
"=" +
os.environ["LD_LIBRARY_PATH"] +
":" +
self.getBuildDir())
else:
if lldb.remote_platform:
wd = lldb.remote_platform.GetWorkingDirectory()
else:
wd = self.getBuildDir()
self.runCmd(
"settings set target.env-vars " +
self.dylibPath +
"=" +
wd)
def copy_shlibs_to_remote(self, hidden_dir=False):
""" Copies the shared libs required by this test suite to remote.
Does nothing in case of non-remote platforms.
"""
if lldb.remote_platform:
ext = 'so'
if self.platformIsDarwin():
ext = 'dylib'
shlibs = ['libloadunload_a.' + ext, 'libloadunload_b.' + ext,
'libloadunload_c.' + ext, 'libloadunload_d.' + ext]
wd = lldb.remote_platform.GetWorkingDirectory()
cwd = os.getcwd()
for f in shlibs:
err = lldb.remote_platform.Put(
lldb.SBFileSpec(self.getBuildArtifact(f)),
lldb.SBFileSpec(os.path.join(wd, f)))
if err.Fail():
raise RuntimeError(
"Unable copy '%s' to '%s'.\n>>> %s" %
(f, wd, err.GetCString()))
if hidden_dir:
shlib = 'libloadunload_d.' + ext
hidden_dir = os.path.join(wd, 'hidden')
hidden_file = os.path.join(hidden_dir, shlib)
err = lldb.remote_platform.MakeDirectory(hidden_dir)
if err.Fail():
raise RuntimeError(
"Unable to create a directory '%s'." % hidden_dir)
err = lldb.remote_platform.Put(
lldb.SBFileSpec(os.path.join('hidden', shlib)),
lldb.SBFileSpec(hidden_file))
if err.Fail():
raise RuntimeError(
"Unable copy 'libloadunload_d.so' to '%s'.\n>>> %s" %
(wd, err.GetCString()))
# libloadunload_d.so does not appear in the image list because executable
# dependencies are resolved relative to the debuggers PWD. Bug?
@expectedFailureAll(oslist=["linux"])
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@not_remote_testsuite_ready
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
def test_modules_search_paths(self):
"""Test target modules list after loading a different copy of the library libd.dylib, and verifies that it works with 'target modules search-paths add'."""
if self.platformIsDarwin():
dylibName = 'libloadunload_d.dylib'
else:
dylibName = 'libloadunload_d.so'
# The directory with the dynamic library we did not link to.
new_dir = os.path.join(self.getBuildDir(), "hidden")
old_dylib = os.path.join(self.getBuildDir(), dylibName)
new_dylib = os.path.join(new_dir, dylibName)
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.expect("target modules list",
substrs=[old_dylib])
# self.expect("target modules list -t 3",
# patterns = ["%s-[^-]*-[^-]*" % self.getArchitecture()])
# Add an image search path substitution pair.
self.runCmd(
"target modules search-paths add %s %s" %
(self.getBuildDir(), new_dir))
self.expect("target modules search-paths list",
substrs=[self.getBuildDir(), new_dir])
self.expect(
"target modules search-paths query %s" %
self.getBuildDir(),
"Image search path successfully transformed",
substrs=[new_dir])
# Obliterate traces of libd from the old location.
os.remove(old_dylib)
# Inform (DY)LD_LIBRARY_PATH of the new path, too.
env_cmd_string = "settings set target.env-vars " + self.dylibPath + "=" + new_dir
if self.TraceOn():
print("Set environment to: ", env_cmd_string)
self.runCmd(env_cmd_string)
self.runCmd("settings show target.env-vars")
remove_dyld_path_cmd = "settings remove target.env-vars " + self.dylibPath
self.addTearDownHook(
lambda: self.dbg.HandleCommand(remove_dyld_path_cmd))
self.runCmd("run")
self.expect(
"target modules list",
"LLDB successfully locates the relocated dynamic library",
substrs=[new_dylib])
# libloadunload_d.so does not appear in the image list because executable
# dependencies are resolved relative to the debuggers PWD. Bug?
@expectedFailureAll(oslist=["linux"])
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@expectedFailureAndroid # wrong source file shows up for hidden library
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
def test_dyld_library_path(self):
"""Test (DY)LD_LIBRARY_PATH after moving libd.dylib, which defines d_function, somewhere else."""
self.copy_shlibs_to_remote(hidden_dir=True)
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Shut off ANSI color usage so we don't get ANSI escape sequences
# mixed in with stop locations.
self.dbg.SetUseColor(False)
if self.platformIsDarwin():
dylibName = 'libloadunload_d.dylib'
dsymName = 'libloadunload_d.dylib.dSYM'
else:
dylibName = 'libloadunload_d.so'
# The directory to relocate the dynamic library and its debugging info.
special_dir = "hidden"
if lldb.remote_platform:
wd = lldb.remote_platform.GetWorkingDirectory()
else:
wd = self.getBuildDir()
old_dir = wd
new_dir = os.path.join(wd, special_dir)
old_dylib = os.path.join(old_dir, dylibName)
remove_dyld_path_cmd = "settings remove target.env-vars " \
+ self.dylibPath
self.addTearDownHook(
lambda: self.dbg.HandleCommand(remove_dyld_path_cmd))
# For now we don't track (DY)LD_LIBRARY_PATH, so the old
# library will be in the modules list.
self.expect("target modules list",
substrs=[os.path.basename(old_dylib)],
matching=True)
lldbutil.run_break_set_by_file_and_line(
self, "d.cpp", self.line_d_function, num_expected_locations=1)
# After run, make sure the non-hidden library is picked up.
self.expect("run", substrs=["return", "700"])
self.runCmd("continue")
# Add the hidden directory first in the search path.
env_cmd_string = ("settings set target.env-vars %s=%s" %
(self.dylibPath, new_dir))
if not self.platformIsDarwin():
env_cmd_string += ":" + wd
self.runCmd(env_cmd_string)
# This time, the hidden library should be picked up.
self.expect("run", substrs=["return", "12345"])
@expectedFailureAll(
bugnumber="llvm.org/pr25805",
hostoslist=["windows"],
triple='.*-android')
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
def test_lldb_process_load_and_unload_commands(self):
"""Test that lldb process load/unload command work correctly."""
self.copy_shlibs_to_remote()
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Break at main.cpp before the call to dlopen().
# Use lldb's process load command to load the dylib, instead.
lldbutil.run_break_set_by_file_and_line(
self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True)
self.runCmd("run", RUN_SUCCEEDED)
ctx = self.platformContext
dylibName = ctx.shlib_prefix + 'loadunload_a.' + ctx.shlib_extension
localDylibPath = self.getBuildArtifact(dylibName)
if lldb.remote_platform:
wd = lldb.remote_platform.GetWorkingDirectory()
remoteDylibPath = lldbutil.join_remote_paths(wd, dylibName)
else:
remoteDylibPath = localDylibPath
# Make sure that a_function does not exist at this point.
self.expect(
"image lookup -n a_function",
"a_function should not exist yet",
error=True,
matching=False,
patterns=["1 match found"])
# Use lldb 'process load' to load the dylib.
self.expect(
"process load %s --install=%s" % (localDylibPath, remoteDylibPath),
"%s loaded correctly" % dylibName,
patterns=[
'Loading "%s".*ok' % localDylibPath,
'Image [0-9]+ loaded'])
# Search for and match the "Image ([0-9]+) loaded" pattern.
output = self.res.GetOutput()
pattern = re.compile("Image ([0-9]+) loaded")
for l in output.split(os.linesep):
#print("l:", l)
match = pattern.search(l)
if match:
break
index = match.group(1)
# Now we should have an entry for a_function.
self.expect(
"image lookup -n a_function",
"a_function should now exist",
patterns=[
"1 match found .*%s" %
dylibName])
# Use lldb 'process unload' to unload the dylib.
self.expect(
"process unload %s" %
index,
"%s unloaded correctly" %
dylibName,
patterns=[
"Unloading .* with index %s.*ok" %
index])
self.runCmd("process continue")
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
def test_load_unload(self):
"""Test breakpoint by name works correctly with dlopen'ing."""
self.copy_shlibs_to_remote()
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Break by function name a_function (not yet loaded).
lldbutil.run_break_set_by_symbol(
self, "a_function", num_expected_locations=0)
self.runCmd("run", RUN_SUCCEEDED)
# The stop reason of the thread should be breakpoint and at a_function.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'a_function',
'stop reason = breakpoint'])
# The breakpoint should have a hit count of 1.
self.expect("breakpoint list -f", BREAKPOINT_HIT_ONCE,
substrs=[' resolved, hit count = 1'])
# Issue the 'contnue' command. We should stop agaian at a_function.
# The stop reason of the thread should be breakpoint and at a_function.
self.runCmd("continue")
# rdar://problem/8508987
# The a_function breakpoint should be encountered twice.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'a_function',
'stop reason = breakpoint'])
# The breakpoint should have a hit count of 2.
self.expect("breakpoint list -f", BREAKPOINT_HIT_ONCE,
substrs=[' resolved, hit count = 2'])
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
def test_step_over_load(self):
"""Test stepping over code that loads a shared library works correctly."""
self.copy_shlibs_to_remote()
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Break by function name a_function (not yet loaded).
lldbutil.run_break_set_by_file_and_line(
self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True)
self.runCmd("run", RUN_SUCCEEDED)
# The stop reason of the thread should be breakpoint and at a_function.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'stop reason = breakpoint'])
self.runCmd(
"thread step-over",
"Stepping over function that loads library")
# The stop reason should be step end.
self.expect("thread list", "step over succeeded.",
substrs=['stopped',
'stop reason = step over'])
# We can't find a breakpoint location for d_init before launching because
# executable dependencies are resolved relative to the debuggers PWD. Bug?
@expectedFailureAll(oslist=["linux"])
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
def test_static_init_during_load(self):
"""Test that we can set breakpoints correctly in static initializers"""
self.copy_shlibs_to_remote()
exe = self.getBuildArtifact("a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
a_init_bp_num = lldbutil.run_break_set_by_symbol(
self, "a_init", num_expected_locations=0)
b_init_bp_num = lldbutil.run_break_set_by_symbol(
self, "b_init", num_expected_locations=0)
d_init_bp_num = lldbutil.run_break_set_by_symbol(
self, "d_init", num_expected_locations=1)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'd_init',
'stop reason = breakpoint %d' % d_init_bp_num])
self.runCmd("continue")
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'b_init',
'stop reason = breakpoint %d' % b_init_bp_num])
self.expect("thread backtrace",
substrs=['b_init',
'dlopen',
'main'])
self.runCmd("continue")
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'a_init',
'stop reason = breakpoint %d' % a_init_bp_num])
self.expect("thread backtrace",
substrs=['a_init',
'dlopen',
'main'])