| // 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 <launchpad/loader-service.h> |
| #include <zircon/device/dmctl.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_loader_service(void* arg, uint32_t load_op, |
| zx_handle_t request_handle, |
| const char* name, |
| zx_handle_t* out) { |
| ++my_loader_service_calls; |
| |
| EXPECT_EQ(request_handle, ZX_HANDLE_INVALID, |
| "called with a request handle"); |
| |
| 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; |
| } |
| EXPECT_EQ(load_op, (uint32_t) LOADER_SVC_OP_LOAD_OBJECT, |
| "called with unexpected load op"); |
| if (load_op != (uint32_t) LOADER_SVC_OP_LOAD_OBJECT) { |
| unittest_printf(" saw %" PRIu32 ", expected %" PRIu32, load_op, |
| LOADER_SVC_OP_LOAD_OBJECT); |
| return ZX_HANDLE_INVALID; |
| } |
| |
| zx_handle_t vmo = ZX_HANDLE_INVALID; |
| zx_status_t status = launchpad_vmo_from_file(arg, &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 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. |
| zx_handle_t my_service; |
| zx_status_t status = loader_service_simple(&my_loader_service, (void*)TEST_ACTUAL_NAME, &my_service); |
| EXPECT_EQ(status, ZX_OK, "fdio_loader_service"); |
| |
| // 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; |
| } |
| |
| #define DMCTL_PATH "/dev/misc/dmctl" |
| |
| bool ioctl_test(void) { |
| BEGIN_TEST; |
| |
| int fd = open(DMCTL_PATH, O_RDONLY); |
| ASSERT_GE(fd, 0, "can't open " DMCTL_PATH); |
| |
| zx_handle_t h = ZX_HANDLE_INVALID; |
| ssize_t s = ioctl_dmctl_get_loader_service_channel(fd, &h); |
| close(fd); |
| |
| EXPECT_EQ(s, (ssize_t)sizeof(zx_handle_t), |
| "unexpected return value from ioctl"); |
| EXPECT_NE(h, ZX_HANDLE_INVALID, "invalid handle from ioctl"); |
| |
| zx_handle_close(h); |
| |
| 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(ioctl_test); |
| END_TEST_CASE(dlfcn_tests) |
| |
| int main(int argc, char** argv) { |
| bool success = unittest_run_all_tests(argc, argv); |
| return success ? 0 : -1; |
| } |