OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // This is the Android-specific Chrome linker, a tiny shared library | |
6 // implementing a custom dynamic linker that can be used to load the | |
7 // real Chrome libraries (e.g. libchromeview.so). | |
8 | |
9 // The main point of this linker is to be able to share the RELRO | |
10 // section of libchromeview.so between the browser and renderer | |
11 // process. This saves about 1.3 MB of private RAM per renderer process. | |
12 | |
13 // This source code *cannot* depend on anything from base/ or the C++ | |
14 // STL, to keep the final library small, and avoid ugly dependency issues. | |
15 | |
16 | |
17 #include <android/log.h> | |
18 #include <crazy_linker.h> | |
19 #include <jni.h> | |
20 #include <stdlib.h> | |
21 | |
22 // Set this to 1 to enable debug traces to the Android log. | |
23 // Note that LOG() from "base/logging.h" cannot be used, since it is | |
24 // in base/ which hasn't been loaded yet. | |
25 #define DEBUG 0 | |
26 | |
27 #if DEBUG | |
28 #define LOG_INFO(...) \ | |
29 __android_log_print(ANDROID_LOG_INFO, "chrome_linker", __VA_ARGS__) | |
30 #define LOG_ERROR(...) \ | |
31 __android_log_print(ANDROID_LOG_ERROR, "chrome_linker", __VA_ARGS__) | |
32 #else | |
33 #define LOG_INFO(...) ((void)0) | |
34 #define LOG_ERROR(...) ((void)0) | |
35 #endif | |
36 | |
37 #define UNUSED __attribute__((unused)) | |
38 | |
39 namespace { | |
40 | |
41 // A simply scoped UTF String class that can be initialized from | |
42 // a Java jstring handle. Modeled like std::string, which cannot | |
43 // be used here. | |
44 class String { | |
45 public: | |
46 String(JNIEnv* env, jstring str); | |
47 | |
48 ~String() { | |
49 if (ptr_) | |
50 ::free(ptr_); | |
51 } | |
52 | |
53 const char* c_str() const { return ptr_ ? ptr_ : ""; } | |
54 size_t size() const { return size_; } | |
55 | |
56 private: | |
57 char* ptr_; | |
58 size_t size_; | |
59 }; | |
60 | |
61 String::String(JNIEnv* env, jstring str) { | |
62 size_ = env->GetStringUTFLength(str); | |
63 ptr_ = reinterpret_cast<char*>(::malloc(size_ + 1)); | |
JF
2013/09/13 05:41:00
static_cast?
digit1
2013/09/18 15:17:30
Done.
| |
64 | |
65 // Note: the result contains Java "modified UTF-8" bytes. | |
66 // Good enough for the linker though. | |
67 const char* bytes = env->GetStringUTFChars(str, NULL); | |
68 ::memcpy(ptr_, bytes, size_); | |
69 ptr_[size_] = '\0'; | |
70 | |
71 env->ReleaseStringUTFChars(str, bytes); | |
72 } | |
73 | |
74 // A scoped crazy_library_t that automatically closes the handle | |
75 // on scope exit, unless Release() has been called. | |
76 class ScopedLibrary { | |
77 public: | |
78 ScopedLibrary() : lib_(NULL) {} | |
79 | |
80 ~ScopedLibrary() { | |
81 if (lib_) | |
82 crazy_library_close(lib_); | |
83 } | |
84 | |
85 crazy_library_t* Get() { return lib_; } | |
86 | |
87 crazy_library_t** GetPtr() { return &lib_; } | |
88 | |
89 crazy_library_t* Release() { | |
90 crazy_library_t* ret = lib_; | |
91 lib_ = NULL; | |
92 return ret; | |
93 } | |
94 | |
95 private: | |
96 crazy_library_t* lib_; | |
97 }; | |
98 | |
99 // Return a pointer to the base name from an input |path| string. | |
100 const char* GetBaseNamePtr(const char* path) { | |
101 const char* p = strchr(path, '/'); | |
102 if (p) | |
103 return p + 1; | |
104 return path; | |
105 } | |
106 | |
107 // Find the jclass JNI reference corresponding to a given |class_name|. | |
108 // |env| is the current JNI environment handle. | |
109 // On success, return true and set |*clazz|. | |
110 bool InitClassReference(JNIEnv* env, | |
111 const char* class_name, | |
112 jclass* clazz) { | |
113 *clazz = env->FindClass(class_name); | |
114 if (!*clazz) { | |
115 LOG_ERROR("Could not find class for %s", class_name); | |
116 return false; | |
117 } | |
118 return true; | |
119 } | |
120 | |
121 // Initialize a jfieldID corresponding to the field of a given |clazz|, | |
122 // with name |field_name| and signature |field_sig|. | |
123 // |env| is the current JNI environment handle. | |
124 // On success, return true and set |*field_id|. | |
125 bool InitFieldId(JNIEnv* env, | |
126 jclass clazz, | |
127 const char* field_name, | |
128 const char* field_sig, | |
129 jfieldID* field_id) { | |
130 *field_id = env->GetFieldID(clazz, field_name, field_sig); | |
131 if (!*field_id) { | |
132 LOG_ERROR("Could not find ID for field '%s'", field_name); | |
133 return false; | |
134 } | |
135 LOG_INFO( | |
136 "%s: Found ID %p for field '%s", __FUNCTION__, *field_id, field_name); | |
137 return true; | |
138 } | |
139 | |
140 // This JavaVM* handle is saved in the linker's JNI_OnLoad(), then passed | |
141 // to the JNI_OnLoad() hook of any function loaded through LoadLibrary). | |
142 JavaVM* g_java_vm; | |
143 | |
144 // A class used to model the field IDs of the org.chromium.base.Linker | |
145 // LibInfo inner class, used to communicate data with the Java side | |
146 // of the linker. | |
147 struct LibInfo_class { | |
148 jfieldID load_address_id; | |
149 jfieldID load_size_id; | |
150 jfieldID relro_start_id; | |
151 jfieldID relro_size_id; | |
152 jfieldID relro_fd_id; | |
153 | |
154 // Initialize an instance. | |
155 bool Init(JNIEnv* env) { | |
156 jclass clazz; | |
157 if (!InitClassReference(env, | |
158 "org/chromium/base/Linker$LibInfo", | |
159 &clazz)) { | |
160 return false; | |
161 } | |
162 | |
163 return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) && | |
164 InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) && | |
165 InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) && | |
166 InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) && | |
167 InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id); | |
168 } | |
169 | |
170 // Use this instance to convert a RelroInfo reference into | |
171 // a crazy_library_info_t. | |
172 void GetLibraryInfo(JNIEnv* env, | |
173 jobject library_info_obj, | |
174 crazy_library_info_t* info) { | |
175 info->load_address = static_cast<size_t>( | |
176 env->GetLongField(library_info_obj, load_address_id)); | |
177 | |
178 info->load_size = static_cast<size_t>( | |
179 env->GetLongField(library_info_obj, load_size_id)); | |
180 | |
181 info->relro_start = static_cast<size_t>( | |
182 env->GetLongField(library_info_obj, relro_start_id)); | |
183 | |
184 info->relro_size = static_cast<size_t>( | |
185 env->GetLongField(library_info_obj, relro_size_id)); | |
186 | |
187 info->relro_fd = env->GetIntField(library_info_obj, relro_fd_id); | |
188 } | |
189 | |
190 void SetLibraryInfo(JNIEnv* env, | |
191 jobject library_info_obj, | |
192 crazy_library_info_t* info) { | |
193 env->SetLongField(library_info_obj, load_address_id, info->load_address); | |
194 env->SetLongField(library_info_obj, load_size_id, info->load_size); | |
195 env->SetLongField(library_info_obj, relro_start_id, info->relro_start); | |
196 env->SetLongField(library_info_obj, relro_size_id, info->relro_size); | |
197 env->SetIntField(library_info_obj, relro_fd_id, info->relro_fd); | |
198 } | |
199 }; | |
200 | |
201 static LibInfo_class s_lib_info_fields; | |
202 | |
203 // The linker uses a single crazy_context_t object created on demand. | |
204 // There is no need to protect this against concurrent access, locking | |
205 // is already handled on the Java side. | |
206 static crazy_context_t* s_crazy_context; | |
207 | |
208 crazy_context_t* GetCrazyContext() { | |
209 if (!s_crazy_context) { | |
210 // Create new context. | |
211 s_crazy_context = crazy_context_create(); | |
212 | |
213 // Ensure libraries located in the same directory than the linker | |
214 // can be loaded. | |
215 crazy_context_add_search_path_for_address( | |
216 s_crazy_context, reinterpret_cast<void*>(&s_crazy_context)); | |
217 } | |
218 | |
219 return s_crazy_context; | |
220 } | |
221 | |
222 // Load a library with the Chrome linker. This will also call its | |
223 // JNI_OnLoad() method, which shall register its methods. Note that | |
224 // lazy native method resolution will _not_ work after this, because | |
225 // Dalvik uses the system's dlsym() which won't see the new library, | |
226 // so explicit registration is mandatory. | |
227 // |env| is the current JNI environment handle. | |
228 // |clazz| is the static class handle for org.chromium.base.Linker, | |
229 // and is ignored here. | |
230 // |library_name| is the library name (e.g. libfoo.so). | |
231 // |load_address| is an explicit load address. | |
232 // |library_info| is a LibInfo handle used to communicate information | |
233 // with the Java side. | |
234 // Return true on success. | |
235 jboolean LoadLibrary(JNIEnv* env, | |
236 jclass clazz, | |
237 jstring library_name, | |
238 jlong load_address, | |
239 jobject library_info) { | |
240 String lib_name(env, library_name); | |
241 const char* UNUSED lib_basename = GetBaseNamePtr(lib_name.c_str()); | |
242 | |
243 crazy_context_t* context = GetCrazyContext(); | |
244 | |
245 // Set the desired load address (0 means randomize it). | |
246 crazy_context_set_load_address(context, | |
247 static_cast<size_t>(load_address)); | |
248 | |
249 // Open the library now. | |
250 LOG_INFO("%s: Opening shared library: %s", __FUNCTION__, lib_name.c_str()); | |
251 | |
252 // Not using a ScopedLibrary here is intentional, since leaking | |
253 // the library, even in case of error, is intentional. | |
254 crazy_library_t* library; | |
255 if (!crazy_library_open(&library, lib_name.c_str(), context)) { | |
256 LOG_ERROR("%s: Could not open %s: %s", __FUNCTION__, lib_basename, | |
257 crazy_context_get_error(context)); | |
258 return false; | |
259 } | |
260 | |
261 if (library_info) { | |
262 crazy_library_info_t info; | |
263 | |
264 memset(&info, 0, sizeof(info)); | |
265 info.relro_fd = -1; | |
266 | |
267 if (!crazy_library_get_info(library, context, &info)) { | |
268 LOG_INFO("%s: Could not get library info for %s: %s", | |
269 __FUNCTION__, lib_name.c_str(), | |
270 crazy_context_get_error(context)); | |
271 } else { | |
272 s_lib_info_fields.SetLibraryInfo(env, library_info, &info); | |
273 } | |
274 } | |
275 | |
276 LOG_INFO("%s: Looking for JNI_OnLoad() in %s", __FUNCTION__, lib_basename); | |
277 | |
278 int (*jni_on_load_ptr)(JavaVM*, void*); | |
279 if (crazy_library_find_symbol( | |
280 library, "JNI_OnLoad", reinterpret_cast<void**>(&jni_on_load_ptr))) { | |
281 | |
282 LOG_INFO("%s: Calling JNI_OnLoad() in %s", __FUNCTION__, lib_basename); | |
283 | |
284 // Call it. | |
285 int ret = (*jni_on_load_ptr)(g_java_vm, NULL); | |
286 if (ret < JNI_VERSION_1_4) { | |
287 LOG_ERROR("%s: JNI_OnLoad() of %s returned %d, at least %d expected", | |
288 __FUNCTION__, lib_basename, ret, JNI_VERSION_1_4); | |
289 return false; | |
290 } | |
291 } | |
292 | |
293 LOG_INFO("%s: Success loading library %s", __FUNCTION__, lib_basename); | |
294 return true; | |
295 } | |
296 | |
297 jboolean EnableSharedRelro(JNIEnv* env, | |
298 jclass clazz, | |
299 jstring library_name, | |
300 jobject lib_info_obj) { | |
301 String lib_name(env, library_name); | |
302 | |
303 LOG_INFO("%s: Called for %s", __FUNCTION__, lib_name.c_str()); | |
304 | |
305 ScopedLibrary library; | |
306 if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) { | |
307 LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str()); | |
308 return false; | |
309 } | |
310 | |
311 crazy_context_t* context = GetCrazyContext(); | |
312 | |
313 if (!crazy_library_enable_relro_sharing(library.Get(), context)) { | |
314 LOG_ERROR("%s: Could not enable RELRO sharing for %s: %s", | |
315 __FUNCTION__, lib_name.c_str(), | |
316 crazy_context_get_error(context)); | |
317 return false; | |
318 } | |
319 | |
320 crazy_library_info_t info; | |
321 if (!crazy_library_get_info(library.Get(), context, &info)) { | |
322 LOG_ERROR("%s: Could not retrieve %s information: %s", | |
323 __FUNCTION__, lib_name.c_str(), | |
324 crazy_context_get_error(context)); | |
325 return false; | |
326 } | |
327 | |
328 s_lib_info_fields.SetLibraryInfo(env, lib_info_obj, &info); | |
329 return true; | |
330 } | |
331 | |
332 jboolean UseSharedRelro(JNIEnv* env, | |
333 jclass clazz, | |
334 jstring library_name, | |
335 jobject lib_info_obj) { | |
336 String lib_name(env, library_name); | |
337 | |
338 LOG_INFO("%s: called for %s, lib_info_ref=%p", | |
339 __FUNCTION__, lib_name.c_str(), lib_info_obj); | |
340 | |
341 ScopedLibrary library; | |
342 if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) { | |
343 LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str()); | |
344 return false; | |
345 } | |
346 | |
347 crazy_context_t* context = GetCrazyContext(); | |
348 crazy_library_info_t info; | |
349 s_lib_info_fields.GetLibraryInfo(env, lib_info_obj, &info); | |
350 | |
351 LOG_INFO( | |
352 "%s: library=%s load_addr=%p load_size=%p relro_start=%p relro_size=%p " | |
353 "relro_fd=%d", | |
354 __FUNCTION__, | |
355 lib_name.c_str(), | |
356 (void*)info.load_address, | |
357 (void*)info.load_size, | |
358 (void*)info.relro_start, | |
359 (void*)info.relro_size, | |
360 info.relro_fd); | |
361 | |
362 if (!crazy_library_use_relro_sharing(library.Get(), | |
363 info.relro_start, | |
364 info.relro_size, | |
365 info.relro_fd, | |
366 context)) { | |
367 LOG_ERROR("%s: Could not enable RELRO sharing for %s: %s", | |
368 __FUNCTION__, lib_name.c_str(), | |
369 crazy_context_get_error(context)); | |
370 return false; | |
371 } | |
372 | |
373 LOG_INFO("%s: Library %s using shared RELRO section!", | |
374 __FUNCTION__, lib_name.c_str()); | |
375 | |
376 return true; | |
377 } | |
378 | |
379 static const JNINativeMethod kNativeMethods[] = { | |
380 {"nativeLoadLibrary", | |
381 "(" | |
382 "Ljava/lang/String;" | |
383 "J" | |
384 "Lorg/chromium/base/Linker$LibInfo;" | |
385 ")" | |
386 "Z", | |
387 reinterpret_cast<void*>(&LoadLibrary)}, | |
388 {"nativeEnableSharedRelro", | |
389 "(" | |
390 "Ljava/lang/String;" | |
391 "Lorg/chromium/base/Linker$LibInfo;" | |
392 ")" | |
393 "Z", | |
394 reinterpret_cast<void*>(&EnableSharedRelro)}, | |
395 {"nativeUseSharedRelro", | |
396 "(" | |
397 "Ljava/lang/String;" | |
398 "Lorg/chromium/base/Linker$LibInfo;" | |
399 ")" | |
400 "Z", | |
401 reinterpret_cast<void*>(&UseSharedRelro)}, }; | |
402 | |
403 } // namespace | |
404 | |
405 // JNI_OnLoad() hook called when the linker library is loaded through | |
406 // the regular System.LoadLibrary) API. This shall save the Java VM | |
407 // handle and initialize LibInfo fields. | |
408 jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |
409 g_java_vm = vm; | |
410 | |
411 LOG_INFO("%s: Entering", __FUNCTION__); | |
412 // Get new JNIEnv | |
413 JNIEnv* env; | |
414 if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), | |
415 JNI_VERSION_1_4)) { | |
416 LOG_ERROR("Could not create JNIEnv"); | |
417 return -1; | |
418 } | |
419 | |
420 // Register native methods. | |
421 jclass linker_class; | |
422 if (!InitClassReference(env, "org/chromium/base/Linker", &linker_class)) | |
423 return -1; | |
424 | |
425 LOG_INFO("%s: Registering native methods", __FUNCTION__); | |
426 env->RegisterNatives(linker_class, | |
427 kNativeMethods, | |
428 sizeof(kNativeMethods) / sizeof(kNativeMethods[0])); | |
429 | |
430 // Find LibInfo field ids. | |
431 LOG_INFO("%s: Caching field IDs", __FUNCTION__); | |
432 if (!s_lib_info_fields.Init(env)) { | |
433 return -1; | |
434 } | |
435 | |
436 LOG_INFO("%s: Done", __FUNCTION__); | |
437 return JNI_VERSION_1_4; | |
438 } | |
OLD | NEW |