blob: 8a775f8aa54e13e47186ee5c5d048c8c7bb9f327 [file] [log] [blame]
Andreas Gampe72ede722019-03-04 14:15:18 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <cstring>
18#include <iostream>
19#include <memory>
20#include <sstream>
21
22#include <jni.h>
23
24#include <jvmti.h>
25
26#include <android-base/file.h>
27#include <android-base/logging.h>
28#include <android-base/macros.h>
29#include <android-base/unique_fd.h>
30
31#include <fcntl.h>
32#include <sys/stat.h>
33
34// We need dladdr.
35#if !defined(__APPLE__) && !defined(_WIN32)
36#ifndef _GNU_SOURCE
37#define _GNU_SOURCE
38#define DEFINED_GNU_SOURCE
39#endif
40#include <dlfcn.h>
41#ifdef DEFINED_GNU_SOURCE
42#undef _GNU_SOURCE
43#undef DEFINED_GNU_SOURCE
44#endif
45#endif
46
47// Slicer's headers have code that triggers these warnings. b/65298177
48#pragma clang diagnostic push
49#pragma clang diagnostic ignored "-Wunused-parameter"
50#pragma clang diagnostic ignored "-Wsign-compare"
51
52#include <slicer/dex_ir.h>
53#include <slicer/code_ir.h>
54#include <slicer/dex_bytecode.h>
55#include <slicer/dex_ir_builder.h>
56#include <slicer/writer.h>
57#include <slicer/reader.h>
58
59#pragma clang diagnostic pop
60
61namespace {
62
63JavaVM* gJavaVM = nullptr;
64
65// Converts a class name to a type descriptor
66// (ex. "java.lang.String" to "Ljava/lang/String;")
67std::string classNameToDescriptor(const char* className) {
68 std::stringstream ss;
69 ss << "L";
70 for (auto p = className; *p != '\0'; ++p) {
71 ss << (*p == '.' ? '/' : *p);
72 }
73 ss << ";";
74 return ss.str();
75}
76
77using namespace dex;
78using namespace lir;
79
Andreas Gampecbc93c72019-04-19 13:25:04 -070080class Transformer {
81public:
82 explicit Transformer(std::shared_ptr<ir::DexFile> dexIr) : dexIr_(dexIr) {}
Andreas Gampe72ede722019-03-04 14:15:18 -080083
Andreas Gampecbc93c72019-04-19 13:25:04 -070084 bool transform() {
85 bool classModified = false;
Andreas Gampe72ede722019-03-04 14:15:18 -080086
Andreas Gampecbc93c72019-04-19 13:25:04 -070087 std::unique_ptr<ir::Builder> builder;
88
89 for (auto& method : dexIr_->encoded_methods) {
90 // Do not look into abstract/bridge/native/synthetic methods.
91 if ((method->access_flags & (kAccAbstract | kAccBridge | kAccNative | kAccSynthetic))
92 != 0) {
93 continue;
94 }
95
96 struct HookVisitor: public Visitor {
97 HookVisitor(Transformer* transformer, CodeIr* c_ir)
98 : transformer(transformer), cIr(c_ir) {
99 }
100
101 bool Visit(Bytecode* bytecode) override {
102 if (bytecode->opcode == OP_MONITOR_ENTER) {
103 insertHook(bytecode, true,
104 reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
105 return true;
106 }
107 if (bytecode->opcode == OP_MONITOR_EXIT) {
108 insertHook(bytecode, false,
109 reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
110 return true;
111 }
112 return false;
113 }
114
115 void insertHook(lir::Instruction* before, bool pre, u4 reg) {
116 transformer->preparePrePost();
117 transformer->addCall(cIr, before, OP_INVOKE_STATIC_RANGE,
118 transformer->hookType_, pre ? "preLock" : "postLock",
119 transformer->voidType_, transformer->objectType_, reg);
120 myModified = true;
121 }
122
123 Transformer* transformer;
124 CodeIr* cIr;
125 bool myModified = false;
126 };
127
128 CodeIr c(method.get(), dexIr_);
129 bool methodModified = false;
130
131 HookVisitor visitor(this, &c);
132 for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
133 lir::Instruction* fi = *it;
134 fi->Accept(&visitor);
135 }
136 methodModified |= visitor.myModified;
137
138 if (methodModified) {
139 classModified = true;
140 c.Assemble();
141 }
Andreas Gampe72ede722019-03-04 14:15:18 -0800142 }
143
Andreas Gampecbc93c72019-04-19 13:25:04 -0700144 return classModified;
145 }
Andreas Gampe72ede722019-03-04 14:15:18 -0800146
Andreas Gampecbc93c72019-04-19 13:25:04 -0700147private:
148 void preparePrePost() {
149 // Insert "void LockHook.(pre|post)(Object o)."
Andreas Gampe72ede722019-03-04 14:15:18 -0800150
Andreas Gampecbc93c72019-04-19 13:25:04 -0700151 prepareBuilder();
Andreas Gampe72ede722019-03-04 14:15:18 -0800152
Andreas Gampecbc93c72019-04-19 13:25:04 -0700153 if (voidType_ == nullptr) {
154 voidType_ = builder_->GetType("V");
Andreas Gampe72ede722019-03-04 14:15:18 -0800155 }
Andreas Gampecbc93c72019-04-19 13:25:04 -0700156 if (hookType_ == nullptr) {
157 hookType_ = builder_->GetType("Lcom/android/lock_checker/LockHook;");
158 }
159 if (objectType_ == nullptr) {
160 objectType_ = builder_->GetType("Ljava/lang/Object;");
Andreas Gampe72ede722019-03-04 14:15:18 -0800161 }
162 }
163
Andreas Gampecbc93c72019-04-19 13:25:04 -0700164 void prepareBuilder() {
165 if (builder_ == nullptr) {
166 builder_ = std::unique_ptr<ir::Builder>(new ir::Builder(dexIr_));
167 }
168 }
169
170 static void addInst(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode,
171 const std::list<Operand*>& operands) {
172 auto instruction = cIr->Alloc<Bytecode>();
173
174 instruction->opcode = opcode;
175
176 for (auto it = operands.begin(); it != operands.end(); it++) {
177 instruction->operands.push_back(*it);
178 }
179
180 cIr->instructions.InsertBefore(instructionAfter, instruction);
181 }
182
183 void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
184 const char* methodName, ir::Type* returnType,
185 const std::vector<ir::Type*>& types, const std::list<int>& regs) {
186 auto proto = builder_->GetProto(returnType, builder_->GetTypeList(types));
187 auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
188
189 VRegList* paramRegs = cIr->Alloc<VRegList>();
190 for (auto it = regs.begin(); it != regs.end(); it++) {
191 paramRegs->registers.push_back(*it);
192 }
193
194 addInst(cIr, instructionAfter, opcode,
195 { paramRegs, cIr->Alloc<Method>(method, method->orig_index) });
196 }
197
198 void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
199 const char* methodName, ir::Type* returnType, ir::Type* paramType,
200 u4 paramVReg) {
201 auto proto = builder_->GetProto(returnType, builder_->GetTypeList( { paramType }));
202 auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
203
204 VRegRange* args = cIr->Alloc<VRegRange>(paramVReg, 1);
205
206 addInst(cIr, instructionAfter, opcode,
207 { args, cIr->Alloc<Method>(method, method->orig_index) });
208 }
209
210 std::shared_ptr<ir::DexFile> dexIr_;
211 std::unique_ptr<ir::Builder> builder_;
212
213 ir::Type* voidType_ = nullptr;
214 ir::Type* hookType_ = nullptr;
215 ir::Type* objectType_ = nullptr;
216};
Andreas Gampe72ede722019-03-04 14:15:18 -0800217
218std::pair<dex::u1*, size_t> maybeTransform(const char* name, size_t classDataLen,
219 const unsigned char* classData, dex::Writer::Allocator* allocator) {
220 // Isolate byte code of class class. This is needed as Android usually gives us more
221 // than the class we need.
222 dex::Reader reader(classData, classDataLen);
223
224 dex::u4 index = reader.FindClassIndex(classNameToDescriptor(name).c_str());
225 CHECK_NE(index, kNoIndex);
226 reader.CreateClassIr(index);
227 std::shared_ptr<ir::DexFile> ir = reader.GetIr();
228
Andreas Gampecbc93c72019-04-19 13:25:04 -0700229 {
230 Transformer transformer(ir);
231 if (!transformer.transform()) {
232 return std::make_pair(nullptr, 0);
233 }
Andreas Gampe72ede722019-03-04 14:15:18 -0800234 }
235
236 size_t new_size;
237 dex::Writer writer(ir);
238 dex::u1* newClassData = writer.CreateImage(allocator, &new_size);
239 return std::make_pair(newClassData, new_size);
240}
241
242void transformHook(jvmtiEnv* jvmtiEnv, JNIEnv* env ATTRIBUTE_UNUSED,
243 jclass classBeingRedefined ATTRIBUTE_UNUSED, jobject loader, const char* name,
244 jobject protectionDomain ATTRIBUTE_UNUSED, jint classDataLen,
245 const unsigned char* classData, jint* newClassDataLen, unsigned char** newClassData) {
246 // Even reading the classData array is expensive as the data is only generated when the
247 // memory is touched. Hence call JvmtiAgent#shouldTransform to check if we need to transform
248 // the class.
249
250 // Skip bootclasspath classes. TODO: Make this configurable.
251 if (loader == nullptr) {
252 return;
253 }
254
255 // Do not look into java.* classes. Should technically be filtered by above, but when that's
256 // configurable have this.
257 if (strncmp("java", name, 4) == 0) {
258 return;
259 }
260
261 // Do not look into our Java classes.
262 if (strncmp("com/android/lock_checker", name, 24) == 0) {
263 return;
264 }
265
266 class JvmtiAllocator: public dex::Writer::Allocator {
267 public:
268 explicit JvmtiAllocator(::jvmtiEnv* jvmti) :
269 jvmti_(jvmti) {
270 }
271
272 void* Allocate(size_t size) override {
273 unsigned char* res = nullptr;
274 jvmti_->Allocate(size, &res);
275 return res;
276 }
277
278 void Free(void* ptr) override {
279 jvmti_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
280 }
281
282 private:
283 ::jvmtiEnv* jvmti_;
284 };
285 JvmtiAllocator allocator(jvmtiEnv);
286 std::pair<dex::u1*, size_t> result = maybeTransform(name, classDataLen, classData,
287 &allocator);
288
289 if (result.second > 0) {
290 *newClassData = result.first;
291 *newClassDataLen = static_cast<jint>(result.second);
292 }
293}
294
295void dataDumpRequestHook(jvmtiEnv* jvmtiEnv ATTRIBUTE_UNUSED) {
296 if (gJavaVM == nullptr) {
297 LOG(ERROR) << "No JavaVM for dump";
298 return;
299 }
300 JNIEnv* env;
301 if (gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
302 LOG(ERROR) << "Could not get env for dump";
303 return;
304 }
305 jclass lockHookClass = env->FindClass("com/android/lock_checker/LockHook");
306 if (lockHookClass == nullptr) {
307 env->ExceptionClear();
308 LOG(ERROR) << "Could not find LockHook class";
309 return;
310 }
311 jmethodID dumpId = env->GetStaticMethodID(lockHookClass, "dump", "()V");
312 if (dumpId == nullptr) {
313 env->ExceptionClear();
314 LOG(ERROR) << "Could not find LockHook.dump";
315 return;
316 }
317 env->CallStaticVoidMethod(lockHookClass, dumpId);
318 env->ExceptionClear();
319}
320
321// A function for dladdr to search.
322extern "C" __attribute__ ((visibility ("default"))) void lock_agent_tag_fn() {
323}
324
325bool fileExists(const std::string& path) {
326 struct stat statBuf;
327 int rc = stat(path.c_str(), &statBuf);
328 return rc == 0;
329}
330
331std::string findLockAgentJar() {
332 // Check whether the jar is located next to the agent's so.
333#ifndef __APPLE__
334 {
335 Dl_info info;
336 if (dladdr(reinterpret_cast<const void*>(&lock_agent_tag_fn), /* out */ &info) != 0) {
337 std::string lockAgentSoPath = info.dli_fname;
338 std::string dir = android::base::Dirname(lockAgentSoPath);
339 std::string lockAgentJarPath = dir + "/" + "lockagent.jar";
340 if (fileExists(lockAgentJarPath)) {
341 return lockAgentJarPath;
342 }
343 } else {
344 LOG(ERROR) << "dladdr failed";
345 }
346 }
347#endif
348
349 std::string sysFrameworkPath = "/system/framework/lockagent.jar";
350 if (fileExists(sysFrameworkPath)) {
351 return sysFrameworkPath;
352 }
353
354 std::string relPath = "lockagent.jar";
355 if (fileExists(relPath)) {
356 return relPath;
357 }
358
359 return "";
360}
361
362void prepareHook(jvmtiEnv* env) {
363 // Inject the agent Java code.
364 {
365 std::string path = findLockAgentJar();
366 if (path.empty()) {
367 LOG(FATAL) << "Could not find lockagent.jar";
368 }
369 LOG(INFO) << "Will load Java parts from " << path;
370 jvmtiError res = env->AddToBootstrapClassLoaderSearch(path.c_str());
371 if (res != JVMTI_ERROR_NONE) {
372 LOG(FATAL) << "Could not add lockagent from " << path << " to boot classpath: " << res;
373 }
374 }
375
376 jvmtiCapabilities caps;
377 memset(&caps, 0, sizeof(caps));
378 caps.can_retransform_classes = 1;
379
380 if (env->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
381 LOG(FATAL) << "Could not add caps";
382 }
383
384 jvmtiEventCallbacks cb;
385 memset(&cb, 0, sizeof(cb));
386 cb.ClassFileLoadHook = transformHook;
387 cb.DataDumpRequest = dataDumpRequestHook;
388
389 if (env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
390 LOG(FATAL) << "Could not set cb";
391 }
392
393 if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr)
394 != JVMTI_ERROR_NONE) {
395 LOG(FATAL) << "Could not enable events";
396 }
397 if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr)
398 != JVMTI_ERROR_NONE) {
399 LOG(FATAL) << "Could not enable events";
400 }
401}
402
403jint attach(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
404 gJavaVM = vm;
405
406 jvmtiEnv* env;
407 jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_2);
408 if (jvmError != JNI_OK) {
409 return jvmError;
410 }
411
412 prepareHook(env);
413
414 return JVMTI_ERROR_NONE;
415}
416
417extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
418 return attach(vm, options, reserved);
419}
420
421extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
422 return attach(vm, options, reserved);
423}
424
425int locktest_main(int argc, char *argv[]) {
426 if (argc != 3) {
427 LOG(FATAL) << "Need two arguments: dex-file class-name";
428 }
429 struct stat statBuf;
430 int rc = stat(argv[1], &statBuf);
431 if (rc != 0) {
432 PLOG(FATAL) << "Could not get file size for " << argv[1];
433 }
434 std::unique_ptr<char[]> data(new char[statBuf.st_size]);
435 {
436 android::base::unique_fd fd(open(argv[1], O_RDONLY));
437 if (fd.get() == -1) {
438 PLOG(FATAL) << "Could not open file " << argv[1];
439 }
440 if (!android::base::ReadFully(fd.get(), data.get(), statBuf.st_size)) {
441 PLOG(FATAL) << "Could not read file " << argv[1];
442 }
443 }
444
445 class NewDeleteAllocator: public dex::Writer::Allocator {
446 public:
447 explicit NewDeleteAllocator() {
448 }
449
450 void* Allocate(size_t size) override {
451 return new char[size];
452 }
453
454 void Free(void* ptr) override {
455 delete[] reinterpret_cast<char*>(ptr);
456 }
457 };
458 NewDeleteAllocator allocator;
459
460 std::pair<dex::u1*, size_t> result = maybeTransform(argv[2], statBuf.st_size,
461 reinterpret_cast<unsigned char*>(data.get()), &allocator);
462
463 if (result.second == 0) {
464 LOG(INFO) << "No transformation";
465 return 0;
466 }
467
468 std::string newName(argv[1]);
469 newName.append(".new");
470
471 {
472 android::base::unique_fd fd(
473 open(newName.c_str(), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR));
474 if (fd.get() == -1) {
475 PLOG(FATAL) << "Could not open file " << newName;
476 }
477 if (!android::base::WriteFully(fd.get(), result.first, result.second)) {
478 PLOG(FATAL) << "Could not write file " << newName;
479 }
480 }
481 LOG(INFO) << "Transformed file written to " << newName;
482
483 return 0;
484}
485
486} // namespace
487
488int main(int argc, char *argv[]) {
489 return locktest_main(argc, argv);
490}