Art中的函数inline
zsk Lv4

在art中有大量的inline内联函数,就先来了解什么是inline

什么是inline

inline: 内联函数是一种优化技术,可以减少函数调用的开销,渐少参数压栈时消耗空间,从而提高代码的执行效率。使用inline关键字声明的函数在编译时会被内联展开,即编译器会在每个调用该函数的地方插入该函数的实际代码,而不是生成函数调用。关键字inline必须与函数定于放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面是不起任何作用。inline对编译器来水只是一种建议,编译器可以选择忽略这个建议,因此有inline不一定就会被编译器内联编译。

ALWAYS_INLINE: 是强制内联,所有加 inline __attribute__((always_inline)) 修饰的函数在被调用的时候不会被编译成函数调用,而是直接扩展到调用函数体内。当含有递归的函数在任何情况下都不会被编译器进行inline 编译。

1
2
3
4
5
6
7
8
9
10
11
__attribute__((always_inline)) int add(int a, int b) {
return a + b;
}

int sum(int m) {
int result = 0;
for (int i = 0; i < m; ++i) {
result = add(result, i);
}
return result;
}

在加与不加__attribute__((always_inline))的情况下,编译后的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall sum(int a1)
{
int i; // [xsp+4h] [xbp-Ch]
unsigned int v3; // [xsp+8h] [xbp-8h]

v3 = 0;
for ( i = 0; i < a1; ++i )
v3 = add(v3, i);
return v3;
}

__int64 __fastcall add(int a1, int a2)
{
return (unsigned int)(a1 + a2);
}

image
image

加了__attribute__((always_inline)) 后的sum函数

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sum(int a1)
{
int i; // [xsp+Ch] [xbp-14h]
unsigned int v3; // [xsp+10h] [xbp-10h]

v3 = 0;
for ( i = 0; i < a1; ++i )
v3 += i;
return v3;
}

image

少了 BL add,相比于原来的,汇编指令减少了,也省去了函数调用时栈空间的花销。

总结:

  1. 内联函数在编译器最终生成的代码中是没有定义的,这个函数是不存在的,也就无法实现对内联函数的hook。
  2. 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回等)
  3. 内联函数是一种特殊的函数,在源码中具有普通函数的特征。
  4. 内联函数是对编译器的一种请求,因此编译器有可能拒绝这种请求。
  5. 内联函数由编译器处理,直接将编译后的函数插入调用的地方。
  6. 内联和宏的效果很像,但是宏代码是由预处理器处理,进行简单的文本替换,没有任何编译过程

基于frida版的art

上一篇写了art下DexFile的内存布局,那如何拿到DexFile的对象呢?
通过查看源码发现在ART中获取dexfile对象的函数是个inline,那又要怎么去调用呢

1
2
3
inline const DexFile* ArtMethod::GetDexFile() {
return GetDexCache()->GetDexFile();
}

当在源码中进行定制ART时,任何inline函数都可以直接访问,只需要注意访问权限即可,那如果是使用frida等hook技术进行ART的定制呢?自然inline函数无法被hook,那当需要这个inline函数的功能时,如何实现inline函数的主动调用呢?
两种解决方案:
1. 分析inline函数的源码逻辑,自行参考实现即可
2. 在源码中添加一个导出函数调用该inline函数,编译后直接参考ida反编译的该函数内容即可
实现第二种
接下来解决从一个artmethod对象得到一个该对象所属的dexfile对象。
在源码 /art/runtime/art_method-inl.h 添加如下代码导出dexfile对象

1
2
3
extern "C" const DexFile* getDexFileByMethod(ArtMethod* artmethod) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
return artmethod->GetDexFile();
};

重新编译,找到编译好后lib32,lib64下的libart.so,ida打开找到添加的函数片段,在nexus 5 android6和pixel android8下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// nexus5 arm32
int __fastcall getDexFileByMethod(int a1)
{
return *(_DWORD *)(*(_DWORD *)(*(_DWORD *)a1 + 16) + 32);
}

