Add a way to load an image using a library name and list of paths.

This provides an efficient (at least on Posix platforms) way to offload to the
target process the search & loading of a library when all we have are the 
library name and a set of potential candidate locations.

<rdar://problem/40905971>

llvm-svn: 335912
This commit is contained in:
Jim Ingham 2018-06-28 20:02:11 +00:00
parent 3cabf73270
commit 0d231f7161
14 changed files with 480 additions and 19 deletions

View File

@ -313,6 +313,40 @@ public:
const lldb::SBFileSpec &remote_image_spec,
lldb::SBError &error);
//------------------------------------------------------------------
/// Load a shared library into this process, starting with a
/// library name and a list of paths, searching along the list of
/// paths till you find a matching library.
///
/// @param[in] local_spec
/// The name of the shared library that you want to load.
/// If local_spec is a relative path, the relative path will be
/// appended to the search paths.
/// If the local_spec is an absolute path, just the basename is used.
///
/// @param[in] paths
/// A list of paths to search for the library whose basename is
/// local_spec.
///
/// @param[out] loaded_path
/// If the library was found along the paths, this will store the
/// full path to the found library.
///
/// @param[out] error
/// An error object that gets filled in with any errors that
/// might occur when trying to search for the shared library.
///
/// @return
/// A token that represents the shared library that can be
/// later passed to UnloadImage. A value of
/// LLDB_INVALID_IMAGE_TOKEN will be returned if the shared
/// library can't be opened.
//------------------------------------------------------------------
uint32_t LoadImageUsingPaths(const lldb::SBFileSpec &image_spec,
SBStringList &paths,
lldb::SBFileSpec &loaded_path,
lldb::SBError &error);
lldb::SBError UnloadImage(uint32_t image_token);
lldb::SBError SendEventData(const char *data);

View File

@ -832,9 +832,45 @@ public:
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error);
//------------------------------------------------------------------
/// Load a shared library specified by base name into this process,
/// looking by hand along a set of paths.
///
/// @param[in] process
/// The process to load the image.
///
/// @param[in] library_name
/// The name of the library to look for.
///
/// @param[in] path_list
/// The list of paths to use to search for the library. First
/// match wins.
///
/// @param[out] error
/// An error object that gets filled in with any errors that
/// might occur when trying to load the shared library.
///
/// @param[out] loaded_path
/// If non-null, the path to the dylib that was successfully loaded
/// is stored in this path.
///
/// @return
/// A token that represents the shared library which can be
/// passed to UnloadImage. A value of
/// LLDB_INVALID_IMAGE_TOKEN will be returned if the shared
/// library can't be opened.
//------------------------------------------------------------------
uint32_t LoadImageUsingPaths(lldb_private::Process *process,
const lldb_private::FileSpec &library_name,
const std::vector<std::string> &paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_path);
virtual uint32_t DoLoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error);
const std::vector<std::string> *paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_path = nullptr);
virtual Status UnloadImage(lldb_private::Process *process,
uint32_t image_token);

View File

@ -0,0 +1 @@
basic_process

View File

@ -0,0 +1,13 @@
LEVEL := ../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
all: hidden_lib a.out
hidden_lib:
$(MAKE) VPATH=$(SRCDIR)/hidden -I $(SRCDIR)/hidden -C hidden -f $(SRCDIR)/hidden/Makefile
clean::
$(MAKE) -I $(SRCDIR)/hidden -C hidden -f $(SRCDIR)/hidden/Makefile clean

View File

