Android 9.0 System.getProperty("java.library.path") 源码解析

本文将一步步解析 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/odm
dir.vendor = /data/nativetest64/odm
dir.vendor = /data/benchmarktest/odm
dir.vendor = /data/benchmarktest64/odm
dir.vendor = /data/nativetest/vendor
dir.vendor = /data/nativetest64/vendor
dir.vendor = /data/benchmarktest/vendor
dir.vendor = /data/benchmarktest64/vendor

dir.system = /data/nativetest
dir.system = /data/nativetest64
dir.system = /data/benchmarktest
dir.system = /data/benchmarktest64

dir.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 = true

namespace.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}/drm
namespace.default.permitted.paths += /system/${LIB}/extractors
namespace.default.permitted.paths += /system/${LIB}/hw
namespace.default.permitted.paths += /system/product/${LIB}
# These are where odex files are located. libart has to be able to dlopen the files
namespace.default.permitted.paths += /system/framework
namespace.default.permitted.paths += /system/app
namespace.default.permitted.paths += /system/priv-app
namespace.default.permitted.paths += /vendor/framework
namespace.default.permitted.paths += /vendor/app
namespace.default.permitted.paths += /vendor/priv-app
namespace.default.permitted.paths += /odm/framework
namespace.default.permitted.paths += /odm/app
namespace.default.permitted.paths += /odm/priv-app
namespace.default.permitted.paths += /oem/app
namespace.default.permitted.paths += /system/product/framework
namespace.default.permitted.paths += /system/product/app
namespace.default.permitted.paths += /system/product/priv-app
namespace.default.permitted.paths += /data
namespace.default.permitted.paths += /mnt/expand

namespace.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  = /data
namespace.default.asan.permitted.paths += /system/${LIB}/drm
namespace.default.asan.permitted.paths += /system/${LIB}/extractors
namespace.default.asan.permitted.paths += /system/${LIB}/hw
namespace.default.asan.permitted.paths += /system/framework
namespace.default.asan.permitted.paths += /system/app
namespace.default.asan.permitted.paths += /system/priv-app
namespace.default.asan.permitted.paths += /vendor/framework
namespace.default.asan.permitted.paths += /vendor/app
namespace.default.asan.permitted.paths += /vendor/priv-app
namespace.default.asan.permitted.paths += /odm/framework
namespace.default.asan.permitted.paths += /odm/app
namespace.default.asan.permitted.paths += /odm/priv-app
namespace.default.asan.permitted.paths += /oem/app
namespace.default.asan.permitted.paths += /system/product/${LIB}
namespace.default.asan.permitted.paths += /system/product/framework
namespace.default.asan.permitted.paths += /system/product/app
namespace.default.asan.permitted.paths += /system/product/priv-app
namespace.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->initUnchangeableSystemProperties
	System.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 的值即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值