本文将一步步解析 System.getProperty("java.library.path")Android 9.0 中的源码实现。话不多说开干。

源码分析

首先,来分析下 System.getProperty() 函数的实现。

libcore/ojluni/src/main/java/java/lang/System.java

public static String getProperty(String key) {    checkKey(key);    SecurityManager sm = getSecurityManager();    if (sm != null) {        sm.checkPropertyAccess(key);    }//主要是调用了 props 的 getProperty() 方法。    return props.getProperty(key);}

getProperty 其实取得是 props 中的值,所以我们只要知道 props 什么时候赋值即可。

static {//props 初始化    unchangeableProps = initUnchangeableSystemProperties(); //初始化一些不可修改的系统属性,"java.library.path"也在其中    props = initProperties();...}private static Properties props; //全部属性包括不可修改的属性private static Properties unchangeableProps; //不可以修改的属性private static native String[] specialProperties();//顾名思义,就是系统默认的属性就不会去修改它的值,这里的默认属性就是不可修改的属性。static final class PropertiesWithNonOverrideableDefaults extends Properties {    PropertiesWithNonOverrideableDefaults(Properties defaults) {        super(defaults);    }    @Override    public Object put(Object key, Object value) {        if (defaults.containsKey(key)) { //如果是默认属性就直接返回默认属性的值            logE("Ignoring attempt to set property \"" + key +                    "\" to value \"" + value + "\".");            return defaults.get(key);        }        return super.put(key, value);    }    @Override    public Object remove(Object key) {        if (defaults.containsKey(key)) { //如果是默认属性就返回 null            logE("Ignoring attempt to remove property \"" + key + "\".");            return null;        }        return super.remove(key);    }}//将 assignments 中的xxx=yyy分离出来,分别存入对应的key(xxx)和value(yyy)中private static void parsePropertyAssignments(Properties p, String[] assignments) {    for (String assignment : assignments) {        int split = assignment.indexOf('=');        String key = assignment.substring(0, split);        String value = assignment.substring(split + 1);        p.put(key, value);    }}//初始化一些系统不可修改的属性值private static Properties initUnchangeableSystemProperties() {    VMRuntime runtime = VMRuntime.getRuntime();    Properties p = new Properties();    // Set non-static properties.    p.put("java.boot.class.path", runtime.bootClassPath());    p.put("java.class.path", runtime.classPath());    // TODO: does this make any sense? Should we just leave java.home unset?    String javaHome = getenv("JAVA_HOME");    if (javaHome == null) {        javaHome = "/system";    }    p.put("java.home", javaHome);    p.put("java.vm.version", runtime.vmVersion());    try {        StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid());        p.put("user.name", passwd.pw_name);    } catch (ErrnoException exception) {        throw new AssertionError(exception);    }    StructUtsname info = Libcore.os.uname();    p.put("os.arch", info.machine);    if (p.get("os.name") != null && !p.get("os.name").equals(info.sysname)) {        logE("Wrong compile-time assumption for os.name: " + p.get("os.name") + " vs " +                info.sysname);        p.put("os.name", info.sysname);    }    p.put("os.version", info.release);    // Android-added: Undocumented properties that exist only on Android.    p.put("android.icu.library.version", ICU.getIcuVersion());    p.put("android.icu.unicode.version", ICU.getUnicodeVersion());    p.put("android.icu.cldr.version", ICU.getCldrVersion());    // Property override for ICU4J : this is the location of the ICU4C data. This    // is prioritized over the properties in ICUConfig.properties. The issue with using    // that is that it doesn't play well with jarjar and it needs complicated build rules    // to change its default value.    String icuDataPath = TimeZoneDataFiles.generateIcuDataPath();    p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath);//"java.library.path" 的值在 specialProperties() 中获取    //specialProperties()={"user.dir=xxx", "android.zlib.version=yyy", "android.openssl.version=mmm", "java.library.path=nnn"}    parsePropertyAssignments(p, specialProperties());    // Override built-in properties with settings from the command line.    // Note: it is not possible to override hardcoded values.    parsePropertyAssignments(p, runtime.properties());    // Set static hardcoded properties.    // These come last, as they must be guaranteed to agree with what a backend compiler    // may assume when compiling the boot image on Android.    for (String[] pair : AndroidHardcodedSystemProperties.STATIC_PROPERTIES) {        if (p.containsKey(pair[0])) {            logE("Ignoring command line argument: -D" + pair[0]);        }        if (pair[1] == null) {            p.remove(pair[0]);        } else {            p.put(pair[0], pair[1]);        }    }    return p;}private static Properties initProperties() {    Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);    setDefaultChangeableProperties(p);    return p;}private static Properties setDefaultChangeableProperties(Properties p) {    if (!unchangeableProps.containsKey("java.io.tmpdir")) {        p.put("java.io.tmpdir", "/tmp");    }    if (!unchangeableProps.containsKey("user.home")) {        p.put("user.home", "");    }    return p;}

specialPropertiesnative 方法。它在 C 层对应的就是 System_specialProperties 函数,它的实现其实也就是保存几个固定的属性值,分别是 "user.dir""android.zlib.version""android.openssl.version""java.library.path"。这里面就包括了我们要找的 "java.library.path" 属性。

libcore/ojluni/src/main/native/System.c

#define NATIVE_METHOD(className, functionName, signature) \{ #functionName, signature, (void*)(className ## _ ## functionName) }static JNINativeMethod gMethods[] = {...    NATIVE_METHOD(System, specialProperties, "()[Ljava/lang/String;"), //根据NATIVE_METHOD的宏定义,这里实际就是绑定的java层的specialProperties对应的就是c层的System_specialProperties这个函数。    ...};void register_java_lang_System(JNIEnv* env) {jniRegisterNativeMethods(env, "java/lang/System", gMethods, NELEM(gMethods));}static jobjectArray System_specialProperties(JNIEnv* env, jclass ignored) {    jclass stringClass = (*env)->FindClass(env, "java/lang/String");    jobjectArray result = (*env)->NewObjectArray(env, 4, stringClass, NULL);    char path[PATH_MAX];    char* process_path = getcwd(path, sizeof(path));    char user_dir[PATH_MAX + 10] = "user.dir=";    strncat(user_dir, process_path, PATH_MAX);    jstring user_dir_str = (*env)->NewStringUTF(env, user_dir);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    (*env)->SetObjectArrayElement(env, result, 0, user_dir_str);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    jstring zlib_str = (*env)->NewStringUTF(env, "android.zlib.version=" ZLIB_VERSION);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    (*env)->SetObjectArrayElement(env, result, 1, zlib_str);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    jstring ssl_str = (*env)->NewStringUTF(env, "android.openssl.version=" OPENSSL_VERSION_TEXT);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    (*env)->SetObjectArrayElement(env, result, 2, ssl_str);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }//adb shell env LD_LIBRARY_PATH 为空    const char* library_path = getenv("LD_LIBRARY_PATH");#if defined(__ANDROID__)    if (library_path == NULL) {    //这里才是真正获取 java.library.path 的值的地方        android_get_LD_LIBRARY_PATH(path, sizeof(path));        library_path = path;    }#endif    if (library_path == NULL) {        library_path = "";    }    char* java_path = malloc(strlen("java.library.path=") + strlen(library_path) + 1);    strcpy(java_path, "java.library.path=");    strcat(java_path, library_path);    jstring java_path_str = (*env)->NewStringUTF(env, java_path);    free((void*)java_path);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    (*env)->SetObjectArrayElement(env, result, 3, java_path_str);    if ((*env)->ExceptionCheck(env)) {        return NULL;    }    return result;}

"java.library.path" 的属性值首先通过 getenv("LD_LIBRARY_PATH") 获取,如果为 null,就从 android_get_LD_LIBRARY_PATH(path, sizeof(path)) 获取。我们通过 adb shell env LD_LIBRARY_PATH 命令控制其为 null,所以 “java.library.path” 的属性值是从 android_get_LD_LIBRARY_PATH 方法中得到的。

bionic/libdl/libdl.cpp

__attribute__((__weak__))void android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {  __loader_android_get_LD_LIBRARY_PATH(buffer, buffer_size);}

bionic/linker/dlfcn.cpp

void __loader_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {  ScopedPthreadMutexLocker locker(&g_dl_mutex);  do_android_get_LD_LIBRARY_PATH(buffer, buffer_size);}

android_get_LD_LIBRARY_PATH 最终调用的是 do_android_get_LD_LIBRARY_PATH 方法。而它取的是 g_default_namespace 中的 get_default_library_paths 的值。所以,我们只要知道 g_default_namespace 中的 default_library_paths 什么时候赋值即可。

bionic/linker/linker.cpp

void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {    const auto& default_ld_paths = g_default_namespace.get_default_library_paths();    size_t required_size = 0;    for (const auto& path : default_ld_paths) {    required_size += path.size() + 1;    }    if (buffer_size < required_size) {    async_safe_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "                     "buffer len %zu, required len %zu", buffer_size, required_size);    }    char* end = buffer;    for (size_t i = 0; i < default_ld_paths.size(); ++i) {    if (i > 0) *end++ = ':';    end = stpcpy(end, default_ld_paths[i].c_str());    }}std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path) {    ...//ld_config_file_path="/system/etc/ld.config.28.txt"    std::string ld_config_file_path = get_ld_config_file_path();//executable_path=/proc/self/exe->/system/bin/toybox    if (!Config::read_binary_config(ld_config_file_path.c_str(),                                  executable_path,                                  g_is_asan,                                  &config,                                  &error_msg)) {    if (!error_msg.empty()) {      DL_WARN("Warning: couldn't read \"%s\" for \"%s\" (using default configuration instead): %s",              ld_config_file_path.c_str(),              executable_path,              error_msg.c_str());    }    config = nullptr;    }    if (config == nullptr) {    return init_default_namespace_no_config(g_is_asan);    }    const auto& namespace_configs = config->namespace_configs();    std::unordered_map<std::string, android_namespace_t*> namespaces;    // 1. Initialize default namespace    const NamespaceConfig* default_ns_config = config->default_namespace_config();    g_default_namespace.set_isolated(default_ns_config->isolated());    //default_ns_config->search_paths()="/system/lib64"    //default_library_paths 被 namespace 为 default 的 config 中的 search_paths 赋值    g_default_namespace.set_default_library_paths(default_ns_config->search_paths());    g_default_namespace.set_permitted_paths(default_ns_config->permitted_paths());    ...    return created_namespaces;}

g_default_namespacedefault_library_paths 有两个地方赋值,分别是 init_default_namespace_no_configinit_default_namespaces。如果有 “/system/etc/ld.config.*“相关的配置文件,就从配置文件中解析出来。否则就从 init_default_namespace_no_config 中获取。我的系统存在”/system/etc/ld.config.28.txt”,所以 default_library_paths 的值来自 ld.config.28.txt 文件中。ld.config.28.txt 文件的解析在 read_binary_config 中处理。

bionic/linker/linker_config.cpp

static constexpr const char* kDefaultConfigName = "default";static constexpr const char* kPropertyAdditionalNamespaces = "additional.namespaces";bool Config::read_binary_config(const char* ld_config_file_path,                                      const char* binary_realpath,                                      bool is_asan,                                      const Config** config,                                      std::string* error_msg) {    ...    namespace_configs[kDefaultConfigName] = g_config.create_namespace_config(kDefaultConfigName);//additional_namespaces=sphal,vndk,rs    std::vector<std::string> additional_namespaces = properties.get_strings(kPropertyAdditionalNamespaces);    for (const auto& name : additional_namespaces) {    namespace_configs[name] = g_config.create_namespace_config(name);    }    ...    for (auto ns_config_it : namespace_configs) {        auto& name = ns_config_it.first;        NamespaceConfig* ns_config = ns_config_it.second;        std::string property_name_prefix = std::string("namespace.") + name;        ...        // search paths are resolved (canonicalized). This is required mainly for        // the case when /vendor is a symlink to /system/vendor, which is true for        // non Treble-ized legacy devices.        ns_config->set_search_paths(properties.get_paths(property_name_prefix + ".search.paths", true));        // However, for permitted paths, we are not required to resolve the paths        // since they are only set for isolated namespaces, which implies the device        // is Treble-ized (= /vendor is not a symlink to /system/vendor).        // In fact, the resolving is causing an unexpected side effect of selinux        // denials on some executables which are not allowed to access some of the        // permitted paths.        ns_config->set_permitted_paths(properties.get_paths(property_name_prefix + ".permitted.paths", false));    }    failure_guard.Disable();    *config = &g_config;    return true;}

config 的 set_search_pathsnamespace.xxx.search.paths 赋值。我们主要是想知道default的search.paths 所以,namespace.default.search.paths = /system/lib64:/system/product/lib64。 因为我的系统是64的,如果是32的就是 /system/lib。同时,/system/product/lib64 在系统中不存在。所以,namespace.default.search.paths 的最终值是 /system/lib64。所以,g_default_namespaceset_default_library_paths值就是为 /system/lib64

/system/etc/ld.config.28.txt

# Copyright (C) 2017 The Android Open Source Project## Bionic loader config file.## Don't change the order here. The first pattern that matches with the# absolute path of an executable is selected.dir.system = /system/bin/dir.system = /system/xbin/dir.system = /system/product/bin/dir.vendor = /odm/bin/dir.vendor = /vendor/bin/dir.vendor = /data/nativetest/odmdir.vendor = /data/nativetest64/odmdir.vendor = /data/benchmarktest/odmdir.vendor = /data/benchmarktest64/odmdir.vendor = /data/nativetest/vendordir.vendor = /data/nativetest64/vendordir.vendor = /data/benchmarktest/vendordir.vendor = /data/benchmarktest64/vendordir.system = /data/nativetestdir.system = /data/nativetest64dir.system = /data/benchmarktestdir.system = /data/benchmarktest64dir.postinstall = /postinstall[system]additional.namespaces = sphal,vndk,rs################################################################################ "default" namespace## Framework-side code runs in this namespace. Libs from /vendor partition# can't be loaded in this namespace.###############################################################################namespace.default.isolated = truenamespace.default.search.paths  = /system/${LIB}namespace.default.search.paths += /system/product/${LIB}# We can't have entire /system/${LIB} as permitted paths because doing so# makes it possible to load libs in /system/${LIB}/vndk* directories by# their absolute paths (e.g. dlopen("/system/lib/vndk/libbase.so");).# VNDK libs are built with previous versions of Android and thus must not be# loaded into this namespace where libs built with the current version of# Android are loaded. Mixing the two types of libs in the same namespace can# cause unexpected problem.namespace.default.permitted.paths  = /system/${LIB}/drmnamespace.default.permitted.paths += /system/${LIB}/extractorsnamespace.default.permitted.paths += /system/${LIB}/hwnamespace.default.permitted.paths += /system/product/${LIB}# These are where odex files are located. libart has to be able to dlopen the filesnamespace.default.permitted.paths += /system/frameworknamespace.default.permitted.paths += /system/appnamespace.default.permitted.paths += /system/priv-appnamespace.default.permitted.paths += /vendor/frameworknamespace.default.permitted.paths += /vendor/appnamespace.default.permitted.paths += /vendor/priv-appnamespace.default.permitted.paths += /odm/frameworknamespace.default.permitted.paths += /odm/appnamespace.default.permitted.paths += /odm/priv-appnamespace.default.permitted.paths += /oem/appnamespace.default.permitted.paths += /system/product/frameworknamespace.default.permitted.paths += /system/product/appnamespace.default.permitted.paths += /system/product/priv-appnamespace.default.permitted.paths += /datanamespace.default.permitted.paths += /mnt/expandnamespace.default.asan.search.paths  = /data/asan/system/${LIB}namespace.default.asan.search.paths +=           /system/${LIB}namespace.default.asan.search.paths += /data/asan/product/${LIB}namespace.default.asan.search.paths +=           /product/${LIB}namespace.default.asan.permitted.paths  = /datanamespace.default.asan.permitted.paths += /system/${LIB}/drmnamespace.default.asan.permitted.paths += /system/${LIB}/extractorsnamespace.default.asan.permitted.paths += /system/${LIB}/hwnamespace.default.asan.permitted.paths += /system/frameworknamespace.default.asan.permitted.paths += /system/appnamespace.default.asan.permitted.paths += /system/priv-appnamespace.default.asan.permitted.paths += /vendor/frameworknamespace.default.asan.permitted.paths += /vendor/appnamespace.default.asan.permitted.paths += /vendor/priv-appnamespace.default.asan.permitted.paths += /odm/frameworknamespace.default.asan.permitted.paths += /odm/appnamespace.default.asan.permitted.paths += /odm/priv-appnamespace.default.asan.permitted.paths += /oem/appnamespace.default.asan.permitted.paths += /system/product/${LIB}namespace.default.asan.permitted.paths += /system/product/frameworknamespace.default.asan.permitted.paths += /system/product/appnamespace.default.asan.permitted.paths += /system/product/priv-appnamespace.default.asan.permitted.paths += /mnt/expand...

通过上面的源码分析知道 android_get_LD_LIBRARY_PATH 获取到的路径为 /system/lib64。 所以,java.library.path 的值就是 /system/lib64

总结

系统为了获取 java.library.path 的值也是饶了和多圈子的。只是难为人啊。只想说一句:“Read the fuck source code”。
理一下 java.library.path 的赋值流程。
首先,System 初始化给 java.library.path 赋值。

System.java->initUnchangeableSystemPropertiesSystem.c->specialProperties    linker.cpp->do_android_get_LD_LIBRARY_PATH    init_default_namespace    if (no config)    init_default_namespace_no_config     kDefaultLdPaths    else    default_config->search_paths

然后,getProperty 直接取 java.library.path 的值即可。

更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. Android(安卓)输入系统(二)EventHub
  3. android 实现ImageView按压效果和解决背景图片拉申问题
  4. 在自定义view中获取android layout_width等属性值
  5. android sdk没有64位linux版本
  6. Android点击通知栏 ,移除通知
  7. android excel读写
  8. 整理Android(安卓)显示系统相关文章及链接
  9. Android之关于onSaveInstanceState和onRestoreInstanceState触发

随机推荐

  1. 另类观点:Android平板制造商应该选择亚马
  2. cocos2dx通过Jni调用Android的Java层代码
  3. 谷歌 Android 3.0计划四季度推出:代号姜饼
  4. android四大组件--android service详解
  5. Singletons vs. Application Context in
  6. [置顶] [Android基础]Android的消息机制
  7. 腾讯暑假实习面试总结
  8. android修改虚拟内存(方法)
  9. android 借助AccessibilityService实现模
  10. 阿里Android开发规范:四大基本组件