[lldb/test] Make TestLoadUnload compatible with windows

Summary:
This patch introduces a header "dylib.h" which can be used in tests to
handle shared libraries semi-portably. The shared library APIs on
windows and posix systems look very different, but their underlying
functionality is relatively similar, so the mapping is not difficult.

It also introduces two new macros to wrap the functinality necessary to
export/import function across the dll boundary on windows. Previously we
had the LLDB_TEST_API macro for this purpose, which automagically
changed meaning depending on whether we were building the shared library
or the executable. While convenient for simple cases, this approach was
not sufficient for the more complicated setups where one deals with
multiple shared libraries.

Lastly it rewrites TestLoadUnload, to make use of the new APIs. The
trickiest aspect there is the handling of DYLD_LIBRARY_PATH on macos --
previously setting this variable was not needed as the test used
@executable_path-relative dlopens, but the new generic api does not
support that. Other systems do not support such dlopens either so the
test already contained support for setting the appropriate path
variable, and this patch just makes that logic more generic. In doesn't
seem that the purpose of this test was to exercise @executable_path
imports, so this should not be a problem.

These changes are sufficient to make some of the TestLoadUnload tests
pass on windows. Two other tests will start to pass once D77287 lands.

Reviewers: amccarth, jingham, JDevlieghere, compnerd

Subscribers: lldb-commits

Tags: #lldb

Differential Revision: https://reviews.llvm.org/D77662
This commit is contained in:
Pavel Labath 2020-04-06 22:24:51 +02:00
parent 0c61e91100
commit e0dbd02513
10 changed files with 142 additions and 123 deletions

View File

@ -166,19 +166,20 @@ def findMainThreadCheckerDylib():
class _PlatformContext(object):
"""Value object class which contains platform-specific options."""
def __init__(self, shlib_environment_var, shlib_prefix, shlib_extension):
def __init__(self, shlib_environment_var, shlib_path_separator, shlib_prefix, shlib_extension):
self.shlib_environment_var = shlib_environment_var
self.shlib_path_separator = shlib_path_separator
self.shlib_prefix = shlib_prefix
self.shlib_extension = shlib_extension
def createPlatformContext():
if platformIsDarwin():
return _PlatformContext('DYLD_LIBRARY_PATH', 'lib', 'dylib')
return _PlatformContext('DYLD_LIBRARY_PATH', ':', 'lib', 'dylib')
elif getPlatform() in ("freebsd", "linux", "netbsd"):
return _PlatformContext('LD_LIBRARY_PATH', 'lib', 'so')
return _PlatformContext('LD_LIBRARY_PATH', ':', 'lib', 'so')
else:
return None
return _PlatformContext('PATH', ';', '', 'dll')
def hasChattyStderr(test_case):

View File

@ -474,7 +474,7 @@ endif
# Additional system libraries
#----------------------------------------------------------------------
ifeq (1,$(USE_LIBDL))
ifneq ($(OS),NetBSD)
ifeq (,$(filter $(OS), NetBSD Windows_NT))
LDFLAGS += -ldl
endif
endif

View File

@ -0,0 +1,55 @@
#ifndef LLDB_TEST_DYLIB_H
#define LLDB_TEST_DYLIB_H
#include <stdio.h>
#ifdef _WIN32
#include <Windows.h>
#define dylib_get_symbol(handle, name) GetProcAddress((HMODULE)handle, name)
#define dylib_close(handle) (!FreeLibrary((HMODULE)handle))
#else
#include <dlfcn.h>
#define dylib_get_symbol(handle, name) dlsym(handle, name)
#define dylib_close(handle) dlclose(handle)
#endif
inline void *dylib_open(const char *name) {
char dylib_prefix[] =
#ifdef _WIN32
"";
#else
"lib";
#endif
char dylib_suffix[] =
#ifdef _WIN32
".dll";
#elif defined(__APPLE__)
".dylib";
#else
".so";
#endif
char fullname[1024];
snprintf(fullname, sizeof(fullname), "%s%s%s", dylib_prefix, name, dylib_suffix);
#ifdef _WIN32
return LoadLibraryA(fullname);
#else
return dlopen(fullname, RTLD_NOW);
#endif
}
inline const char *dylib_last_error() {
#ifndef _WIN32
return dlerror();
#else
DWORD err = GetLastError();
char *msg;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char *)&msg, 0, NULL);
return msg;
#endif
}
#endif

View File

