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 的值即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章