2016976438 发表于 2023-5-1 12:32

luckfollowme arm学习篇 9 --在C内写arm介绍

# luckfollowme arm学习篇 9 --在C内写arm介绍

这章是为了给 手写 **inline Hook** 做些准备的,要手写 **inline hook**无非两种,

一种就像 dobby 那样插入字节码

```c
void EmitInt64(int64_t value) {
    buffer_->Emit64(value);
}
```

还有一种就是 **内联汇编 asm** 或者 通过 **gnu assembly**

本次目标就是做到能够在 **C内联 ARM**



## Inline ASM

**Inline Assembly**一般来说 c/c++ 的编译器都自带 汇编编译器

最明显的关键字就是 **asm**

为了我们能够使用 **asm**

您需要在 **CMakeLists.txt** 中加入:

```cmake
# 启动支持 asm 汇编
enable_language(ASM)
```

而 **asm** 一般跟**volatile** 一起使用,来告诉编译器编译的时候不要进行优化。

并且它还可以根据 **输出操作数** 和 **输入操作数** 来跟 **C/C++** 的变量进行交互

```
asm volatile("add %0,%1,%2":输出操作符:输入操作符1,输入操作符2)
```



组合起来就是这样:

```c
int a = 100;
int b = 10;
int sum;
asm volatile("add %0,%1,%2":"=r"(sum):"r"(a),"r"(b)); # sum = a + b
```

其中 **r** 代表通用寄存器**=r** 表示输出到的位置



下面是一个完整的例子:

首先您创建一个 **AssemblyDemo.cpp**

代码如下:

```c
#include "stdio.h"
#include "stdint.h"

uint64_t _global_sum;


int sum(int a,int b){
    int _sum;
    asm volatile("add %w0 ,%w1,%w2":"=r"(_sum):"r"(a),"r"(b));
    return _sum;
}


int main()
{

    //1. 测试b 指令 死循环
    while (true)
    {
      int ret = sum(10,99);
      //3. 测试传参 和 调用方法
      printf("sum:%d\n",ret);
      getchar();
    }
    return 0;
}

```

随后您修改 **ndk_builder.py** 和 **CMakeLists.txt**

```cmake
# CMakeLists.txt
set(assmbly_demo1
    ./source/AssemblyDemo1.cpp
)
add_executable(assmblyDemo1 ${assmbly_demo1})
```



记住,我们只测试 **arm64** 的,您应该修改 **abis** 否则 如果变成其他 **arch** 架构的平台 ,您写的汇编指令肯定是不通过的。

```python
# abis = ["x86", "x86_64", "armeabi-v7a", "arm64-v8a"]
abis = ["arm64-v8a"]
```

编译出来后,我们拖入到 **ida pro** 静态分析一下:

</p>



</p>

可以看到 生成的 **add** 貌似并不是一条指令,这是正常的,因为它涉及到 **读取和写入局部变量** 也就是 **输出操作符和输出操作符**的位置

```asm
# 分配局部变量空间
SUB             SP, SP, #0x10
# 储存 x0-x1 的参数
STR             W0,
STR             W1,
# 读取 x0-x1 的参数
LDR             W8,
LDR             W9,
# a + b
ADD             W8, W8, W9
# _sum = a + b
STR             W8,
# 读取 _sum 到 x0 准备返回值
LDR             W0,
# 释放局部变量空间
ADD             SP, SP, #0x10
# 返回
RET
```

## GAS

一般来说 写 gas 更方便一点,它的全称叫 **GNU ASM**

它里面有很多 **Assembly Directive(汇编指示符)** 来帮助我们编写各种格式的汇编

汇编指示符有点多,直接说长篇大论有点麻烦,也不容易理解,我们通过 将 原有代码 转换成 **GAS** 的汇编文件进行分析



为了分析的更方便,后面我都写成 **.c** 文件 而不是 **.cpp**



1.首先我们编写简单的**HelloAssembly.c**文件

```c
#include <stdio.h>

int add(int a,int b){
    return a + b + 100;
}

int main(int argc, char const *argv[])
{
    while (1)
    {
      int a = 10;
      int b = 30;
      int sum = add(a,b);
      printf("sum:%d\n",sum);
      getchar();
    }
   

    /* code */
    return 0;
}

```

