本文将一步步解析 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/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
的值即可。