@ -1,13 +1,18 @@
// This header is included in all the test programs (C and C++) and provides a
// hook for dealing with platform-specifics.
#if defined(_WIN32) || defined(_WIN64)
#ifdef COMPILING_LLDB_TEST_DLL
#define LLDB_TEST_API __declspec(dllexport)
#define LLDB_DYLIB_EXPORT __declspec(dllexport)
#define LLDB_DYLIB_IMPORT __declspec(dllimport)
#else
#define LLDB_TEST_API __declspec(dllimport)
#define LLDB_DYLIB_EXPORT
#define LLDB_DYLIB_IMPORT
#endif
#ifdef COMPILING_LLDB_TEST_DLL
#define LLDB_TEST_API LLDB_DYLIB_EXPORT
#else
#define LLDB_TEST_API
#define LLDB_TEST_API LLDB_DYLIB_IMPORT
#endif
#if defined(_WIN32)

View File

@ -26,7 +26,6 @@ ifeq ($(OS),Darwin)
install_name_tool -id @executable_path/libloadunload_d.dylib libloadunload_d.dylib
endif
hidden_lib_d:
mkdir -p hidden
hidden_lib_d: hidden
$(MAKE) VPATH=$(SRCDIR)/hidden -C hidden -f $(MAKEFILE_RULES) \
DYLIB_ONLY=YES DYLIB_CXX_SOURCES=d.cpp DYLIB_NAME=loadunload_d

View File