@ -0,0 +1,126 @@
"""
Test that SBProcess.LoadImageUsingPaths works correctly.
"""
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 # The Windows platform doesn't implement DoLoadImage.
class LoadUsingPathsTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
# Make the hidden directory in the build hierarchy:
lldbutil.mkdir_p(self.getBuildArtifact("hidden"))
# Invoke the default build rule.
self.build()
ext = 'so'
if self.platformIsDarwin():
ext = 'dylib'
self.lib_name = 'libloadunload.' + ext
self.wd = self.getBuildDir()
self.hidden_dir = os.path.join(self.wd, 'hidden')
self.hidden_lib = os.path.join(self.hidden_dir, self.lib_name)
@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_load_using_paths(self):
"""Test that we can load a module by providing a set of search paths."""
if self.platformIsDarwin():
dylibName = 'libloadunload_d.dylib'
else:
dylibName = 'libloadunload_d.so'
# The directory with the dynamic library we did not link to.
path_dir = os.path.join(self.getBuildDir(), "hidden")
(target, process, thread,
_) = lldbutil.run_to_source_breakpoint(self,
"Break here to do the load using paths",
lldb.SBFileSpec("main.cpp"))
error = lldb.SBError()
lib_spec = lldb.SBFileSpec(self.lib_name)
paths = lldb.SBStringList()
paths.AppendString(self.wd)
paths.AppendString(os.path.join(self.wd, "no_such_dir"))
out_spec = lldb.SBFileSpec()
# First try with no correct directories on the path, and make sure that doesn't blow up:
token = process.LoadImageUsingPaths(lib_spec, paths, out_spec, error)
self.assertEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN, "Only looked on the provided path.")
# Now add the correct dir to the paths list and try again:
paths.AppendString(self.hidden_dir)
token = process.LoadImageUsingPaths(lib_spec, paths, out_spec, error)
self.assertNotEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN, "Got a valid token")
self.assertEqual(out_spec, lldb.SBFileSpec(self.hidden_lib), "Found the expected library")
# Make sure this really is in the image list:
loaded_module = target.FindModule(out_spec)
self.assertTrue(loaded_module.IsValid(), "The loaded module is in the image list.")
# Now see that we can call a function in the loaded module.
value = thread.frames[0].EvaluateExpression("d_function()", lldb.SBExpressionOptions())
self.assertTrue(value.GetError().Success(), "Got a value from the expression")
ret_val = value.GetValueAsSigned()
self.assertEqual(ret_val, 12345, "Got the right value")
# Make sure the token works to unload it:
process.UnloadImage(token)
# Make sure this really is no longer in the image list:
loaded_module = target.FindModule(out_spec)
self.assertFalse(loaded_module.IsValid(), "The unloaded module is no longer in the image list.")
# Make sure a relative path also works:
paths.Clear()
paths.AppendString(os.path.join(self.wd, "no_such_dir"))
paths.AppendString(self.wd)
relative_spec = lldb.SBFileSpec(os.path.join("hidden", self.lib_name))
out_spec = lldb.SBFileSpec()
token = process.LoadImageUsingPaths(relative_spec, paths, out_spec, error)
self.assertNotEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN, "Got a valid token")
self.assertEqual(out_spec, lldb.SBFileSpec(self.hidden_lib), "Found the expected library")
process.UnloadImage(token)
# Finally, passing in an absolute path should work like the basename:
# This should NOT work because we've taken hidden_dir off the paths:
abs_spec = lldb.SBFileSpec(os.path.join(self.hidden_dir, self.lib_name))
token = process.LoadImageUsingPaths(lib_spec, paths, out_spec, error)
self.assertEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN, "Only looked on the provided path.")
# But it should work when we add the dir:
# Now add the correct dir to the paths list and try again:
paths.AppendString(self.hidden_dir)
token = process.LoadImageUsingPaths(lib_spec, paths, out_spec, error)
self.assertNotEqual(token, lldb.LLDB_INVALID_IMAGE_TOKEN, "Got a valid token")
self.assertEqual(out_spec, lldb.SBFileSpec(self.hidden_lib), "Found the expected library")

View File

@ -0,0 +1,7 @@
LEVEL := ../../../make
DYLIB_NAME := loadunload
DYLIB_CXX_SOURCES := d.cpp
DYLIB_ONLY := YES
include $(LEVEL)/Makefile.rules

View File

@ -0,0 +1,21 @@
//===-- c.c -----------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
int d_init()
{
return 456;
}
int d_global = d_init();
int
d_function ()
{ // Find this line number within d_dunction().
return 12345;
}

View File

@ -0,0 +1,16 @@
//===-- main.c --------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <stdio.h>
int
main (int argc, char const *argv[])
{
printf("Break here to do the load using paths.");
return 0;
}

View File

