From 1b20dafdbe65e43b9f4c95057e8482380833ea91 Mon Sep 17 00:00:00 2001 From: Dmitriy Ivanov Date: Mon, 19 May 2014 15:06:58 -0700 Subject: [PATCH] Add RTLD_NODELETE flag support Bug: https://code.google.com/p/android/issues/detail?id=64069 Change-Id: Ie5f90482feae86391172be4b32d6cb7d76f446fb --- libc/include/dlfcn.h | 1 + linker/linker.cpp | 16 ++++- linker/linker.h | 18 ++--- tests/dlfcn_test.cpp | 80 +++++++++++++++++++++++ tests/libs/Android.mk | 29 ++++++++ tests/libs/dlopen_nodelete_1.cpp | 31 +++++++++ tests/libs/dlopen_nodelete_2.cpp | 31 +++++++++ tests/libs/dlopen_nodelete_dt_flags_1.cpp | 30 +++++++++ 8 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 tests/libs/dlopen_nodelete_1.cpp create mode 100644 tests/libs/dlopen_nodelete_2.cpp create mode 100644 tests/libs/dlopen_nodelete_dt_flags_1.cpp diff --git a/libc/include/dlfcn.h b/libc/include/dlfcn.h index 8dde08cf5..afa76878f 100644 --- a/libc/include/dlfcn.h +++ b/libc/include/dlfcn.h @@ -64,6 +64,7 @@ enum { RTLD_GLOBAL = 2, #endif RTLD_NOLOAD = 4, + RTLD_NODELETE = 0x01000, }; #if defined (__LP64__) diff --git a/linker/linker.cpp b/linker/linker.cpp index 4f42c8215..d86379eb4 100644 --- a/linker/linker.cpp +++ b/linker/linker.cpp @@ -976,6 +976,11 @@ static soinfo* find_library(const char* name, int rtld_flags, const android_dlex } static void soinfo_unload(soinfo* si) { + if (!si->can_unload()) { + TRACE("not unloading '%s' - the binary is flagged with NODELETE", si->name); + return; + } + if (si->ref_count == 1) { TRACE("unloading '%s'", si->name); si->CallDestructors(); @@ -1034,7 +1039,7 @@ void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path) { } soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) { - if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NOLOAD)) != 0) { + if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) { DL_ERR("invalid flags to dlopen: %x", flags); return nullptr; } @@ -1794,6 +1799,9 @@ const char* soinfo::get_string(ElfW(Word) index) const { return strtab + index; } +bool soinfo::can_unload() const { + return (rtld_flags & (RTLD_NODELETE | RTLD_GLOBAL)) == 0; +} /* Force any of the closed stdin, stdout and stderr to be associated with /dev/null. */ static int nullify_closed_stdio() { @@ -2062,9 +2070,13 @@ bool soinfo::PrelinkImage() { if ((d->d_un.d_val & DF_1_GLOBAL) != 0) { rtld_flags |= RTLD_GLOBAL; } + + if ((d->d_un.d_val & DF_1_NODELETE) != 0) { + rtld_flags |= RTLD_NODELETE; + } // TODO: Implement other flags - if ((d->d_un.d_val & ~(DF_1_NOW | DF_1_GLOBAL)) != 0) { + if ((d->d_un.d_val & ~(DF_1_NOW | DF_1_GLOBAL | DF_1_NODELETE)) != 0) { DL_WARN("Unsupported flags DT_FLAGS_1=%p", reinterpret_cast(d->d_un.d_val)); } break; diff --git a/linker/linker.h b/linker/linker.h index f6e3a48ba..c8e5c68eb 100644 --- a/linker/linker.h +++ b/linker/linker.h @@ -134,7 +134,7 @@ struct soinfo { #endif soinfo* next; - unsigned flags; + uint32_t flags; private: const char* strtab; @@ -143,8 +143,8 @@ struct soinfo { size_t nbucket; size_t nchain; - unsigned* bucket; - unsigned* chain; + uint32_t* bucket; + uint32_t* chain; #if defined(__mips__) || !defined(__LP64__) // This is only used by mips and mips64, but needs to be here for @@ -179,12 +179,12 @@ struct soinfo { #if defined(__arm__) // ARM EABI section used for stack unwinding. - unsigned* ARM_exidx; + uint32_t* ARM_exidx; size_t ARM_exidx_count; #elif defined(__mips__) - unsigned mips_symtabno; - unsigned mips_local_gotno; - unsigned mips_gotsym; + uint32_t mips_symtabno; + uint32_t mips_local_gotno; + uint32_t mips_gotsym; #endif size_t ref_count; @@ -225,10 +225,12 @@ struct soinfo { ElfW(Addr) resolve_symbol_address(ElfW(Sym)* s); const char* get_string(ElfW(Word) index) const; + bool can_unload() const; bool inline has_min_version(uint32_t min_version) const { return (flags & FLAG_NEW_SOINFO) != 0 && version >= min_version; } + private: void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse); void CallFunction(const char* function_name, linker_function_t function); @@ -258,7 +260,7 @@ struct soinfo { friend soinfo* get_libdl_info(); }; -extern soinfo* get_libdl_info(); +soinfo* get_libdl_info(); void do_android_get_LD_LIBRARY_PATH(char*, size_t); void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path); diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp index 1bf186b4b..1d7c29a54 100644 --- a/tests/dlfcn_test.cpp +++ b/tests/dlfcn_test.cpp @@ -232,10 +232,15 @@ TEST(dlfcn, dlopen_check_rtld_global) { ASSERT_TRUE(sym == nullptr); void* handle = dlopen("libtest_simple.so", RTLD_NOW | RTLD_GLOBAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); sym = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); ASSERT_TRUE(sym != nullptr) << dlerror(); ASSERT_TRUE(reinterpret_cast(sym)()); dlclose(handle); + + // RTLD_GLOBAL implies RTLD_NODELETE, let's check that + void* sym_after_dlclose = dlsym(RTLD_DEFAULT, "dlopen_testlib_simple_func"); + ASSERT_EQ(sym, sym_after_dlclose); } // libtest_with_dependency_loop.so -> libtest_with_dependency_loop_a.so -> @@ -263,6 +268,81 @@ TEST(dlfcn, dlopen_check_loop) { #endif } +TEST(dlfcn, dlopen_nodelete) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_1.so", RTLD_NOW | RTLD_NODELETE); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast(dlsym(handle, "dlopen_nodelete_1_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + uint32_t* taxicab_number = reinterpret_cast(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_TRUE(taxicab_number != nullptr) << dlerror(); + ASSERT_EQ(1729U, *taxicab_number); + *taxicab_number = 2; + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); + + uint32_t* taxicab_number_after_dlclose = reinterpret_cast(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_EQ(taxicab_number_after_dlclose, taxicab_number); + ASSERT_EQ(2U, *taxicab_number_after_dlclose); + + + handle = dlopen("libtest_nodelete_1.so", RTLD_NOW); + uint32_t* taxicab_number2 = reinterpret_cast(dlsym(handle, "dlopen_nodelete_1_taxicab_number")); + ASSERT_EQ(taxicab_number2, taxicab_number); + + ASSERT_EQ(2U, *taxicab_number2); + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); +} + +TEST(dlfcn, dlopen_nodelete_on_second_dlopen) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_2.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast(dlsym(handle, "dlopen_nodelete_2_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + uint32_t* taxicab_number = reinterpret_cast(dlsym(handle, "dlopen_nodelete_2_taxicab_number")); + ASSERT_TRUE(taxicab_number != nullptr) << dlerror(); + + ASSERT_EQ(1729U, *taxicab_number); + *taxicab_number = 2; + + // This RTLD_NODELETE should be ignored + void* handle1 = dlopen("libtest_nodelete_2.so", RTLD_NOW | RTLD_NODELETE); + ASSERT_TRUE(handle1 != nullptr) << dlerror(); + ASSERT_EQ(handle, handle1); + + dlclose(handle1); + dlclose(handle); + + ASSERT_TRUE(is_unloaded); +} + +TEST(dlfcn, dlopen_nodelete_dt_flags_1) { + static bool is_unloaded = false; + + void* handle = dlopen("libtest_nodelete_dt_flags_1.so", RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + void (*set_unload_flag_ptr)(bool*); + set_unload_flag_ptr = reinterpret_cast(dlsym(handle, "dlopen_nodelete_dt_flags_1_set_unload_flag_ptr")); + ASSERT_TRUE(set_unload_flag_ptr != nullptr) << dlerror(); + set_unload_flag_ptr(&is_unloaded); + + dlclose(handle); + ASSERT_TRUE(!is_unloaded); +} + + TEST(dlfcn, dlopen_failure) { void* self = dlopen("/does/not/exist", RTLD_NOW); ASSERT_TRUE(self == NULL); diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk index bc5f1086d..c98fea8d9 100644 --- a/tests/libs/Android.mk +++ b/tests/libs/Android.mk @@ -103,6 +103,35 @@ libtest_simple_src_files := \ module := libtest_simple include $(LOCAL_PATH)/Android.build.testlib.mk +# ----------------------------------------------------------------------------- +# Library used by dlfcn nodelete tests +# ----------------------------------------------------------------------------- +libtest_nodelete_1_src_files := \ + dlopen_nodelete_1.cpp + +module := libtest_nodelete_1 +include $(LOCAL_PATH)/Android.build.testlib.mk + +# ----------------------------------------------------------------------------- +# Library used by dlfcn nodelete tests +# ----------------------------------------------------------------------------- +libtest_nodelete_2_src_files := \ + dlopen_nodelete_2.cpp + +module := libtest_nodelete_2 +include $(LOCAL_PATH)/Android.build.testlib.mk + +# ----------------------------------------------------------------------------- +# Library used by dlfcn nodelete tests +# ----------------------------------------------------------------------------- +libtest_nodelete_dt_flags_1_src_files := \ + dlopen_nodelete_dt_flags_1.cpp + +libtest_nodelete_dt_flags_1_ldflags := -Wl,-z,nodelete + +module := libtest_nodelete_dt_flags_1 +include $(LOCAL_PATH)/Android.build.testlib.mk + # ----------------------------------------------------------------------------- # Libraries used by dlfcn tests to verify correct load order: # libtest_check_order_2_right.so diff --git a/tests/libs/dlopen_nodelete_1.cpp b/tests/libs/dlopen_nodelete_1.cpp new file mode 100644 index 000000000..943897815 --- /dev/null +++ b/tests/libs/dlopen_nodelete_1.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +uint32_t dlopen_nodelete_1_taxicab_number = 1729; +static bool* unload_flag_ptr = nullptr; + +extern "C" void dlopen_nodelete_1_set_unload_flag_ptr(bool* ptr) { + unload_flag_ptr = ptr; +} + +static void __attribute__((destructor)) unload_guard() { + if (unload_flag_ptr != nullptr) { + *unload_flag_ptr = true; + } +} diff --git a/tests/libs/dlopen_nodelete_2.cpp b/tests/libs/dlopen_nodelete_2.cpp new file mode 100644 index 000000000..b5ab5c1ae --- /dev/null +++ b/tests/libs/dlopen_nodelete_2.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +uint32_t dlopen_nodelete_2_taxicab_number = 1729; +static bool* unload_flag_ptr = nullptr; + +extern "C" void dlopen_nodelete_2_set_unload_flag_ptr(bool* ptr) { + unload_flag_ptr = ptr; +} + +static void __attribute__((destructor)) unload_guard() { + if (unload_flag_ptr != nullptr) { + *unload_flag_ptr = true; + } +} diff --git a/tests/libs/dlopen_nodelete_dt_flags_1.cpp b/tests/libs/dlopen_nodelete_dt_flags_1.cpp new file mode 100644 index 000000000..39c0a7ea6 --- /dev/null +++ b/tests/libs/dlopen_nodelete_dt_flags_1.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +static bool* unload_flag_ptr = nullptr; + +extern "C" void dlopen_nodelete_dt_flags_1_set_unload_flag_ptr(bool* ptr) { + unload_flag_ptr = ptr; +} + +static void __attribute__((destructor)) unload_guard() { + if (unload_flag_ptr != nullptr) { + *unload_flag_ptr = true; + } +}