版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

Thumb指令集

ARM 指令集:最早在 1985 年随第一代 ARM 处理器问世。ARM 指令集一开始是 32 位固定长度的指令,用于各种计算任务。

Thumb 指令集:在 1994 年的 ARM7TDMI 处理器中首次引入。这是在 ARM 指令集基础上开发的一个 16 位指令集,旨在优化嵌入式系统中代码密度和内存效率。

寄存器 Thumb 跟 ARM 是一样的。

随着 ARM 架构的演进,后来加入了 32 位的 Thumb 指令,称为 Thumb-2 指令集。

以简单的加载立即数到寄存器为例,解释一下 16 位和 32 位的 Thumb 指令的区别

16 位 Thumb 指令

MOVS R0, #1   // 将立即数 1 加载到 R0,指令长度为 16 位

32 位 Thumb 指令

MOV.W R0, #65535  // 将较大的立即数 65535 加载到 R0,指令长度为 32 位

由于 16 位 Thumb 指令长度有限,无法直接处理大立即数,而 32 位 Thumb 指令支持更大的立即数范围。32 位指令格式可以容纳更多位的立即数、更复杂的操作码。

在 ARM64 架构(也称为 AArch64)中,不再支持 Thumb 指令集,因此也没有 16 位和 32 位的 Thumb 指令。

ARM64 仅支持 32 位的固定长度指令,取消了 Thumb 指令集以及 ARM 和 Thumb 模式的切换,从而简化了指令解码和执行流程。

Thumb指令集文档

google 搜索 armv7 site:arm.com

截图.png

找到arm指令参考手册并下载pdf到本地:https://developer.arm.com/documentation/ddi0406/latest/

在文档书签目录可以看到有16位和32位 Thumb 指令集文档,也可以找 thumb 指令的编码方式说明

截图.png

Thumb 指令的编码方式

以下面的 16 位 Thumb 指令为例

MOVS R0, #1   // 将立即数 1 加载到 R0,指令长度为 16 位

在 16 位 Thumb 指令集中,MOVS Rd, #imm8 指令的编码格式如下

1514131211109876543210
含义00100Rd(3 位)Imm(8 位)

其中:

  • 位 15-11:00100 是 MOVS 指令的操作码。

  • 位 10-8:寄存器 Rd,即目标寄存器的编码,这里 R0 的编码为 000。

  • 位 7-0:立即数 imm8,8 位长度的立即数。

对于 MOVS R0, #1:

  • 操作码:00100(固定)。

  • 寄存器 Rd:目标寄存器为 R0,编码为 000。

  • 立即数 imm8:#1 的二进制为 0000 0001。

将这些字段组合在一起

0010 0000 0000 0001

十六进制值为 0x2001 image.png 转为为小端模式后为 0x0120

在 ARM 架构(包括 Thumb 指令集)中,机器码在存储时通常使用 小端模式。小端模式下,低字节先存储,所以存储在内存中的顺序会变为 01 20。

打开 https://armconverter.com/?disasm,验证一下是否正确 image.png

编译 Thumb 汇编指令

编写一个C程序 hello_thumb.c 源码如下

#include <stdio.h>

int main() {
    int result = 1 + 1;
    printf("1 + 1 = %d\n", result);
    return 0;
}

执行下面命令把 hello_thumb.c 编译成 thumb 汇编代码文件 hello_thumb.s

armv7a-linux-androideabi35-clang -S -mthumb hello_thumb.c -o hello_thumb.s

-mthumb 表示使用 thumb 汇编

关于ARM程序生成过程可以参考这篇文章【详解ARM64可执行程序的生成过程