// pixel arm32
int __fastcall getDexFileByMethod(int a1)
{
int result; // r0

if ( *(_DWORD *)(a1 + 4) & 0x40000 )
result = *((_DWORD *)art::ArtMethod::GetObsoleteDexCache((char *)a1) + 4);
else
result = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)a1 + 16) + 16);
return result;
}

// pixel arm64
__int64 __fastcall getDexFileByMethod(art::ArtMethod *a1)
{
__int64 result; // x0

if ( *((_DWORD *)a1 + 1) & 0x40000 )
result = *(_QWORD *)(art::ArtMethod::GetObsoleteDexCache(a1) + 16LL);
else
result = *(_QWORD *)(*(unsigned int *)(*(unsigned int *)a1 + 0x10LL) + 0x10LL);
return result;
}

实际是artmethod对象获取dexfile对象inline函数的内联编译生成的片段,
现在参考ida反编译的该函数,根据自己的安卓系统版本做调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <jni.h>
#include <string>
#include <android/log.h>
#define _DWORD uint32_t
#define __int64 long long
#define _QWORD uint64_t

// #if defined(__arm__)
// typedef uint32_t (GetObsoleteDexCache)(void *a1);
// //arm32 a1=artmethod指针,a2是函数art::ArtMethod::GetObsoleteDexCache(art::ArtMethod *this)的地址
// extern "C" JNIEXPORT uint32_t GetDexFile(void *a1, GetObsoleteDexCache funcptr) {
// uint32_t result; // r0
// if (*((_DWORD *) a1 + 1) & 0x40000)
// result = *(_DWORD *) (funcptr(a1) + 16);
// else
// result = *(_DWORD *) (*(_DWORD *) (*(_DWORD *) a1 + 16) + 16);
// return result;
// }
// #else
// typedef uint64_t (GetObsoleteDexCache)(void *a1);
// //arm64 a1=artmethod指针,a2是函数art::ArtMethod::GetObsoleteDexCache(art::ArtMethod *this)的地址
// extern "C" JNIEXPORT __int64 GetDexFile(void*a1, GetObsoleteDexCache funcptr)
// {
// __int64 result; // x0
// if ( *((_DWORD *)a1 + 1) & 0x40000 )
// result = *(_QWORD *)(funcptr(a1) + 16LL);
// else
// result = *(_QWORD *)(*(unsigned int *)(*(unsigned int *)a1 + 0x10LL) + 0x10LL);
// return result;
// }
// #endif

