0x00 前言

之前一直都是在Dalvik 虚拟机上在折腾,从Android 4.4开始开始引入ART,到5.0已经成为默认选择。而且最近看到阿里开源的 Dexposed 框架,已经提供了对于android art 模式下的 hook 支持,所以对照着android art 部分的源码和之前 liang 大牛放出了hook代码研究了一下ART模式下的hook原理,做个简单的整理。关于android ART 更详尽的部分 可以阅读csdn的博客专栏《老罗的android之旅》。

Android运行时ART执行类方法的过程分析

Android运行时ART加载类和方法的过程分析

Android运行时ART加载OAT文件的过程分析

0x01 ART

ART是Android平台上的新一代运行时,用来代替dalvik。它主要采用了AOT(Ahead Of Time)的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行。因为Dalvik执行的是Dex字节码,通过解释器执行。虽然Dalvik也会对频繁执行的代码进行jIT生成本地机器指令来执行,但毕竟在应用程序运行过程中将Dex字节码翻译成本地机器指令也会影响到应用程序本身的执行。因此ART节省了运行时间,提高了效率,但是在一定程度上使得应用安装的时间变长,空间占用变大。

下图是ART 的源码目录结构:

中间有几个目录比较关键,

首先是dex2oat,负责将dex文件给转换为oat文件,具体的翻译工作需要由compiler来完成,最后编译为dex2oat;

其次是runtime目录,内容比较多,主要就是运行时,编译为libart.so用来替换libdvm.so,dalvik是一个外壳,其中还是在调用ART runtime;

oatdump也是一个比较重要的工具,编译为oatdump程序,主要用来对oat文件进行分析并格式化显示出文件的组成结构;

jdwpspy是java的调试支持部分,即JDWP服务端的实现。

ART也是由zygote所启动的,与dalvik的启动过程完全一样,保证了由dalvik到ART的无缝衔接。

整个启动过程是从app_process(/framework/base/cmds/app_process/app_main.cpp)开始的,开始的时候,创建了一个对象AppRuntime runtime,这是个单例,整个系统运行时只有一个,随着zygote 的fork过程,每个子进程只是在不断的复制指向这个对象的指针个数。然后开始执行runtime.start方法(/frameworks/base/core/jni/AndrroidRuntime.cpp)。在start方法中会对系统的属性进行判断,选择libdvm.so 或者是libart.so进行链接。

/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env) != ) {
return;
}

可以在JniInvocation.Init函数中看到初始化过程

bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
char default_library[PROPERTY_VALUE_MAX];
property_get("persist.sys.dalvik.vm.lib", default_library, "libdvm.so");
#else
const char* default_library = "libdvm.so";
#endif
if (library == NULL) {
library = default_library;
} handle_ = dlopen(library, RTLD_NOW);
if (handle_ == NULL) {
ALOGE("Failed to dlopen %s: %s", library, dlerror());
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
"JNI_GetDefaultJavaVMInitArgs")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
"JNI_CreateJavaVM")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
"JNI_GetCreatedJavaVMs")) {
return false;
}
return true;
}

而对于libdvm.so或者libart.so都需要提供几个公用的接口,以达到从Dalvik到ART的无缝衔接。而接下的来调用的JNI_CreateJavaVM()实际上是JniInvocation中的JNI_CreateJavaVM()函数

jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
}

在之前的JniInvocation::init中函数指针已经选择保存了libdvm.so或者libart.so中的函数地址,在这里正式开始划分Dalvik和ART启动流程。Android系统通过将ART运行时抽象成一个Java虚拟机,以及通过系统属性persist.sys.dalvik.vm.lib和一个适配层JniInvocation,就可以无缝地将Dalvik虚拟机替换为ART运行时。

而hook代码中对于android运行模式判断也是如此,和JniInvocation::init函数中一样,都是判断系统属性值。

static bool isArt(){
char value[PROPERTY_VALUE_MAX];
property_get("persist.sys.dalvik.vm.lib", value, "");
LOGI("[+] persist.sys.dalvik.vm.lib = %s", value);
return strncmp(value, "libart.so", strlen("libart.so")) == ;
}

0x02  ART 中方法的调用

还是通过源码,在ART启动过程中:

/* frameworks/base/core/jni/AndroidRuntime.cpp */
void AndroidRuntime::start(const char* className, const char* options)
{
......
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
......
}
}
......
}