打开 hello_thumb.s 可以看到,结构上和 ARM 汇编是差不多的,多了一些 thumb 标记,如:.code 16、.thumb_func 标记为 16 位 thumb,还有使用到 thumb 指令。

    .text                                // 指令区段开始
    .syntax unified                      // 统一汇编语法
    .eabi_attribute 67, "2.09"           // EABI 属性,符合 ARM EABI 2.09
    .eabi_attribute 6, 10                // EABI 属性,CPU 架构为 ARMv7
    .eabi_attribute 7, 65                // EABI 属性,CPU 为 Application Profile (A-profile)
    .eabi_attribute 8, 1                 // EABI 属性,使用 ARM 指令集
    .eabi_attribute 9, 2                 // EABI 属性,使用 Thumb-2 指令集
    .fpu neon                            // 指定 NEON 浮点单元 (FPU) 支持
    .eabi_attribute 34, 1                // EABI 属性,支持未对齐的内存访问
    .eabi_attribute 15, 1                // EABI 属性,RW 数据符合 PCS
    .eabi_attribute 16, 1                // EABI 属性,RO 数据符合 PCS
    .eabi_attribute 17, 2                // EABI 属性,GOT 符合 PCS
    .eabi_attribute 20, 1                // EABI 属性,FP 支持非正规数
    .eabi_attribute 21, 0                // EABI 属性,不支持 FP 异常
    .eabi_attribute 23, 3                // EABI 属性,IEEE 754 浮点模型
    .eabi_attribute 24, 1                // EABI 属性,内存对齐需求
    .eabi_attribute 25, 1                // EABI 属性,内存对齐保留
    .eabi_attribute 38, 1                // EABI 属性,支持 16 位浮点格式
    .eabi_attribute 18, 4                // EABI 属性,wchar_t 为 4 字节
    .eabi_attribute 26, 2                // EABI 属性,enum 类型大小为 32 位
    .eabi_attribute 14, 0                // EABI 属性,不使用 R9 寄存器作为特殊用途
    .file "hello_thumb.c"                // 文件名 "hello_thumb.c"
    .globl main                          // 声明全局符号 main
    .p2align 2                           // 代码段对齐到 4 字节
    .type main,%function                 // 声明 main 函数
    .code 16                             // 使用 Thumb 指令集
    .thumb_func                          // 标记为 Thumb 函数
main:
    .fnstart                             // 函数开始标记
@ %bb.0:
    .save {r7, lr}                       // 保存 r7 和 lr 到栈中
    push {r7, lr}                        // 压栈保存 r7 和 lr
    .setfp r7, sp                        // 设置帧指针 r7 指向当前栈指针 sp
    mov r7, sp                           // 将 sp 值复制到 r7
    .pad #16                             // 保留 16 字节栈空间
    sub sp, #16                          // sp 减 16,分配栈空间
    movs r0, #0                          // 将 0 存入 r0
    str r0, [sp, #4]                     // 将 r0 (0) 存入栈的偏移量 4 处
    str r0, [sp, #12]                    // 将 r0 (0) 存入栈的偏移量 12 处
    movs r0, #2                          // 将 2 存入 r0
    str r0, [sp, #8]                     // 将 r0 (2) 存入栈的偏移量 8 处
    ldr r1, [sp, #8]                     // 将偏移量 8 的值 (2) 加载到 r1 中
    ldr r0, .LCPI0_0                     // 加载 .LCPI0_0 到 r0 中
.LPC0_0:
    add r0, pc                           // 计算字符串地址并将其加到 r0
    bl printf                            // 调用 printf 函数
                                        // printf 执行完成后,返回主函数
    ldr r0, [sp, #4]                     // 从栈的偏移量 4 处加载 r0 (0)
    add sp, #16                          // 恢复栈指针,释放 16 字节的栈空间
    pop {r7, pc}                         // 恢复 r7 并从栈中弹出返回地址到 pc
    .p2align 2                           // 代码对齐
@ %bb.1:
.LCPI0_0:
    .long .L.str-(.LPC0_0+4)             // 存储字符串 .L.str 的偏移量
.Lfunc_end0:
    .size main, .Lfunc_end0-main         // 指定 main 函数的大小
    .cantunwind                          // 标记不能展开
    .fnend                               // 函数结束标记
                                        // main 函数定义结束
    .type .L.str,%object                 // 声明字符串常量
    .section .rodata.str1.1,"aMS",%progbits,1  // 只读数据段,存储字符串
.L.str:
    .asciz "1 + 1 = %d\n"                // 字符串 "1 + 1 = %d\n"
    .size .L.str, 12                     // 指定字符串的大小为 12 字节

    .ident "Android ... clang version ..."  // 编译器标识符
    .section ".note.GNU-stack","",%progbits   // 栈保护

执行下面命令把 thumb 汇编文件编译成可执行程序 hello_thumb。

armv7a-linux-androideabi35-clang hello_thumb.s -o hello_thumb

把 hello_thumb 推送到 android 设备并执行

adb push .\hello_thumb /data/local/tmp
adb shell chmod +x /data/local/tmp/hello_thumb
adb shell /data/local/tmp/hello_thumb

输出如下 image.png

在 gdb gef 调试中识别 thumb 指令

在 arm 汇编指令中,bx lr 中 bx 指跳转到 lr 地址中的 thumb 指令,blx 是调用 thumb 指令方法,其中的 x 就是代表 thumb 指令,没有 x 就是 arm 指令。

在 gdb gef 调试中,当走到 bx 时,cpsr 中 THUMB 高亮显示,表示当前指令已经切换到 thumb 模式 截图.png

IDA Pro 中切换 Thumb 模式

跳转到对应地址后 C 一下转换为代码

点击 Edit -> segments -> change segment register value(Alt+G) 截图.png

修改T的值。0x0为ARM指令,0x1为Thumb指令。 截图.png