@ -13,7 +13,6 @@ 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__)
@ -35,25 +34,15 @@ class LoadUnloadTestCase(TestBase):
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)
if lldb.remote_platform:
path = lldb.remote_platform.GetWorkingDirectory()
else:
path = self.getBuildDir()
if self.dylibPath in os.environ:
sep = self.platformContext.shlib_path_separator
path = os.environ[self.dylibPath] + sep + path
self.runCmd("settings append target.env-vars '{}={}'".format(self.dylibPath, path))
self.default_path = path
def copy_shlibs_to_remote(self, hidden_dir=False):
""" Copies the shared libs required by this test suite to remote.
@ -142,16 +131,12 @@ class LoadUnloadTestCase(TestBase):
# 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
env_cmd_string = "settings replace 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(
@ -195,17 +180,14 @@ class LoadUnloadTestCase(TestBase):
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)
self.runCmd(env_cmd_string)
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.
@ -214,10 +196,9 @@ class LoadUnloadTestCase(TestBase):
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
env_cmd_string = ("settings set target.env-vars %s=%s%s%s" %
(self.dylibPath, new_dir,
self.platformContext.shlib_path_separator, self.default_path))
self.runCmd(env_cmd_string)
# This time, the hidden library should be picked up.
@ -228,7 +209,7 @@ class LoadUnloadTestCase(TestBase):
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
@expectedFailureAll(oslist=["windows"]) # process load not implemented
def test_lldb_process_load_and_unload_commands(self):
self.setSvr4Support(False)
self.run_lldb_process_load_and_unload_commands()
@ -238,7 +219,7 @@ class LoadUnloadTestCase(TestBase):
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
@expectedFailureAll(oslist=["windows"]) # process load not implemented
def test_lldb_process_load_and_unload_commands_with_svr4(self):
self.setSvr4Support(True)
self.run_lldb_process_load_and_unload_commands()
@ -314,11 +295,13 @@ class LoadUnloadTestCase(TestBase):
self.runCmd("process continue")
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@expectedFailureAll(oslist=["windows"]) # breakpoint not hit
def test_load_unload(self):
self.setSvr4Support(False)
self.run_load_unload()
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@expectedFailureAll(oslist=["windows"]) # breakpoint not hit
def test_load_unload_with_svr4(self):
self.setSvr4Support(True)
self.run_load_unload()
@ -362,7 +345,6 @@ class LoadUnloadTestCase(TestBase):
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
@expectedFailureAll(archs="aarch64", oslist="linux",
bugnumber="https://bugs.llvm.org/show_bug.cgi?id=27806")
def test_step_over_load(self):
@ -370,7 +352,6 @@ class LoadUnloadTestCase(TestBase):
self.run_step_over_load()
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
@expectedFailureAll(archs="aarch64", oslist="linux",
bugnumber="https://bugs.llvm.org/show_bug.cgi?id=27806")
def test_step_over_load_with_svr4(self):
@ -408,7 +389,6 @@ class LoadUnloadTestCase(TestBase):
# executable dependencies are resolved relative to the debuggers PWD. Bug?
@expectedFailureAll(oslist=["linux"], triple=no_match('aarch64-.*-android'))
@skipIfFreeBSD # llvm.org/pr14424 - missing FreeBSD Makefiles/testcase support
@skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently
@expectedFailureNetBSD
def test_static_init_during_load(self):
"""Test that we can set breakpoints correctly in static initializers"""
@ -438,7 +418,7 @@ class LoadUnloadTestCase(TestBase):
'stop reason = breakpoint %d' % b_init_bp_num])
self.expect("thread backtrace",
substrs=['b_init',
'dlopen',
'dylib_open',
'main'])
self.runCmd("continue")
@ -448,5 +428,5 @@ class LoadUnloadTestCase(TestBase):
'stop reason = breakpoint %d' % a_init_bp_num])
self.expect("thread backtrace",
substrs=['a_init',
'dlopen',
'dylib_open',
'main'])

View File

@ -1,4 +1,4 @@
extern int b_function ();
extern LLDB_DYLIB_IMPORT int b_function();
int a_init()
{

View File

@ -5,8 +5,4 @@ int b_init()
int b_global = b_init();
int
b_function ()
{
return 500;
}
int LLDB_DYLIB_EXPORT b_function() { return 500; }

View File

@ -5,8 +5,6 @@ int d_init()
int d_global = d_init();
int
d_function ()
{ // Find this line number within d_dunction().
return 700;
int LLDB_DYLIB_EXPORT d_function() {
return 700; // Find this line number within d_dunction().
}

View File

@ -1,72 +1,57 @@
#include <stdio.h>
#include <dlfcn.h>
#include "dylib.h"
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main (int argc, char const *argv[])
{
#if defined (__APPLE__)
const char *a_name = "@executable_path/libloadunload_a.dylib";
const char *c_name = "@executable_path/libloadunload_c.dylib";
#else
const char *a_name = "libloadunload_a.so";
const char *c_name = "libloadunload_c.so";
#endif
void *a_dylib_handle = NULL;
void *c_dylib_handle = NULL;
int (*a_function) (void);
int main(int argc, char const *argv[]) {
const char *a_name = "loadunload_a";
const char *c_name = "loadunload_c";
void *a_dylib_handle = NULL;
void *c_dylib_handle = NULL; // Set break point at this line for test_lldb_process_load_and_unload_commands().
int (*a_function)(void);
a_dylib_handle = dlopen (a_name, RTLD_NOW); // Set break point at this line for test_lldb_process_load_and_unload_commands().
if (a_dylib_handle == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (1);
}
a_dylib_handle = dylib_open(a_name);
if (a_dylib_handle == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(1);
}
a_function = (int (*) ()) dlsym (a_dylib_handle, "a_function");
if (a_function == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (2);
}
printf ("First time around, got: %d\n", a_function ());
dlclose (a_dylib_handle);
a_function = (int (*)())dylib_get_symbol(a_dylib_handle, "a_function");
if (a_function == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(2);
}
printf("First time around, got: %d\n", a_function());
dylib_close(a_dylib_handle);
c_dylib_handle = dlopen (c_name, RTLD_NOW);
if (c_dylib_handle == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (3);
}
a_function = (int (*) ()) dlsym (c_dylib_handle, "c_function");
if (a_function == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (4);
}
c_dylib_handle = dylib_open(c_name);
if (c_dylib_handle == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(3);
}
a_function = (int (*)())dylib_get_symbol(c_dylib_handle, "c_function");
if (a_function == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(4);
}
a_dylib_handle = dlopen (a_name, RTLD_NOW);
if (a_dylib_handle == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (5);
}
a_dylib_handle = dylib_open(a_name);
if (a_dylib_handle == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(5);
}
a_function = (int (*) ()) dlsym (a_dylib_handle, "a_function");
if (a_function == NULL)
{
fprintf (stderr, "%s\n", dlerror());
exit (6);
}
printf ("Second time around, got: %d\n", a_function ());
dlclose (a_dylib_handle);
a_function = (int (*)())dylib_get_symbol(a_dylib_handle, "a_function");
if (a_function == NULL) {
fprintf(stderr, "%s\n", dylib_last_error());
exit(6);
}
printf("Second time around, got: %d\n", a_function());
dylib_close(a_dylib_handle);
int d_function(void);
printf ("d_function returns: %d\n", d_function());
int LLDB_DYLIB_IMPORT d_function(void);
printf("d_function returns: %d\n", d_function());
return 0;
return 0;
}