2.通过**llvm**的**aarch64**交叉编译器转换成**gas**的汇编文件

aarch64 的教程编译器 我们使用clang 它位于 ndk 的

**ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd**

使用方式如下:

```
aarch64-linux-android21-clang -S 转换成assembly -o 输出位置 来源文件
```

以下是我写的 **arrch64-assembly.bat**下的脚本:

```
C:\Users\hp\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang-S -o ./helloassembly.S    HelloAssembly.c   

```

运行完您的 ``helloassmbly.S``内容如下:

```asm
        .text
        .file        "HelloAssembly.c"
        .globl        add                           // -- Begin function add
        .p2align        2
        .type        add,@function
add:                                    // @add
        .cfi_startproc
// %bb.0:
        sub        sp, sp, #16
        .cfi_def_cfa_offset 16
        str        w0,
        str        w1,
        ldr        w8,
        ldr        w9,
        add        w8, w8, w9
        add        w0, w8, #100
        add        sp, sp, #16
        ret
.Lfunc_end0:
        .size        add, .Lfunc_end0-add
        .cfi_endproc
                                        // -- End function
        .globl        main                            // -- Begin function main
        .p2align        2
        .type        main,@function
main:                                 // @main
        .cfi_startproc
// %bb.0:
        sub        sp, sp, #48
        stp        x29, x30,              // 16-byte Folded Spill
        add        x29, sp, #32
        .cfi_def_cfa w29, 16
        .cfi_offset w30, -8
        .cfi_offset w29, -16
        stur        wzr,
        stur        w0,
        str        x1,
        b        .LBB1_1
.LBB1_1:                              // =>This Inner Loop Header: Depth=1
        mov        w8, #10
        str        w8,
        mov        w8, #30
        str        w8,
        ldr        w0,
        ldr        w1,
        bl        add
        str        w0,
        ldr        w1,
        adrp        x0, .L.str
        add        x0, x0, :lo12:.L.str
        bl        printf
        bl        getchar
        b        .LBB1_1
.Lfunc_end1:
        .size        main, .Lfunc_end1-main
        .cfi_endproc
                                        // -- End function
        .type        .L.str,@Object                  // @.str
        .section        .rodata.str1.1,"aMS",@progbits,1
.L.str:
        .asciz        "sum:%d\n"
        .size        .L.str, 8

        .ident        "Android (8490178, based on r450784d) clang version 14.0.6 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0cca074e9238af8b4106c30add4418f6)"
        .section        ".note.GNU-stack","",@progbits

```

然后我们通过这个 **.S** 的 **gas** 汇编文件 通过 cmake 生成:

```cmake
set(assmbly_gas_demo1
    ./source/assembly/helloassembly.S
)
add_executable(assmblygasdemo1 ${assmbly_gas_demo1})
```

您可以发现它依旧可以生成可执行程序。

也就是说我们可以通过 **gas** 汇编文件写出跟 **c** 一样的效果

汇编各位目前都看的懂吧,那么就差一点, gas 的 **assembly directive** 汇编助记符

下章我们将会逐步分析

shuaibi_chen 发表于 2023-5-1 13:16

熟悉的汇编哈哈哈

moruye 发表于 2023-5-1 21:47

xuehailong 发表于 2023-5-2 07:14

学习学习

debug_cat 发表于 2023-12-11 16:48

这里sum函数汇编需要知道申请函数空间,栈顶指针sub就是申请空间,add就是函数执行完恢复之前的lr返回上函数,中间就是取参数和做加法了{:1_892:}

debug_cat 发表于 2023-12-11 16:51

debug_cat 发表于 2023-12-11 16:48
这里sum函数汇编需要知道申请函数空间,栈顶指针sub就是申请空间,add就是函数执行完恢复之前的lr返回上函 ...

这里错了,如果叶子函数是不会恢复lr,非叶子函数需要恢复现场,跑到上一个函数调用结束位置继续跑{:1_906:}
页: [1]
查看完整版本: luckfollowme arm学习篇 9 --在C内写arm介绍