Thumb 汇编指令集,Thumb 指令编码方式,编译 Thumb 汇编代码
版权归作者所有,如有转发,请注明文章出处: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
找到arm指令参考手册并下载pdf到本地:https://developer.arm.com/documentation/ddi0406/latest/
在文档书签目录可以看到有16位和32位 Thumb 指令集文档,也可以找 thumb 指令的编码方式说明
Thumb 指令的编码方式
以下面的 16 位 Thumb 指令为例
MOVS R0, #1 // 将立即数 1 加载到 R0,指令长度为 16 位
在 16 位 Thumb 指令集中,MOVS Rd, #imm8 指令的编码格式如下
位 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
含义 | 0 | 0 | 1 | 0 | 0 | Rd(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 转为为小端模式后为 0x0120
在 ARM 架构(包括 Thumb 指令集)中,机器码在存储时通常使用 小端模式。小端模式下,低字节先存储,所以存储在内存中的顺序会变为 01 20。
打开 https://armconverter.com/?disasm,验证一下是否正确
编译 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
输出如下
在 gdb gef 调试中识别 thumb 指令
在 arm 汇编指令中,bx lr 中 bx 指跳转到 lr 地址中的 thumb 指令,blx 是调用 thumb 指令方法,其中的 x 就是代表 thumb 指令,没有 x 就是 arm 指令。
在 gdb gef 调试中,当走到 bx 时,cpsr 中 THUMB 高亮显示,表示当前指令已经切换到 thumb 模式
IDA Pro 中切换 Thumb 模式
跳转到对应地址后 C 一下转换为代码
点击 Edit -> segments -> change segment register value(Alt+G)
修改T的值。0x0为ARM指令,0x1为Thumb指令。