blob: 59bfa2bf849b9c3b33cc310e227ba6af6172c455 [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
80bool transform(std::shared_ptr<ir::DexFile> dexIr) {
81 bool modified = false;
82
83 std::unique_ptr<ir::Builder> builder;
84
85 for (auto& method : dexIr->encoded_methods) {
86 // Do not look into abstract/bridge/native/synthetic methods.
87 if ((method->access_flags & (kAccAbstract | kAccBridge | kAccNative | kAccSynthetic))
88 != 0) {
89 continue;
90 }
91
92 struct HookVisitor: public Visitor {
93 HookVisitor(std::unique_ptr<ir::Builder>* b, std::shared_ptr<ir::DexFile> d_ir,
94 CodeIr* c_ir) :
95 b(b), dIr(d_ir), cIr(c_ir) {
96 }
97
98 bool Visit(Bytecode* bytecode) override {
99 if (bytecode->opcode == OP_MONITOR_ENTER) {
100 prepare();
101 addCall(bytecode, OP_INVOKE_STATIC_RANGE, hookType, "preLock", voidType,
102 objectType, reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
103 myModified = true;
104 return true;
105 }
106 if (bytecode->opcode == OP_MONITOR_EXIT) {
107 prepare();
108 addCall(bytecode, OP_INVOKE_STATIC_RANGE, hookType, "postLock", voidType,
109 objectType, reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
110 myModified = true;
111 return true;
112 }
113 return false;
114 }
115
116 void prepare() {
117 if (*b == nullptr) {
118 *b = std::unique_ptr<ir::Builder>(new ir::Builder(dIr));
119 }
120 if (voidType == nullptr) {
121 voidType = (*b)->GetType("V");
122 hookType = (*b)->GetType("Lcom/android/lock_checker/LockHook;");
123 objectType = (*b)->GetType("Ljava/lang/Object;");
124 }
125 }
126
127 void addInst(lir::Instruction* instructionAfter, Opcode opcode,
128 const std::list<Operand*>& operands) {
129 auto instruction = cIr->Alloc<Bytecode>();
130
131 instruction->opcode = opcode;
132
133 for (auto it = operands.begin(); it != operands.end(); it++) {
134 instruction->operands.push_back(*it);
135 }
136
137 cIr->instructions.InsertBefore(instructionAfter, instruction);
138 }
139
140 void addCall(lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
141 const char* methodName, ir::Type* returnType,
142 const std::vector<ir::Type*>& types, const std::list<int>& regs) {
143 auto proto = (*b)->GetProto(returnType, (*b)->GetTypeList(types));
144 auto method = (*b)->GetMethodDecl((*b)->GetAsciiString(methodName), proto, type);
145
146 VRegList* paramRegs = cIr->Alloc<VRegList>();
147 for (auto it = regs.begin(); it != regs.end(); it++) {
148 paramRegs->registers.push_back(*it);
149 }
150
151 addInst(instructionAfter, opcode,
152 { paramRegs, cIr->Alloc<Method>(method, method->orig_index) });
153 }
154
155 void addCall(lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
156 const char* methodName, ir::Type* returnType, ir::Type* paramType,
157 u4 paramVReg) {
158 auto proto = (*b)->GetProto(returnType, (*b)->GetTypeList( { paramType }));
159 auto method = (*b)->GetMethodDecl((*b)->GetAsciiString(methodName), proto, type);
160
161 VRegRange* args = cIr->Alloc<VRegRange>(paramVReg, 1);
162
163 addInst(instructionAfter, opcode,
164 { args, cIr->Alloc<Method>(method, method->orig_index) });
165 }
166
167 std::unique_ptr<ir::Builder>* b;
168 std::shared_ptr<ir::DexFile> dIr;
169 CodeIr* cIr;
170 ir::Type* voidType = nullptr;
171 ir::Type* hookType = nullptr;
172 ir::Type* objectType = nullptr;
173 bool myModified = false;
174 };
175
176 CodeIr c(method.get(), dexIr);
177 HookVisitor visitor(&builder, dexIr, &c);
178
179 for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
180 lir::Instruction* fi = *it;
181 fi->Accept(&visitor);
182 }
183
184 if (visitor.myModified) {
185 modified = true;
186 c.Assemble();
187 }
188 }
189
190 return modified;
191}
192
193std::pair<dex::u1*, size_t> maybeTransform(const char* name, size_t classDataLen,
194 const unsigned char* classData, dex::Writer::Allocator* allocator) {
195 // Isolate byte code of class class. This is needed as Android usually gives us more
196 // than the class we need.
197 dex::Reader reader(classData, classDataLen);
198
199 dex::u4 index = reader.FindClassIndex(classNameToDescriptor(name).c_str());
200 CHECK_NE(index, kNoIndex);
201 reader.CreateClassIr(index);
202 std::shared_ptr<ir::DexFile> ir = reader.GetIr();
203
204 if (!transform(ir)) {
205 return std::make_pair(nullptr, 0);
206 }
207
208 size_t new_size;
209 dex::Writer writer(ir);
210 dex::u1* newClassData = writer.CreateImage(allocator, &new_size);
211 return std::make_pair(newClassData, new_size);
212}
213
214void transformHook(jvmtiEnv* jvmtiEnv, JNIEnv* env ATTRIBUTE_UNUSED,
215 jclass classBeingRedefined ATTRIBUTE_UNUSED, jobject loader, const char* name,
216 jobject protectionDomain ATTRIBUTE_UNUSED, jint classDataLen,
217 const unsigned char* classData, jint* newClassDataLen, unsigned char** newClassData) {
218 // Even reading the classData array is expensive as the data is only generated when the
219 // memory is touched. Hence call JvmtiAgent#shouldTransform to check if we need to transform
220 // the class.
221
222 // Skip bootclasspath classes. TODO: Make this configurable.
223 if (loader == nullptr) {
224 return;
225 }
226
227 // Do not look into java.* classes. Should technically be filtered by above, but when that's
228 // configurable have this.
229 if (strncmp("java", name, 4) == 0) {
230 return;
231 }
232
233 // Do not look into our Java classes.
234 if (strncmp("com/android/lock_checker", name, 24) == 0) {
235 return;
236 }
237
238 class JvmtiAllocator: public dex::Writer::Allocator {
239 public:
240 explicit JvmtiAllocator(::jvmtiEnv* jvmti) :
241 jvmti_(jvmti) {
242 }
243
244 void* Allocate(size_t size) override {
245 unsigned char* res = nullptr;
246 jvmti_->Allocate(size, &res);
247 return res;
248 }
249
250 void Free(void* ptr) override {
251 jvmti_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
252 }
253
254 private:
255 ::jvmtiEnv* jvmti_;
256 };
257 JvmtiAllocator allocator(jvmtiEnv);
258 std::pair<dex::u1*, size_t> result = maybeTransform(name, classDataLen, classData,
259 &allocator);
260
261 if (result.second > 0) {
262 *newClassData = result.first;
263 *newClassDataLen = static_cast<jint>(result.second);
264 }
265}
266
267void dataDumpRequestHook(jvmtiEnv* jvmtiEnv ATTRIBUTE_UNUSED) {
268 if (gJavaVM == nullptr) {
269 LOG(ERROR) << "No JavaVM for dump";
270 return;
271 }
272 JNIEnv* env;
273 if (gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
274 LOG(ERROR) << "Could not get env for dump";
275 return;
276 }
277 jclass lockHookClass = env->FindClass("com/android/lock_checker/LockHook");
278 if (lockHookClass == nullptr) {
279 env->ExceptionClear();
280 LOG(ERROR) << "Could not find LockHook class";
281 return;
282 }
283 jmethodID dumpId = env->GetStaticMethodID(lockHookClass, "dump", "()V");
284 if (dumpId == nullptr) {
285 env->ExceptionClear();
286 LOG(ERROR) << "Could not find LockHook.dump";
287 return;
288 }
289 env->CallStaticVoidMethod(lockHookClass, dumpId);
290 env->ExceptionClear();
291}
292
293// A function for dladdr to search.
294extern "C" __attribute__ ((visibility ("default"))) void lock_agent_tag_fn() {
295}
296
297bool fileExists(const std::string& path) {
298 struct stat statBuf;
299 int rc = stat(path.c_str(), &statBuf);
300 return rc == 0;
301}
302
303std::string findLockAgentJar() {
304 // Check whether the jar is located next to the agent's so.
305#ifndef __APPLE__
306 {
307 Dl_info info;
308 if (dladdr(reinterpret_cast<const void*>(&lock_agent_tag_fn), /* out */ &info) != 0) {
309 std::string lockAgentSoPath = info.dli_fname;
310 std::string dir = android::base::Dirname(lockAgentSoPath);
311 std::string lockAgentJarPath = dir + "/" + "lockagent.jar";
312 if (fileExists(lockAgentJarPath)) {
313 return lockAgentJarPath;
314 }
315 } else {
316 LOG(ERROR) << "dladdr failed";
317 }
318 }
319#endif
320
321 std::string sysFrameworkPath = "/system/framework/lockagent.jar";
322 if (fileExists(sysFrameworkPath)) {
323 return sysFrameworkPath;
324 }
325
326 std::string relPath = "lockagent.jar";
327 if (fileExists(relPath)) {
328 return relPath;
329 }
330
331 return "";
332}
333
334void prepareHook(jvmtiEnv* env) {
335 // Inject the agent Java code.
336 {
337 std::string path = findLockAgentJar();
338 if (path.empty()) {
339 LOG(FATAL) << "Could not find lockagent.jar";
340 }
341 LOG(INFO) << "Will load Java parts from " << path;
342 jvmtiError res = env->AddToBootstrapClassLoaderSearch(path.c_str());
343 if (res != JVMTI_ERROR_NONE) {
344 LOG(FATAL) << "Could not add lockagent from " << path << " to boot classpath: " << res;
345 }
346 }
347
348 jvmtiCapabilities caps;
349 memset(&caps, 0, sizeof(caps));
350 caps.can_retransform_classes = 1;
351
352 if (env->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
353 LOG(FATAL) << "Could not add caps";
354 }
355
356 jvmtiEventCallbacks cb;
357 memset(&cb, 0, sizeof(cb));
358 cb.ClassFileLoadHook = transformHook;
359 cb.DataDumpRequest = dataDumpRequestHook;
360
361 if (env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
362 LOG(FATAL) << "Could not set cb";
363 }
364
365 if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr)
366 != JVMTI_ERROR_NONE) {
367 LOG(FATAL) << "Could not enable events";
368 }
369 if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr)
370 != JVMTI_ERROR_NONE) {
371 LOG(FATAL) << "Could not enable events";
372 }
373}
374
375jint attach(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
376 gJavaVM = vm;
377
378 jvmtiEnv* env;
379 jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_2);
380 if (jvmError != JNI_OK) {
381 return jvmError;
382 }
383
384 prepareHook(env);
385
386 return JVMTI_ERROR_NONE;
387}
388
389extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
390 return attach(vm, options, reserved);
391}
392
393extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
394 return attach(vm, options, reserved);
395}
396
397int locktest_main(int argc, char *argv[]) {
398 if (argc != 3) {
399 LOG(FATAL) << "Need two arguments: dex-file class-name";
400 }
401 struct stat statBuf;
402 int rc = stat(argv[1], &statBuf);
403 if (rc != 0) {
404 PLOG(FATAL) << "Could not get file size for " << argv[1];
405 }
406 std::unique_ptr<char[]> data(new char[statBuf.st_size]);
407 {
408 android::base::unique_fd fd(open(argv[1], O_RDONLY));
409 if (fd.get() == -1) {
410 PLOG(FATAL) << "Could not open file " << argv[1];
411 }
412 if (!android::base::ReadFully(fd.get(), data.get(), statBuf.st_size)) {
413 PLOG(FATAL) << "Could not read file " << argv[1];
414 }
415 }
416
417 class NewDeleteAllocator: public dex::Writer::Allocator {
418 public:
419 explicit NewDeleteAllocator() {
420 }
421
422 void* Allocate(size_t size) override {
423 return new char[size];
424 }
425
426 void Free(void* ptr) override {
427 delete[] reinterpret_cast<char*>(ptr);
428 }
429 };
430 NewDeleteAllocator allocator;
431
432 std::pair<dex::u1*, size_t> result = maybeTransform(argv[2], statBuf.st_size,
433 reinterpret_cast<unsigned char*>(data.get()), &allocator);
434
435 if (result.second == 0) {
436 LOG(INFO) << "No transformation";
437 return 0;
438 }
439
440 std::string newName(argv[1]);
441 newName.append(".new");
442
443 {
444 android::base::unique_fd fd(
445 open(newName.c_str(), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR));
446 if (fd.get() == -1) {
447 PLOG(FATAL) << "Could not open file " << newName;
448 }
449 if (!android::base::WriteFully(fd.get(), result.first, result.second)) {
450 PLOG(FATAL) << "Could not write file " << newName;
451 }
452 }
453 LOG(INFO) << "Transformed file written to " << newName;
454
455 return 0;
456}
457
458} // namespace
459
460int main(int argc, char *argv[]) {
461 return locktest_main(argc, argv);
462}