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;}
specialProperties
是 native
方法。它在 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_namespace
的 default_library_paths
有两个地方赋值,分别是 init_default_namespace_no_config
和 init_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_paths
被 namespace.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_namespace
的set_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
的值即可。
更多相关文章
- Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
- Android(安卓)输入系统(二)EventHub
- android 实现ImageView按压效果和解决背景图片拉申问题
- 在自定义view中获取android layout_width等属性值
- android sdk没有64位linux版本
- Android点击通知栏 ,移除通知
- android excel读写
- 整理Android(安卓)显示系统相关文章及链接
- Android之关于onSaveInstanceState和onRestoreInstanceState触发