@ -373,6 +373,19 @@ public:
uint32_t
LoadImage (lldb::SBFileSpec &image_spec, lldb::SBError &error);
%feature("autodoc", "
Load the library whose filename is given by image_spec looking in all the
paths supplied in the paths argument. If successful, return a token that
can be passed to UnloadImage and fill loaded_path with the path that was
successfully loaded. On failure, return
lldb.LLDB_INVALID_IMAGE_TOKEN.
") LoadImageUsingPaths;
uint32_t
LoadImageUsingPaths(const lldb::SBFileSpec &image_spec,
SBStringList &paths,
lldb::SBFileSpec &loaded_path,
SBError &error);
lldb::SBError
UnloadImage (uint32_t image_token);

View File

@ -1187,6 +1187,57 @@ uint32_t SBProcess::LoadImage(const lldb::SBFileSpec &sb_local_image_spec,
return LLDB_INVALID_IMAGE_TOKEN;
}
uint32_t SBProcess::LoadImageUsingPaths(const lldb::SBFileSpec &image_spec,
SBStringList &paths,
lldb::SBFileSpec &loaded_path,
lldb::SBError &error) {
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_API));
ProcessSP process_sp(GetSP());
if (process_sp) {
Process::StopLocker stop_locker;
if (stop_locker.TryLock(&process_sp->GetRunLock())) {
if (log)
log->Printf("SBProcess(%p)::LoadImageUsingPaths() => "
"calling Platform::LoadImageUsingPaths for: %s",
static_cast<void *>(process_sp.get()),
image_spec.GetFilename());
std::lock_guard<std::recursive_mutex> guard(
process_sp->GetTarget().GetAPIMutex());
PlatformSP platform_sp = process_sp->GetTarget().GetPlatform();
size_t num_paths = paths.GetSize();
std::vector<std::string> paths_vec;
paths_vec.reserve(num_paths);
for (size_t i = 0; i < num_paths; i++)
paths_vec.push_back(paths.GetStringAtIndex(i));
FileSpec loaded_spec;
uint32_t token = platform_sp->LoadImageUsingPaths(process_sp.get(),
*image_spec,
paths_vec,
error.ref(),
&loaded_spec);
if (token != LLDB_INVALID_IMAGE_TOKEN)
loaded_path = loaded_spec;
return token;
} else {
if (log)
log->Printf("SBProcess(%p)::LoadImageUsingPaths() => error: "
"process is running",
static_cast<void *>(process_sp.get()));
error.SetErrorString("process is running");
}
} else {
if (log)
log->Printf("SBProcess(%p)::LoadImageUsingPaths() => error: "
"called with invalid process",
static_cast<void *>(process_sp.get()));
error.SetErrorString("process is invalid");
}
return LLDB_INVALID_IMAGE_TOKEN;
}
lldb::SBError SBProcess::UnloadImage(uint32_t image_token) {
lldb::SBError sb_error;
ProcessSP process_sp(GetSP());

View File

@ -91,8 +91,12 @@ bool FunctionCaller::WriteFunctionWrapper(
m_jit_start_addr, m_jit_end_addr, m_execution_unit_sp, exe_ctx,
can_interpret, eExecutionPolicyAlways));
if (!jit_error.Success())
if (!jit_error.Success()) {
diagnostic_manager.Printf(eDiagnosticSeverityError,
"Error in PrepareForExecution: %s.",
jit_error.AsCString());
return false;
}
if (m_parser->GetGenerateDebugInfo()) {
lldb::ModuleSP jit_module_sp(m_execution_unit_sp->GetJITModule());

View File

@ -945,13 +945,40 @@ PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
void *image_ptr;
const char *error_str;
};
extern void *memcpy(void *, void *, size_t size);
extern size_t strlen(const char *);
void * __lldb_dlopen_wrapper (const char *path,
void * __lldb_dlopen_wrapper (const char *name,
const char *path_strings,
char *buffer,
__lldb_dlopen_result *result_ptr)
{
result_ptr->image_ptr = dlopen(path, 2);
if (result_ptr->image_ptr == (void *) 0x0)
// This is the case where the name is the full path:
if (path_strings == (char *) 0x0) {
result_ptr->image_ptr = dlopen(name, 2);
if (result_ptr->image_ptr != (void *) 0x0)
result_ptr->error_str = nullptr;
return nullptr;
}
// This is the case where we have a list of paths:
size_t name_len = strlen(name);
while (path_strings != (void *) 0x0 && path_strings[0] != '\0') {
size_t path_len = strlen(path_strings);
memcpy((void *) buffer, (void *) path_strings, path_len);
buffer[path_len] = '/';
char *target_ptr = buffer+path_len+1;
memcpy((void *) target_ptr, (void *) name, name_len + 1);
result_ptr->image_ptr = dlopen(buffer, 2);
if (result_ptr->image_ptr != (void *) 0x0) {
result_ptr->error_str = nullptr;
break;
}
result_ptr->error_str = dlerror();
path_strings = path_strings + path_len + 1;
}
return nullptr;
}
)";
@ -993,13 +1020,16 @@ PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
CompilerType clang_char_pointer_type
= ast->GetBasicType(eBasicTypeChar).GetPointerType();
// We are passing two arguments, the path to dlopen, and a pointer to the
// storage we've made for the result:
// We are passing four arguments, the basename, the list of places to look,
// a buffer big enough for all the path + name combos, and
// a pointer to the storage we've made for the result:
value.SetValueType(Value::eValueTypeScalar);
value.SetCompilerType(clang_void_pointer_type);
arguments.PushValue(value);
value.SetCompilerType(clang_char_pointer_type);
arguments.PushValue(value);
arguments.PushValue(value);
arguments.PushValue(value);
do_dlopen_function = dlopen_utility_func_up->MakeFunctionCaller(
clang_void_pointer_type, arguments, exe_ctx.GetThreadSP(), utility_error);
@ -1021,7 +1051,12 @@ PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error) {
const std::vector<std::string> *paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_image) {
if (loaded_image)
loaded_image->Clear();
std::string path;
path = remote_file.GetPath();
@ -1100,10 +1135,82 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
process->DeallocateMemory(return_addr);
});
// Set the values into our args and write them to the target:
arguments.GetValueAtIndex(0)->GetScalar() = path_addr;
arguments.GetValueAtIndex(1)->GetScalar() = return_addr;
// This will be the address of the storage for paths, if we are using them,
// or nullptr to signal we aren't.
lldb::addr_t path_array_addr = 0x0;
llvm::Optional<CleanUp> path_array_cleanup;
// This is the address to a buffer large enough to hold the largest path
// conjoined with the library name we're passing in. This is a convenience
// to avoid having to call malloc in the dlopen function.
lldb::addr_t buffer_addr = 0x0;
llvm::Optional<CleanUp> buffer_cleanup;
// Set the values into our args and write them to the target:
if (paths != nullptr) {
// First insert the paths into the target. This is expected to be a
// continuous buffer with the strings laid out null terminated and
// end to end with an empty string terminating the buffer.
// We also compute the buffer's required size as we go.
size_t buffer_size = 0;
std::string path_array;
for (auto path : *paths) {
size_t path_size = path.size();
path_array.append(path);
path_array.push_back('\0');
if (path_size > buffer_size)
buffer_size = path_size;
}
path_array.push_back('\0');
path_array_addr = process->AllocateMemory(path_array.size(),
permissions,
utility_error);
if (path_array_addr == LLDB_INVALID_ADDRESS) {
error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
"for path array: %s",
utility_error.AsCString());
return LLDB_INVALID_IMAGE_TOKEN;
}
// Make sure we deallocate the paths array.
path_array_cleanup.emplace([process, path_array_addr] {
process->DeallocateMemory(path_array_addr);
});
process->WriteMemory(path_array_addr, path_array.data(),
path_array.size(), utility_error);
if (utility_error.Fail()) {
error.SetErrorStringWithFormat("dlopen error: could not write path array:"
" %s", utility_error.AsCString());
return LLDB_INVALID_IMAGE_TOKEN;
}
// Now make spaces in the target for the buffer. We need to add one for
// the '/' that the utility function will insert and one for the '\0':
buffer_size += path.size() + 2;
buffer_addr = process->AllocateMemory(buffer_size,
permissions,
utility_error);
if (buffer_addr == LLDB_INVALID_ADDRESS) {
error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
"for buffer: %s",
utility_error.AsCString());
return LLDB_INVALID_IMAGE_TOKEN;
}
// Make sure we deallocate the buffer memory:
buffer_cleanup.emplace([process, buffer_addr] {
process->DeallocateMemory(buffer_addr);
});
}
arguments.GetValueAtIndex(0)->GetScalar() = path_addr;
arguments.GetValueAtIndex(1)->GetScalar() = path_array_addr;
arguments.GetValueAtIndex(2)->GetScalar() = buffer_addr;
arguments.GetValueAtIndex(3)->GetScalar() = return_addr;
lldb::addr_t func_args_addr = LLDB_INVALID_ADDRESS;
diagnostics.Clear();
@ -1146,7 +1253,7 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
ExpressionResults results = do_dlopen_function->ExecuteFunction(
exe_ctx, &func_args_addr, options, diagnostics, return_value);
if (results != eExpressionCompleted) {
error.SetErrorStringWithFormat("dlopen error: could write execute "
error.SetErrorStringWithFormat("dlopen error: failed executing "
"dlopen wrapper function: %s",
diagnostics.GetString().c_str());
return LLDB_INVALID_IMAGE_TOKEN;
@ -1162,8 +1269,19 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
}
// The dlopen succeeded!
if (token != 0x0)
if (token != 0x0) {
if (loaded_image && buffer_addr != 0x0)
{
// Capture the image which was loaded. We leave it in the buffer on
// exit from the dlopen function, so we can just read it from there:
std::string name_string;
process->ReadCStringFromMemory(buffer_addr, name_string, utility_error);
if (utility_error.Success())
loaded_image->SetFile(name_string, false,
llvm::sys::path::Style::posix);
}
return process->AddImageToken(token);
}
// We got an error, lets read in the error string:
std::string dlopen_error_str;

View File

@ -165,7 +165,9 @@ public:
uint32_t DoLoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error) override;
const std::vector<std::string> *paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_image) override;
lldb_private::Status UnloadImage(lldb_private::Process *process,
uint32_t image_token) override;

