blob: 388ad9199897e6bfc3ebae76d5c998b76791fa0b [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <inttypes.h>
#include <launchpad/vmo.h>
#include <loader-service/loader-service.h>
#include <ldmsg/ldmsg.h>
#include <zircon/dlfcn.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <unittest/unittest.h>
#if __has_feature(address_sanitizer)
# define LIBPREFIX "/boot/lib/asan/"
#else
# define LIBPREFIX "/boot/lib/"
#endif
bool dlopen_vmo_test(void) {
BEGIN_TEST;
zx_handle_t vmo = ZX_HANDLE_INVALID;
zx_status_t status = launchpad_vmo_from_file(LIBPREFIX "liblaunchpad.so", &vmo);
EXPECT_EQ(status, ZX_OK, "");
EXPECT_NE(vmo, ZX_HANDLE_INVALID, "launchpad_vmo_from_file");
void* obj = dlopen_vmo(vmo, RTLD_LOCAL);
EXPECT_NONNULL(obj, "dlopen_vmo");
zx_handle_close(vmo);
void* sym = dlsym(obj, "launchpad_create");
EXPECT_NONNULL(sym, "dlsym");
int ok = dlclose(obj);
EXPECT_EQ(ok, 0, "dlclose");
END_TEST;
}
// This should be some library that this program links against.
#define TEST_SONAME "libfdio.so"
#define TEST_NAME "foobar"
#define TEST_ACTUAL_NAME LIBPREFIX TEST_SONAME
static atomic_bool my_loader_service_ok = false;
static atomic_int my_loader_service_calls = 0;
static zx_status_t my_load_object(void* ctx, const char* name, zx_handle_t* out) {
++my_loader_service_calls;
int cmp = strcmp(name, TEST_NAME);
EXPECT_EQ(cmp, 0, "called with unexpected name");
if (cmp != 0) {
unittest_printf(" saw \"%s\", expected \"%s\"", name, TEST_NAME);
return ZX_HANDLE_INVALID;
}
zx_handle_t vmo = ZX_HANDLE_INVALID;
zx_status_t status = launchpad_vmo_from_file((char*) ctx, &vmo);
EXPECT_EQ(status, ZX_OK, "");
EXPECT_NE(vmo, ZX_HANDLE_INVALID, "launchpad_vmo_from_file");
if (status < 0) {
return status;
}
my_loader_service_ok = true;
*out = vmo;
return ZX_OK;
}
static zx_status_t my_load_abspath(void* ctx, const char* name, zx_handle_t* vmo) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t my_publish_data_sink(void* ctx, const char* name, zx_handle_t vmo) {
zx_handle_close(vmo);
return ZX_ERR_NOT_SUPPORTED;
}
static loader_service_ops_t my_loader_ops = {
.load_object = my_load_object,
.load_abspath = my_load_abspath,
.publish_data_sink = my_publish_data_sink,
};
static void show_dlerror(void) {
unittest_printf_critical("dlerror: %s\n", dlerror());
}
bool loader_service_test(void) {
BEGIN_TEST;
// Get a handle to an existing library with a known SONAME.
void* by_name = dlopen(TEST_SONAME, RTLD_NOLOAD);
EXPECT_NONNULL(by_name, "dlopen failed on " TEST_SONAME);
if (by_name == NULL)
show_dlerror();
// Spin up our test service.
loader_service_t* svc = NULL;
zx_status_t status = loader_service_create(NULL, &my_loader_ops, (void*)TEST_ACTUAL_NAME, &svc);
EXPECT_EQ(status, ZX_OK, "loader_service_create");
zx_handle_t my_service = ZX_HANDLE_INVALID;
status = loader_service_connect(svc, &my_service);
EXPECT_EQ(status, ZX_OK, "loader_service_connect");
// Install the service.
zx_handle_t old = dl_set_loader_service(my_service);
EXPECT_NE(old, ZX_HANDLE_INVALID, "dl_set_loader_service");
// Now to a lookup that should go through our service. It
// should load up the new copy of the file, find that its
// SONAME matches an existing library, and just return it.
void *via_service = dlopen(TEST_NAME, RTLD_LOCAL);
EXPECT_EQ(my_loader_service_calls, 1,
"loader-service not called exactly once");
EXPECT_NONNULL(via_service, "dlopen via service");
if (via_service == NULL)
show_dlerror();
EXPECT_TRUE(my_loader_service_ok, "loader service thread not happy");
// It should not just have succeeded, but gotten the very
// same handle as the by-name lookup.
EXPECT_TRUE(via_service == by_name, "dlopen via service");
int fail = dlclose(by_name);
EXPECT_EQ(fail, 0, "dlclose on by-name");
if (fail)
show_dlerror();
fail = dlclose(via_service);
EXPECT_EQ(fail, 0, "dlclose on via-service");
if (fail)
show_dlerror();
// Put things back to how they were.
zx_handle_t old2 = dl_set_loader_service(old);
EXPECT_EQ(old2, my_service, "unexpected previous service handle");
zx_handle_close(old2);
END_TEST;
}
bool clone_test(void) {
BEGIN_TEST;
zx_handle_t h = ZX_HANDLE_INVALID;
zx_status_t s = dl_clone_loader_service(&h);
EXPECT_EQ(s, ZX_OK, "unexpected return value from ioctl");
EXPECT_NE(h, ZX_HANDLE_INVALID, "invalid handle from ioctl");
zx_handle_close(h);
END_TEST;
}
void test_global_function(void) {}
static bool dladdr_unexported_test(void) {
BEGIN_TEST;
Dl_info info;
ASSERT_NE(dladdr(&test_global_function, &info), 0, "dladdr failed");
// This symbol is not exported to .dynsym, so it won't be found.
EXPECT_NULL(info.dli_sname, "unexpected symbol name");
EXPECT_NULL(info.dli_saddr, "unexpected symbol address");
END_TEST;
}
// TODO(dbort): Test that this process uses the system loader service by default
BEGIN_TEST_CASE(dlfcn_tests)
RUN_TEST(dlopen_vmo_test);
RUN_TEST(loader_service_test);
RUN_TEST(clone_test);
RUN_TEST(dladdr_unexported_test);
END_TEST_CASE(dlfcn_tests)