跟入CallStaicVoidMethod() 函数

/* art/runtime/jni_internal.cc */
static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
va_list ap;
va_start(ap, mid);
CHECK_NON_NULL_ARGUMENT(CallStaticVoidMethod, mid);
ScopedObjectAccess soa(env);
InvokeWithVarArgs(soa, NULL, mid, ap);
va_end(ap);}

JNI类的成员函数CallStaticVoidMethod实际上又是通过全局函数InvokeWithVarArgs来调用参数mid指定的方法。

/* art/runtime/jni_internal.cc */
static JValue InvokeWithVarArgs(const ScopedObjectAccess& soa, jobject obj,
jmethodID mid, va_list args)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
ArtMethod* method = soa.DecodeMethod(mid);
Object* receiver = method->IsStatic() ? NULL : soa.Decode<Object*>(obj);
MethodHelper mh(method);
JValue result;
ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
arg_array.BuildArgArray(soa, receiver, args);
InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty()[]);
return result;
}

函数InvokeWithVarArgs将调用参数封装在一个数组中,然后再调用另外一个函数InvokeWithArgArray来参数mid指定的方法。

/* art/runtime/jni_internal.cc */
void InvokeWithArgArray(const ScopedObjectAccess& soa, ArtMethod* method,
ArgArray* arg_array, JValue* result, char result_type)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
uint32_t* args = arg_array->GetArray();
if (UNLIKELY(soa.Env()->check_jni)) {
CheckMethodArguments(method, args);
}
method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, result_type);
}

可以看到参数mid实际上是一个ArtMethod对象指针,因此,将它转换为一个ArtMethod指针(dalvik也是如此),于是就可以得到被调用类方法的相关信息了。

函数InvokeWithArgArray通过ArtMethod类的成员函数Invoke来调用参数method指定的类方法。ArtMethod类的成员函数Invoke的实现如下所示:

/* art/runtime/mirror/art_method.cc*/
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
char result_type) {
...... // Push a transition back into managed code onto the linked list in thread.
ManagedStack fragment;
self->PushManagedStackFragment(&fragment); Runtime* runtime = Runtime::Current();
// Call the invoke stub, passing everything as arguments.
if (UNLIKELY(!runtime->IsStarted())) {
......
if (result != NULL) {
result->SetJ();
}
} else {
const bool kLogInvocationStartAndReturn = false;
if (GetEntryPointFromCompiledCode() != NULL) {
......
#ifdef ART_USE_PORTABLE_COMPILER
(*art_portable_invoke_stub)(this, args, args_size, self, result, result_type);
#else
(*art_quick_invoke_stub)(this, args, args_size, self, result, result_type);
#endif
if (UNLIKELY(reinterpret_cast<int32_t>(self->GetException(NULL)) == -)) {
// Unusual case where we were running LLVM generated code and an
// exception was thrown to force the activations to be removed from the
// stack. Continue execution in the interpreter.
self->ClearException();
ShadowFrame* shadow_frame = self->GetAndClearDeoptimizationShadowFrame(result);
self->SetTopOfStack(NULL, );
self->SetTopOfShadowStack(shadow_frame);
interpreter::EnterInterpreterFromDeoptimize(self, shadow_frame, result);
}
......
} else {
......
if (result != NULL) {
result->SetJ();
}
}
} // Pop transition.
self->PopManagedStackFragment(fragment);
}

整个过程的重点就在art_protable_invoke_stub 和 art_quick_invoke_stub上,这也是整个hook工作的关键。函数中根据预定义宏ART_USE_PORTABLE_COMPILER来判断是protable 还是 quick 的方式。这里的protable 和 quick是android对于编译dex文件采用的两种不同的后端,protable生成的oat文件和传统的so,dll文件类似,处理不同模块之间的调用关系时需要重定位操作,而quick是通过线程的TLS中的跳转表来实现,不需要重定位操作,因此加载的速度更快。而android默认的是采用的quick,所以我们只分析quick的调用过程,也就是这里的art_quikc_invoke_stub。更详细的过程参考《老罗的android之旅》。

我们继续看art_quick_invoke_stub的源码:

/*art/runtime/arch/arm/quick_entrypoints_arm.S*/
/*
* Quick invocation stub.
* On entry:
* r0 = method pointer
* r1 = argument array or NULL for no argument methods
* r2 = size of argument array in bytes
* r3 = (managed) thread pointer
* [sp] = JValue* result
* [sp + ] = result type char
*/
ENTRY art_quick_invoke_stub
push {r0, r4, r5, r9, r11, lr} @ spill regs
.save {r0, r4, r5, r9, r11, lr}
.pad #
.cfi_adjust_cfa_offset
.cfi_rel_offset r0,
.cfi_rel_offset r4,
.cfi_rel_offset r5,
.cfi_rel_offset r9,
.cfi_rel_offset r11,
.cfi_rel_offset lr,
mov r11, sp @ save the stack pointer
.cfi_def_cfa_register r11
mov r9, r3 @ move managed thread pointer into r9
mov r4, #SUSPEND_CHECK_INTERVAL @ reset r4 to suspend check interval
add r5, r2, # @ create space for method pointer in frame
and r5, #0xFFFFFFF0 @ align frame size to bytes
sub sp, r5 @ reserve stack space for argument array
add r0, sp, # @ pass stack pointer + method ptr as dest for memcpy
bl memcpy @ memcpy (dest, src, bytes)
ldr r0, [r11] @ restore method*
ldr r1, [sp, #] @ copy arg value for r1
ldr r2, [sp, #] @ copy arg value for r2
ldr r3, [sp, #] @ copy arg value for r3
mov ip, # @ set ip to
str ip, [sp] @ store NULL for method* at bottom of frame
ldr ip, [r0, #METHOD_CODE_OFFSET] @ get pointer to the code
blx ip @ call the method
mov sp, r11 @ restore the stack pointer
ldr ip, [sp, #] @ load the result pointer
strd r0, [ip] @ store r0/r1 into result pointer
pop {r0, r4, r5, r9, r11, lr} @ restore spill regs
.cfi_adjust_cfa_offset -
bx lr
END art_quick_invoke_stub

前面的注释列出了 函数art_quick_invoke_stub被调用的时候,寄存器r0-r3的值,以及调用栈顶端的两个值。其中,

r0指向当前被调用的类方法,

r1指向一个参数数组地址,

r2记录参数数组的大小,

r3指向当前线程,

调用栈顶端的两个元素分别用来保存调用结果及其类型。

真正调用类方法的汇编指令如下:

ldr    ip, [r0, #METHOD_CODE_OFFSET]   @ get pointer to the code
blx ip @ call the method

这里的 METHOD_CODE_OFFSET 就是在ArtMethod*结构体中的偏移

/*art/runtime/asm_support.h*/
// Offset of field Method::entry_point_from_compiled_code_
#define METHOD_CODE_OFFSET

就是进入类方法的入口点,entry_point_from_compiled_code_字段,也是hook点。

0x03 调用约定

ART 其实也有两种执行模式,一种是本地机器指令,一种是类似于虚拟机的解释执行。ArtMethod结构体中的两个成员就和类方法入口有关:

// Compiled code associated with this method for callers from managed code.
const void* entry_point_from_compiled_code_; //本地机器指令入口 code_offset / GetCompiledCodeToInterpreterBridge (art_quick_to_interpreter_bridge) // Called by the interpreter to execute this method.
EntryPointFromInterpreter* entry_point_from_interpreter_; //解释执行入口 artInterpreterToInterpreterBridge / artInterpreterToCompiledCodeBridg

这两个成员都指针,其中EntryPointFromInterpreter* 是函数指针类型,实际上也就是一种调用,表示调用者是来自解释执行方式的一种调用约定

typedef void (EntryPointFromInterpreter)(Thread* self, MethodHelper& mh,
const CodeItem* code_item, ShadowFrame* shadow_frame,
JValue* result);

entry_point_from_interpreter_ 是作为调用者是解释执行的入口函数,也是分为两种情况:

  1.当前ArtMethod对应的方法如果是解释执行话,将entry_point_from_interpreter_ 设置为artInterpreterToInterpreterBridge;

  2.当前ArtMethod 对应的是方法是机器指令的话,就entry_point_from_interpreter_设置为artInterpreterToCompiledCodeBridge

而entry_point_from_compiled_code_表示调用者是机器指令的类方法入口,而他的值也是分为两种情况:

  1.被调用的方法,也就是ArtMethod 所对应的方法如果需要通过解释执行,则赋值为GetCompiledCodeToInterpreterBridge() 函数的返回值;

  2.ArtMethod 所对应的方法如果是本地机器指令,则直接指向方法在oat文件中的指令。

这两个字段的值的问题,更详细的可以阅读android art/runtime/class_linker.cc 文件中LinkCode()方法的源码,而这里我们hook的就是针对entry_point_from_compiled_code_ 字段。

可以通过art_quick_invoke_stub 汇编代码得出在调用ArtMethod 应该方法的执行入口时的栈帧布局:

   -(low)
| caller(Method *) | <- sp
| arg1 | <- r1
| arg2 | <- r2
| arg3 | <- r3
| ... |
| argN |
| callee(Method *) | <- r0
+(high)

前三个参数还会额外地保存在寄存器r1、r2和r3中。这样对于小于等于3个参数的类方法,就可以通过访问寄存器来快速地获得参数。

注意,传递给被调用类方法的参数并不是从栈顶第一个位置(一个位置等于一个字长,即4个字节)开始保存的,而是从第二个位置开始的,即sp + 4。这是因为栈顶的第一个位置是预留用来保存用来描述当调用类方法(Caller)的ArtMethod对象地址的。由于函数art_quick_invoke_stub是用来从外部进入到ART运行时的,即不存在调用类方法,因此这时候栈顶第一个位置会被设置为NULL。

0x04  Hook

之前说过,Method的id也就是jmethod实际上是一个指针,指向的就是代码类方法的ArtMethod结构体,通过类型转换就可以获得目标类方法的ArtMethod的指针

ArtMethod *artmeth = reinterpret_cast<ArtMethod *>(methid);

获得了ArtMethod* ,就可以设置类方法的entrypoint:

 if(art_quick_dispatcher != artmeth->GetEntryPointFromCompiledCode()){
uint64_t (*entrypoint)(ArtMethod* method, Object *thiz, u4 *arg1, u4 *arg2);
entrypoint = (uint64_t (*)(ArtMethod*, Object *, u4 *, u4 *))artmeth->GetEntryPointFromCompiledCode(); info->entrypoint = (const void *)entrypoint;
info->nativecode = artmeth->GetNativeMethod(); artmeth->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);

也就是如果替换了entry_point_from_compiled_code_的值,使其指向我们的代码art_quick_diapatcher,这时art_quick_invoke_stub调用我们自己的代码,但是调用约定并不是普通的arm下C/C++的调用约定,所以我们需要用汇编代码来对堆栈进行处理,然后再调用真正的额外执行的C++代码,而在C++代码中也需要返回原始的方法,同样的也需要对堆栈进行处理,同样需要借助汇编来还原堆栈,调用原始的entrypoint。

ENTRY art_quick_dispatcher
push {r4, r5, lr} @ sp -
mov r0, r0 @ pass r0 to method
str r1, [sp, #( + )] @ arg array
str r2, [sp, #( + )]
str r3, [sp, #( + )]
mov r1, r9 @ pass r1 to thread
add r2, sp, #( + ) @ pass r2 to args array
add r3, sp, # @ pass r3 to old SP
blx artQuickToDispatcher @ (Method* method, Thread*, u4 **, u4 **)
pop {r4, r5, pc} @ return on success, r0 and r1 hold the result
END art_quick_dispatcher

上面的汇编代码art_quick_dispatcher就是替换原始entrypoint的值,处理堆栈,然后调用自己的C++函数artQuickToDispatcher(),之后在artQuickToDispatcher()调用原始的entrypoint。当然这里对于原始的entrypoint是不能直接进行调用的,需要在利用一段汇编代码,将堆栈还原成art_quick_invoke_stub调用entrypoint时的样子。

/*
*
* Art Quick Call Entrypoint
* On entry:
* r0 = method pointer
* r1 = thread pointer
* r2 = args arrays pointer
* r3 = old_sp
* [sp] = entrypoint
*/
ENTRY art_quick_call_entrypoint
push {r4, r5, lr} @ sp -
sub sp, #( + ) @ sp - -
str r0, [sp, #( + )] @ var_40_0 = method_pointer
str r1, [sp, #( + )] @ var_40_4 = thread_pointer
str r2, [sp, #( + )] @ var_40_8 = args_array
str r3, [sp, #( + )] @ var_40_12 = old_sp
mov r0, sp
mov r1, r3
ldr r2, =
blx memcpy @ memcpy(dest, src, size_of_byte)
ldr r0, [sp, #( + )] @ restore method to r0
ldr r1, [sp, #( + )]
mov r9, r1 @ restore thread to r9
ldr r5, [sp, #( + )] @ pass r5 to args_array
ldr r1, [r5] @ restore arg1
ldr r2, [r5, #] @ restore arg2
ldr r3, [r5, #] @ restore arg3
ldr r5, [sp, #( + + )] @ pass ip to entrypoint
blx r5
add sp, #( + )
pop {r4, r5, pc} @ return on success, r0 and r1 hold the result
END art_quick_call_entrypoint

也就是art_quick_call_entrypoint恢复原来的堆栈,调用原始的entrypoint。但是,还有一个问题存在,也就是ART中关于延迟加载的问题。

 /* art/runtime/class_linker.cc*/
static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class,
uint32_t method_index)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Method shouldn't have already been linked.
//判断类方法是否已经加载链接了
DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
// Every kind of method should at least get an invoke stub from the oat_method.
// non-abstract methods also get their code pointers. /*method_index描述的索引号可以在oat_class表示的OatClass结构体中找到一个OatMethod结构体oat_method。
这个OatMethod结构描述了类方法method的本地机器指令相关信息,
通过调用它的成员函数LinkMethod可以将这些信息设置到参数method描述的ArtMethod对象中去
*/
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
//在LinkMethod中将ArtMethod中的entry_point_from_compiled_code_设置为code_offset
oat_method.LinkMethod(method.get()); // Install entry point from interpreter.
Runtime* runtime = Runtime::Current();
boolenter_interpreter=NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
/*为了统一管理,为一个类方法都设置一个解释器入口点。需要通过解释执行的类方法的解释器入口点函数是artInterpreterToInterpreterBridge,
它会继续通过解释器来执行该类方法。需要通过本地机器指令执行的类方法的解释器入口点函数是artInterpreterToCompiledCodeBridge,
它会间接地调用该类方法的本地机器指令。*/
if (enter_interpreter) {
//需要解释执行 设置entry_point_from_interpreter_
method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
} else {
//native code
method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
} if (method->IsAbstract()) {
// 设置entry_point_from_compiled_code_
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
return;
}
//trampoline 延迟链接
if (method->IsStatic() && !method->IsConstructor()) {
// For static methods excluding the class initializer, install the trampoline.
// It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
// after initializing class (see ClassLinker::InitializeClass method).
method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
} else if (enter_interpreter) {
// Set entry point from compiled code if there's no code or in interpreter only mode.
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
} if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative(Thread::Current());
} // Allow instrumentation its chance to hijack code.
runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
method->GetEntryPointFromCompiledCode());
}

LinkCode 源码

在LinkCode() 的源码中可以看到这句代码:

 //trampoline 延迟链接
if (method->IsStatic() && !method->IsConstructor()) {
// For static methods excluding the class initializer, install the trampoline.
// It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
// after initializing class (see ClassLinker::InitializeClass method).
method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
}

将entrypoint的设置为GetResolutionTrampoline() 的返回值,而这里就是

/*art/runtime/entrypoints/entrypoint_utils.h*/
static inline const void* GetCompiledCodeToInterpreterBridge() {
#if defined(ART_USE_PORTABLE_COMPILER)
return GetPortableToInterpreterBridge();
#else
return GetQuickToInterpreterBridge();
#endif
}

这里就是延迟链接,意思是在加载和链接类的时候,部分方法的entrypoint设置的并不是本地机器指令,或者解释执行的入口,而是一个代理函数。而这个代理函数真正是干什么的?简单来说就是延迟链接,只有当真正调用这个类方法的时候,调用trampoline 函数才会对这个类方法进行链接,设置ArtMethod*的entry_point_from_compiled_code_的值为真正的本地机器指令或者解释执行入口。那这时在之前设置的entry_point_from_compiled_code_ 的值为art_quick_dispatcher的地址就被覆盖调用了,所以需要在我们自己的artQuickToDispatcher调用完原始的entrypoint以后,再对entrypoint进行一次判断和赋值:

  /*
* 处理的就是trampoline 在调用原来的tramp方法以后,重新绑定entry_pooint_from_complied_ 字段*/
entrypoint = method->GetEntryPointFromCompiledCode();
if(entrypoint != (const void *)art_quick_dispatcher){
LOGW("[*] entrypoint was replaced. %s->%s", info->classDesc, info->methodName); method->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);

整个ART模式下的hook流程大致就是如此。

项目代码:https://github.com/boyliang/AllHookInOne

android ART hook的更多相关文章

  1. Android ART运行时与Dalvik虚拟机

    这几天在做一个项目时需要在Android中使用OSGi框架(Apache Felix),于是在一个android 4.4.2 版本系统的某品牌的平板上实验. 实验内容很简单:把felix包里的feli ...

  2. Android ART简介

    一.    Android ART简介 Android DEX/ODEX/OAT文件

  3. Android ART

    这几天在做一个项目时需要在Android中使用OSGi框架(Apache Felix),于是在一个android 4.4.2 版本系统的某品牌的平板上实验.实验内容很简单:把felix包里的felix ...

  4. Android ART运行时无缝替换Dalvik虚拟机的过程分析

    Android ART运行时无缝替换Dalvik虚拟机的过程分析 分类: Android2014-01-13 00:59 42722人阅读 评论(66) 收藏 举报 AndroidARTDalvikV ...

  5. 使用cydia substrate 来进行android native hook

      cydia不仅可以hook java代码,同样可以hook native代码,下面举一个例子来进行android native hook 我是在网上找到的supermathhook这个项目,在他基 ...

  6. Android Exception Hook

    承接上一篇文章Android Inline Hook,接下来我们看一下android系统中基于异常的hook方式,这种方式与inline hook相比实现较为简单,但执行效率是它的短板. except ...

  7. Android Xpose Hook(一)

    实验环境:     Droid4x模拟器 (目前Android版本4.2.2)     Android Studio 1.下载相关工具 XposedInstaller下载 http://repo.xp ...

  8. android inline hook

    最近终于沉下心来对着书把hook跟注入方面的代码敲了一遍,打算写几个博客把它们记录下来. 第一次介绍一下我感觉难度最大的inline hook,实现代码参考了腾讯GAD的游戏安全入门. inline ...

  9. Android ART、Dalvik在multidex上的差异、关联

    为提升应用运行性能,谷歌官方从5.0(api level:21)版本开始,将虚拟机运行环境默认为ART, 此处主要研究ART.Dalvik在multidex处理上的差异和关联,做了一个简单的手绘,如下 ...

随机推荐

  1. PHP开发——进制转换

    常用进制 l  10进制:有10个基本数,分别为0.1.2.3.4.5.6.7.8.9,运算规则”逢10进1”: l  8进制:有8个基本数,分别为0.1.2.3.4.5.6.7,运算规则”逢8进1” ...

  2. CSS的六大选择器

    选择器:选择器是一种模式,用于选择需要添加样式的元素. 首先简述六大选择器 基本选择器 标签选择器 类选择器 ID选择器 高级选择器 层次选择器 结构伪类选择器 属性选择器 其中基本选择器与层次选择器 ...

  3. 深入理解JVM(一)编译openJDK

    此文总结的很不错:https://www.cnblogs.com/ACFLOOD/p/5528035.html 准备openJDK源码和环境 1.在linux和macOS上编译openJDK更加友好, ...

  4. NFS服务简介与配置

    NFS简介 NFS特点 NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源 在NFS的应用中, ...

  5. jenkins可选插件为空的解决方式

    我是安装的jenkins,文件名字是这个jenkins.msi,点finish安装完成,然后浏览器就自动打开了jenkins页面,输入密码后,便进入了如下页面 之后我是选择的跳过插件安装,在可选插件里 ...

  6. ActiveMQ_7JMX

    activemq配置jmx 配置activemq中的jmx可以用于监控activemq信息. activemq.xml配置 修改broker属性 添加节点managementContext <m ...

  7. xpath和lxml类库

    1. xpath和lxml lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息 2. 什么是xpath XPath (XML ...

  8. DL_1_week1_概论

    standard NN,CNN,RNN,custom hybrid NN architecture(Radar) 结构化数据是数据的数据库,相比结构化数据计算机更难理解非结构化数据,人生来很容易理解非 ...

  9. Beta冲刺 (3/7)

    Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 熟悉并编写小程序的自定义控件 编辑文 ...

  10. JSP的分页技术

    在实际应用中,如果从数据库中查询的记录特别的多,甚至超过了显示屏的显示范围,这个时候可将结果进行分页显示. 假设总记录数为intRowCount,每页显示的数量为inPageSize,总页数为intP ...