View File

@ -1738,7 +1738,7 @@ uint32_t Platform::LoadImage(lldb_private::Process *process,
if (error.Fail())
return LLDB_INVALID_IMAGE_TOKEN;
}
return DoLoadImage(process, remote_file, error);
return DoLoadImage(process, remote_file, nullptr, error);
}
if (local_file) {
@ -1751,12 +1751,12 @@ uint32_t Platform::LoadImage(lldb_private::Process *process,
if (error.Fail())
return LLDB_INVALID_IMAGE_TOKEN;
}
return DoLoadImage(process, target_file, error);
}
return DoLoadImage(process, target_file, nullptr, error);
}
if (remote_file) {
// Only remote file was specified so we don't have to do any copying
return DoLoadImage(process, remote_file, error);
return DoLoadImage(process, remote_file, nullptr, error);
}
error.SetErrorString("Neither local nor remote file was specified");
@ -1765,11 +1765,30 @@ uint32_t Platform::LoadImage(lldb_private::Process *process,
uint32_t Platform::DoLoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error) {
const std::vector<std::string> *paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_image) {
error.SetErrorString("LoadImage is not supported on the current platform");
return LLDB_INVALID_IMAGE_TOKEN;
}
uint32_t Platform::LoadImageUsingPaths(lldb_private::Process *process,
const lldb_private::FileSpec &remote_filename,
const std::vector<std::string> &paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_path)
{
FileSpec file_to_use;
if (remote_filename.IsAbsolute())
file_to_use = FileSpec(remote_filename.GetFilename().GetStringRef(),
false,
remote_filename.GetPathStyle());
else
file_to_use = remote_filename;
return DoLoadImage(process, file_to_use, &paths, error, loaded_path);
}
Status Platform::UnloadImage(lldb_private::Process *process,
uint32_t image_token) {
return Status("UnloadImage is not supported on the current platform");