// a1=artmethod指针
extern "C" JNIEXPORT uint32_t GetDexFile(void* a1)
{
return *(_DWORD *)(*(_DWORD *)(*(_DWORD *)a1 + 16) + 32);
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_zsk_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

打包解压,把lib目录下的so推送到手机上,修改777权限
编写frida脚本

  1. 先加载导入的so,枚举该so的导出函数,找到编写的GetDexFile地址
  2. 如果是安卓8及以上,需要枚举libart.so的符号表,找到GetObsoleteDexCache的地址
  3. 声明GetDexFile函数
  4. 通过反射获取任意类的java层函数,通过$handle获取函数的引用,再由jni的函数
    fromReflectedMethod转为artmethod对象
  5. 将参数传入声明的GetDexFile函数得到dexfile对象
  6. 根据art中的DexFile内存布局dump内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    function getDexFileByMethod(){
    Java.perform(function(){
    console.log("go into init," + "Process.arch:" + Process.arch);
    var module_libext = null;
    // 加载导入的so
    if (Process.arch == "arm64") {
    module_libext = Module.load("/data/local/tmp/libmy_64.so");
    } else if (Process.arch == "arm") {
    module_libext = Module.load("/data/local/tmp/libmy.so");
    }
    var getDexFileByMethodFunc = null;
    var getDexFileByMethodAddr = null;
    if (module_libext != null) {
    module_libext.enumerateExports().forEach(function(symbol){
    // 找到编写的GetDexFile函数
    if (symbol.name.indexOf("GetDexFile") != -1) {
    console.log(JSON.stringify(symbol))
    getDexFileByMethodAddr = symbol.address;
    }
    })
    }
    if (getDexFileByMethodAddr != null) {
    getDexFileByMethodFunc = new NativeFunction(getDexFileByMethodAddr, "pointer", ["pointer"]);
    // 第一个参数是artmethod,第二个参数是GetObsoleteDexCache
    // getDexFileByMethodFunc = new NativeFunction(getDexFileByMethodAddr, "pointer", ["pointer", "pointer"]);
    }
    // 找到GetObsoleteDexCache,安卓8及以上才需要
    // var GetObsoleteDexCacheAddr = null;
    // var libartmodule = Process.getModuleByName("libart.so");
    // libartmodule.enumerateSymbols().forEach(function(symbol){
    // if (symbol.name.indexOf("GetObsoleteDexCache") != -1) {
    // console.log(JSON.stringify(symbol))
    // GetObsoleteDexCacheAddr = symbol.address;
    // }
    // })

    // 通过反射获取任意一个java层函数, 再将函数转为artmethod对象
    var MainActivity = Java.use("com.example.zsk.MainActivity");
    var methods = MainActivity.class.getDeclaredMethods(); // 枚举所有函数
    methods.forEach(function (method) {
    // console.log(method);
    var methodhandle = method.$handle; // 获取函数引用
    console.log(methodhandle)
    var ArtMethodPtr = Java.vm.tryGetEnv().fromReflectedMethod(methodhandle) // 将java函数转为artmethod对象
    if (getDexFileByMethodFunc != null) {
    var dexfilePtr = getDexFileByMethodFunc(ArtMethodPtr);
    // var dexfilePtr = getDexFileByMethodFunc(ArtMethodPtr, GetObsoleteDexCacheAddr);
    console.log(method.toString() + "------" + ArtMethodPtr + "------" + dexfilePtr);
    var dexfileBegin = ptr(dexfilePtr).add(Process.pointerSize * 1).readPointer();
    var dexfileSize = ptr(dexfilePtr).add(Process.pointerSize * 2).readU32();
    console.warn("get a dex:size:" + dexfileSize + "---" + hexdump(dexfileBegin, {
    length: 16
    }))
    console.log("go into LoadMethodaddr->" + hexdump(dexfilePtr, {
    length: 32
    }));
    }
    })
    })
    }
    function main(){
    getDexFileByMethod();
    }
    setImmediate(main);
    打印内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    go into init,Process.arch:arm
    {"type":"function","name":"GetDexFile","address":"0x9cb9d471"}
    0x1004c2
    protected void com.example.zsk.MainActivity.onCreate(android.os.Bundle)------0xb0697cf0------0xab0da3c0
    get a dex:size:2100--- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
    9fc11078 64 65 78 0a 30 33 35 00 ee 74 d8 e6 95 2a 0a 65 dex.035..t...*.e
    go into LoadMethodaddr-> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
    ab0da3c0 e8 3e c3 b4 78 10 c1 9f 34 08 00 00 41 00 00 00 .>..x...4...A...
    ab0da3d0 32 00 00 00 40 b2 53 ab e5 9a 18 6c 00 00 00 00 2...@.S....l....
    0x1004de
    public native java.lang.String com.example.zsk.MainActivity.stringFromJNI()------0xb0697d18------0xab0da3c0
    get a dex:size:2100--- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
    9fc11078 64 65 78 0a 30 33 35 00 ee 74 d8 e6 95 2a 0a 65 dex.035..t...*.e
    go into LoadMethodaddr-> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
    ab0da3c0 e8 3e c3 b4 78 10 c1 9f 34 08 00 00 41 00 00 00 .>..x...4...A...
    ab0da3d0 32 00 00 00 40 b2 53 ab e5 9a 18 6c 00 00 00 00 2...@.S....l....
 评论