Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2592)

Unified Diff: android/linker/modern_linker_jni.cc

Issue 2043183002: Update to Chromium //base at Chromium commit 01cb97b2e09618bbc3a60c7348f0a844eea20547. (Closed) Base URL: https://github.com/domokit/base.git@master
Patch Set: Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « android/linker/modern_linker_jni.h ('k') | callback_internal.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: android/linker/modern_linker_jni.cc
diff --git a/android/linker/modern_linker_jni.cc b/android/linker/modern_linker_jni.cc
new file mode 100644
index 0000000000000000000000000000000000000000..86616360fe0bbd14efdb86587d96b69ead5f12c4
--- /dev/null
+++ b/android/linker/modern_linker_jni.cc
@@ -0,0 +1,595 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is the version of the Android-specific Chromium linker that uses
+// the Android M and later system linker to load libraries.
+
+// This source code *cannot* depend on anything from base/ or the C++
+// STL, to keep the final library small, and avoid ugly dependency issues.
+
+#include "modern_linker_jni.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <limits.h>
+#include <link.h>
+#include <string.h>
+
+#include "android_dlext.h"
+#include "linker_jni.h"
+
+#define PAGE_START(x) ((x)&PAGE_MASK)
+#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1))
+
+namespace chromium_android_linker {
+namespace {
+
+// Record of the Java VM passed to JNI_OnLoad().
+static JavaVM* s_java_vm = nullptr;
+
+// Get the CPU ABI string for which the linker is running.
+//
+// The returned string is used to construct the path to libchrome.so when
+// loading directly from APK.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// Returns the CPU ABI string for which the linker is running.
+jstring GetCpuAbi(JNIEnv* env, jclass clazz) {
+#if defined(__arm__) && defined(__ARM_ARCH_7A__)
+ static const char* kCurrentABI = "armeabi-v7a";
+#elif defined(__arm__)
+ static const char* kCurrentABI = "armeabi";
+#elif defined(__i386__)
+ static const char* kCurrentABI = "x86";
+#elif defined(__mips__)
+ static const char* kCurrentABI = "mips";
+#elif defined(__x86_64__)
+ static const char* kCurrentABI = "x86_64";
+#elif defined(__aarch64__)
+ static const char* kCurrentABI = "arm64-v8a";
+#else
+#error "Unsupported target abi"
+#endif
+ return env->NewStringUTF(kCurrentABI);
+}
+
+// Convenience wrapper around dlsym() on the main executable. Returns
+// the address of the requested symbol, or nullptr if not found. Status
+// is available from dlerror().
+void* Dlsym(const char* symbol_name) {
+ static void* handle = nullptr;
+
+ if (!handle)
+ handle = dlopen(nullptr, RTLD_NOW);
+
+ void* result = dlsym(handle, symbol_name);
+ return result;
+}
+
+// dl_iterate_phdr() wrapper, accessed via dlsym lookup. Done this way.
+// so that this code compiles for Android versions that are too early to
+// offer it. Checks in LibraryLoader.java should ensure that we
+// never reach here at runtime on Android versions that are too old to
+// supply dl_iterate_phdr; that is, earlier than Android M. Returns
+// false if no dl_iterate_phdr() is available, otherwise true with the
+// return value from dl_iterate_phdr() in |status|.
+bool DlIteratePhdr(int (*callback)(dl_phdr_info*, size_t, void*),
+ void* data,
+ int* status) {
+ using DlIteratePhdrCallback = int (*)(dl_phdr_info*, size_t, void*);
+ using DlIteratePhdrFunctionPtr = int (*)(DlIteratePhdrCallback, void*);
+ static DlIteratePhdrFunctionPtr function_ptr = nullptr;
+
+ if (!function_ptr) {
+ function_ptr =
+ reinterpret_cast<DlIteratePhdrFunctionPtr>(Dlsym("dl_iterate_phdr"));
+ if (!function_ptr) {
+ LOG_ERROR("dlsym: dl_iterate_phdr: %s", dlerror());
+ return false;
+ }
+ }
+
+ *status = (*function_ptr)(callback, data);
+ return true;
+}
+
+// Convenience struct wrapper round android_dlextinfo.
+struct AndroidDlextinfo {
+ AndroidDlextinfo(int flags,
+ void* reserved_addr,
+ size_t reserved_size,
+ int relro_fd) {
+ memset(&extinfo, 0, sizeof(extinfo));
+ extinfo.flags = flags;
+ extinfo.reserved_addr = reserved_addr;
+ extinfo.reserved_size = reserved_size;
+ extinfo.relro_fd = relro_fd;
+ }
+
+ android_dlextinfo extinfo;
+};
+
+// android_dlopen_ext() wrapper, accessed via dlsym lookup. Returns false
+// if no android_dlopen_ext() is available, otherwise true with the return
+// value from android_dlopen_ext() in |status|.
+bool AndroidDlopenExt(const char* filename,
+ int flag,
+ const AndroidDlextinfo* dlextinfo,
+ void** status) {
+ using DlopenExtFunctionPtr =
+ void* (*)(const char*, int, const android_dlextinfo*);
+ static DlopenExtFunctionPtr function_ptr = nullptr;
+
+ if (!function_ptr) {
+ function_ptr =
+ reinterpret_cast<DlopenExtFunctionPtr>(Dlsym("android_dlopen_ext"));
+ if (!function_ptr) {
+ LOG_ERROR("dlsym: android_dlopen_ext: %s", dlerror());
+ return false;
+ }
+ }
+
+ const android_dlextinfo* extinfo = &dlextinfo->extinfo;
+ LOG_INFO(
+ "android_dlopen_ext:"
+ " flags=0x%llx, reserved_addr=%p, reserved_size=%d, relro_fd=%d",
+ extinfo->flags, extinfo->reserved_addr, extinfo->reserved_size,
+ extinfo->relro_fd);
+
+ *status = (*function_ptr)(filename, flag, extinfo);
+ return true;
+}
+
+// Callback data for FindLoadedLibrarySize().
+struct CallbackData {
+ explicit CallbackData(void* address) : load_address(address), load_size(0) {}
+
+ const void* load_address;
+ size_t load_size;
+};
+
+// Callback for dl_iterate_phdr(). Read phdrs to identify whether or not
+// this library's load address matches the |load_address| passed in
+// |data|. If yes, pass back load size via |data|. A non-zero return value
+// terminates iteration.
+int FindLoadedLibrarySize(dl_phdr_info* info, size_t size UNUSED, void* data) {
+ CallbackData* callback_data = reinterpret_cast<CallbackData*>(data);
+
+ // Use max and min vaddr to compute the library's load size.
+ ElfW(Addr) min_vaddr = ~0;
+ ElfW(Addr) max_vaddr = 0;
+
+ bool is_matching = false;
+ for (size_t i = 0; i < info->dlpi_phnum; ++i) {
+ const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ // See if this segment's load address matches what we passed to
+ // android_dlopen_ext as extinfo.reserved_addr.
+ void* load_addr = reinterpret_cast<void*>(info->dlpi_addr + phdr->p_vaddr);
+ if (load_addr == callback_data->load_address)
+ is_matching = true;
+
+ if (phdr->p_vaddr < min_vaddr)
+ min_vaddr = phdr->p_vaddr;
+ if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
+ max_vaddr = phdr->p_vaddr + phdr->p_memsz;
+ }
+
+ // If this library matches what we seek, return its load size.
+ if (is_matching) {
+ callback_data->load_size = PAGE_END(max_vaddr) - PAGE_START(min_vaddr);
+ return true;
+ }
+
+ return false;
+}
+
+// Helper class for anonymous memory mapping.
+class ScopedAnonymousMmap {
+ public:
+ ScopedAnonymousMmap(void* addr, size_t size);
+
+ ~ScopedAnonymousMmap() { munmap(addr_, size_); }
+
+ void* GetAddr() const { return effective_addr_; }
+ void Release() {
+ addr_ = nullptr;
+ size_ = 0;
+ effective_addr_ = nullptr;
+ }
+
+ private:
+ void* addr_;
+ size_t size_;
+
+ // The effective_addr_ is the address seen by client code. It may or may
+ // not be the same as addr_, the real start of the anonymous mapping.
+ void* effective_addr_;
+};
+
+// ScopedAnonymousMmap constructor. |addr| is a requested mapping address, or
+// zero if any address will do, and |size| is the size of mapping required.
+ScopedAnonymousMmap::ScopedAnonymousMmap(void* addr, size_t size) {
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Increase size to extend the address reservation mapping so that it will
+ // also include a guard region from load_bias_ to start_addr. If loading
+ // at a fixed address, move our requested address back by the guard region
+ // size.
+ size += kBreakpadGuardRegionBytes;
+ if (addr) {
+ if (addr < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
+ LOG_ERROR("Fixed address %p is too low to accommodate Breakpad guard",
+ addr);
+ addr_ = MAP_FAILED;
+ size_ = 0;
+ return;
+ }
+ addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) -
+ kBreakpadGuardRegionBytes);
+ }
+ LOG_INFO("Added %d to size, for Breakpad guard", kBreakpadGuardRegionBytes);
+#endif
+
+ addr_ = mmap(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (addr_ != MAP_FAILED) {
+ size_ = size;
+ } else {
+ LOG_INFO("mmap failed: %s", strerror(errno));
+ size_ = 0;
+ }
+ effective_addr_ = addr_;
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // If we increased size to accommodate a Breakpad guard region, move
+ // the effective address, if valid, upwards by the size of the guard region.
+ if (addr_ == MAP_FAILED)
+ return;
+ if (addr_ < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
+ LOG_ERROR("Map address %p is too low to accommodate Breakpad guard", addr_);
+ effective_addr_ = MAP_FAILED;
+ } else {
+ effective_addr_ = reinterpret_cast<void*>(
+ reinterpret_cast<uintptr_t>(addr_) + kBreakpadGuardRegionBytes);
+ }
+#endif
+}
+
+// Helper for LoadLibrary(). Return the actual size of the library loaded
+// at |addr| in |load_size|. Returns false if the library appears not to
+// be loaded.
+bool GetLibraryLoadSize(void* addr, size_t* load_size) {
+ LOG_INFO("Called for %p", addr);
+
+ // Find the real load size for the library loaded at |addr|.
+ CallbackData callback_data(addr);
+ int status = 0;
+ if (!DlIteratePhdr(&FindLoadedLibrarySize, &callback_data, &status)) {
+ LOG_ERROR("No dl_iterate_phdr function found");
+ return false;
+ }
+ if (!status) {
+ LOG_ERROR("Failed to find library at address %p", addr);
+ return false;
+ }
+
+ *load_size = callback_data.load_size;
+ return true;
+}
+
+// Helper for LoadLibrary(). We reserve an address space larger than
+// needed. After library loading we want to trim that reservation to only
+// what is needed.
+bool ResizeReservedAddressSpace(void* addr,
+ size_t reserved_size,
+ size_t load_size) {
+ LOG_INFO("Called for %p, reserved %d, loaded %d", addr, reserved_size,
+ load_size);
+
+ if (reserved_size < load_size) {
+ LOG_ERROR("WARNING: library reservation was too small");
+ return true;
+ }
+
+ // Unmap the part of the reserved address space that is beyond the end of
+ // the loaded library data.
+ void* unmap =
+ reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + load_size);
+ size_t length = reserved_size - load_size;
+ if (munmap(unmap, length) == -1) {
+ LOG_ERROR("Failed to unmap %d at %p", static_cast<int>(length), unmap);
+ return false;
+ }
+
+ return true;
+}
+
+// Load a library with the chromium linker, using android_dlopen_ext().
+//
+// android_dlopen_ext() understands how to directly load from a zipfile,
+// based on the format of |dlopen_ext_path|. If it contains a "!/" separator
+// then the string indicates <zip_path>!/<file_path> and indicates the
+// file_path element within the zip file at zip_path. A library in a
+// zipfile must be uncompressed and page aligned. The library is expected
+// to be lib/<abi_tag>/crazy.<basename>. The <abi_tag> used will be the
+// same as the abi for this linker. The "crazy." prefix is included
+// so that the Android Package Manager doesn't extract the library into
+// /data/app-lib.
+//
+// If |dlopen_ext_path| contains no "!/" separator then android_dlopen_ext()
+// assumes that it is a normal path to a standalone library file.
+//
+// Loading the library will also call its JNI_OnLoad() method, which
+// shall register its methods. Note that lazy native method resolution
+// will _not_ work after this, because Dalvik uses the system's dlsym()
+// which won't see the new library, so explicit registration is mandatory.
+// Load a library with the chromium linker. This will also call its
+// JNI_OnLoad() method, which shall register its methods. Note that
+// lazy native method resolution will _not_ work after this, because
+// Dalvik uses the system's dlsym() which won't see the new library,
+// so explicit registration is mandatory.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
+// |load_address| is an explicit load address.
+// |relro_path| is the path to the file into which RELRO data is held.
+// |lib_info_obj| is a LibInfo handle used to communicate information
+// with the Java side.
+// Return true on success.
+jboolean LoadLibrary(JNIEnv* env,
+ jclass clazz,
+ jstring dlopen_ext_path,
+ jlong load_address,
+ jobject lib_info_obj) {
+ String dlopen_library_path(env, dlopen_ext_path);
+ LOG_INFO("Called for %s, at address 0x%llx", dlopen_library_path.c_str(),
+ load_address);
+
+ if (!IsValidAddress(load_address)) {
+ LOG_ERROR("Invalid address 0x%llx", load_address);
+ return false;
+ }
+
+ const size_t size = kAddressSpaceReservationSize;
+ void* wanted_addr = reinterpret_cast<void*>(load_address);
+
+ // Reserve the address space into which we load the library.
+ ScopedAnonymousMmap mapping(wanted_addr, size);
+ void* addr = mapping.GetAddr();
+ if (addr == MAP_FAILED) {
+ LOG_ERROR("Failed to reserve space for load");
+ return false;
+ }
+ if (wanted_addr && addr != wanted_addr) {
+ LOG_ERROR("Failed to obtain fixed address for load");
+ return false;
+ }
+
+ // Build dlextinfo to load the library into the reserved space, using
+ // the shared RELRO if supplied and if its start address matches addr.
+ int relro_fd = -1;
+ int flags = ANDROID_DLEXT_RESERVED_ADDRESS;
+ if (wanted_addr && lib_info_obj) {
+ void* relro_start;
+ s_lib_info_fields.GetRelroInfo(env, lib_info_obj,
+ reinterpret_cast<size_t*>(&relro_start),
+ nullptr, &relro_fd);
+ if (relro_fd != -1 && relro_start == addr) {
+ flags |= ANDROID_DLEXT_USE_RELRO;
+ }
+ }
+ AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
+
+ // Load the library into the reserved space.
+ const char* path = dlopen_library_path.c_str();
+ void* handle = nullptr;
+ if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
+ LOG_ERROR("No android_dlopen_ext function found");
+ return false;
+ }
+ if (handle == nullptr) {
+ LOG_ERROR("android_dlopen_ext: %s", dlerror());
+ return false;
+ }
+
+ // After loading, trim the mapping to match the library's actual size.
+ size_t load_size = 0;
+ if (!GetLibraryLoadSize(addr, &load_size)) {
+ LOG_ERROR("Unable to find size for load at %p", addr);
+ return false;
+ }
+ if (!ResizeReservedAddressSpace(addr, size, load_size)) {
+ LOG_ERROR("Unable to resize reserved address mapping");
+ return false;
+ }
+
+ // Locate and then call the loaded library's JNI_OnLoad() function. Check
+ // that it returns a usable JNI version.
+ using JNI_OnLoadFunctionPtr = int (*)(void* vm, void* reserved);
+ auto jni_onload =
+ reinterpret_cast<JNI_OnLoadFunctionPtr>(dlsym(handle, "JNI_OnLoad"));
+ if (jni_onload == nullptr) {
+ LOG_ERROR("dlsym: JNI_OnLoad: %s", dlerror());
+ return false;
+ }
+
+ int jni_version = (*jni_onload)(s_java_vm, nullptr);
+ if (jni_version < JNI_VERSION_1_4) {
+ LOG_ERROR("JNI version is invalid: %d", jni_version);
+ return false;
+ }
+
+ // Release mapping before returning so that we do not unmap reserved space.
+ mapping.Release();
+
+ // Note the load address and load size in the supplied libinfo object.
+ const size_t cast_addr = reinterpret_cast<size_t>(addr);
+ s_lib_info_fields.SetLoadInfo(env, lib_info_obj, cast_addr, load_size);
+
+ LOG_INFO("Success loading library %s", dlopen_library_path.c_str());
+ return true;
+}
+
+// Create a shared RELRO file for a library, using android_dlopen_ext().
+//
+// Loads the library similarly to LoadLibrary() above, by reserving address
+// space and then using android_dlopen_ext() to load into the reserved
+// area. Adds flags to android_dlopen_ext() to saved the library's RELRO
+// memory into the given file path, then unload the library and returns.
+//
+// Does not call JNI_OnLoad() or otherwise execute any code from the library.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
+// |load_address| is an explicit load address.
+// |relro_path| is the path to the file into which RELRO data is written.
+// |lib_info_obj| is a LibInfo handle used to communicate information
+// with the Java side.
+// Return true on success.
+jboolean CreateSharedRelro(JNIEnv* env,
+ jclass clazz,
+ jstring dlopen_ext_path,
+ jlong load_address,
+ jstring relro_path,
+ jobject lib_info_obj) {
+ String dlopen_library_path(env, dlopen_ext_path);
+ LOG_INFO("Called for %s, at address 0x%llx", dlopen_library_path.c_str(),
+ load_address);
+
+ if (!IsValidAddress(load_address) || load_address == 0) {
+ LOG_ERROR("Invalid address 0x%llx", load_address);
+ return false;
+ }
+
+ const size_t size = kAddressSpaceReservationSize;
+ void* wanted_addr = reinterpret_cast<void*>(load_address);
+
+ // Reserve the address space into which we load the library.
+ ScopedAnonymousMmap mapping(wanted_addr, size);
+ void* addr = mapping.GetAddr();
+ if (addr == MAP_FAILED) {
+ LOG_ERROR("Failed to reserve space for load");
+ return false;
+ }
+ if (addr != wanted_addr) {
+ LOG_ERROR("Failed to obtain fixed address for load");
+ return false;
+ }
+
+ // Open the shared RELRO file for write. Overwrites any prior content.
+ String shared_relro_path(env, relro_path);
+ const char* filepath = shared_relro_path.c_str();
+ unlink(filepath);
+ int relro_fd = open(filepath, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (relro_fd == -1) {
+ LOG_ERROR("open: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Use android_dlopen_ext() to create the shared RELRO.
+ const int flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO;
+ AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
+
+ const char* path = dlopen_library_path.c_str();
+ void* handle = nullptr;
+ if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
+ LOG_ERROR("No android_dlopen_ext function found");
+ close(relro_fd);
+ return false;
+ }
+ if (handle == nullptr) {
+ LOG_ERROR("android_dlopen_ext: %s", dlerror());
+ close(relro_fd);
+ return false;
+ }
+
+ // Unload the library from this address. The reserved space is
+ // automatically unmapped on exit from this function.
+ dlclose(handle);
+
+ // Reopen the shared RELFO fd in read-only mode. This ensures that nothing
+ // can write to it through the RELRO fd that we return in libinfo.
+ close(relro_fd);
+ relro_fd = open(filepath, O_RDONLY);
+ if (relro_fd == -1) {
+ LOG_ERROR("open: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Delete the directory entry for the RELRO file. The fd we hold ensures
+ // that its data remains intact.
+ if (unlink(filepath) == -1) {
+ LOG_ERROR("unlink: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Note the shared RELRO fd in the supplied libinfo object. In this
+ // implementation the RELRO start is set to the library's load address,
+ // and the RELRO size is unused.
+ const size_t cast_addr = reinterpret_cast<size_t>(addr);
+ s_lib_info_fields.SetRelroInfo(env, lib_info_obj, cast_addr, 0, relro_fd);
+
+ LOG_INFO("Success creating shared RELRO %s", shared_relro_path.c_str());
+ return true;
+}
+
+const JNINativeMethod kNativeMethods[] = {
+ {"nativeGetCpuAbi",
+ "("
+ ")"
+ "Ljava/lang/String;",
+ reinterpret_cast<void*>(&GetCpuAbi)},
+ {"nativeLoadLibrary",
+ "("
+ "Ljava/lang/String;"
+ "J"
+ "Lorg/chromium/base/library_loader/Linker$LibInfo;"
+ ")"
+ "Z",
+ reinterpret_cast<void*>(&LoadLibrary)},
+ {"nativeCreateSharedRelro",
+ "("
+ "Ljava/lang/String;"
+ "J"
+ "Ljava/lang/String;"
+ "Lorg/chromium/base/library_loader/Linker$LibInfo;"
+ ")"
+ "Z",
+ reinterpret_cast<void*>(&CreateSharedRelro)},
+};
+
+} // namespace
+
+bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
+ LOG_INFO("Entering");
+
+ // Register native methods.
+ jclass linker_class;
+ if (!InitClassReference(env, "org/chromium/base/library_loader/ModernLinker",
+ &linker_class))
+ return false;
+
+ LOG_INFO("Registering native methods");
+ env->RegisterNatives(linker_class, kNativeMethods,
+ sizeof(kNativeMethods) / sizeof(kNativeMethods[0]));
+
+ // Record the Java VM handle.
+ s_java_vm = vm;
+
+ return true;
+}
+
+} // namespace chromium_android_linker
« no previous file with comments | « android/linker/modern_linker_jni.h ('k') | callback_internal.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698