第七章 ARM 反汇编基础(六)(Thumb 汇编指令集)指令的内联函数ThumbExpandImm_C() 计算 i、imm3、imm8 三个域组成的 12 位的值计算解释,有机器码反汇编实例

 

第七章 ARM 反汇编基础(六)(Thumb 汇编指令集)

zlmm741 2020-03-30 21:57:49  749  收藏 3
分类专栏: 《Android 软件安全权威指南》学习笔记 文章标签: android ubuntu 安全
版权
文章目录
Thumb 汇编指令集
16 位 Thumb 指令编码
16 位 Thumb 指令格式解析
32 位 Thumb 指令编码
32 位 Thumb 指令格式解析
Thumb 汇编指令集
作为 ARM 指令集的一个子集,针对代码密度问题提出,具有 16 位的指令宽度
与 ARM 指令的 32 位宽度相比,Thumb 指令集在保留 32 位宽度优势的同时大大节省了系统的存储空间
Thumb 不是一个完整的体系结构,包含的指令集十分有限,常与 ARM 指令搭配使用
第一代 Thumb 指令集更新到 ARMv6T2 后,引入了第二代 Thumb 指令集,使用与 ARM 指令相同的 32 位指令,不仅性能与 32 位 ARM 指令相当,还保留了第一代指令的简洁特性
Thumb 指令和 ARM 指令除了长度不同,寄存器的使用也不同。Thumb 指令集中,R11 ~ R15 寄存器使用 FP、IP、SP、LR、PC 等命名,新的命名方式直接在名称上体现了寄存器的用途,使用上也有限制和差异(相较 ARM 指令)
第一代 Thumb 指令的宽度只有 16 位,在地址读取范围上比 ARM 指令小很多,在进行一些大范围的指令跳转操作时只能切换到 ARM 模式,执行 ARM 指令
16 位 Thumb 指令编码
16 位的 Thumb 指令称第一代 Thumb 指令
格式:

可看到,第一代 Thumb 指令设计十分简洁,只用了一个 bits[15:10] 的 Opcode 域来确定指令及其分类
第一代 Thumb 指令的所有指令及分类方法(Opcode 域中的 x 表示取值可为 0 或 1):
Opcode    指令或指令类别
00xxxx    移位、加、减、移动与比较指令
010000    数据处理指令
010001    特殊数据指令,分支与交换指令
01001x    常量池加载指令,LDR 指令
0101xx    加载/存储单个数据指令
011xxx    加载/存储单个数据指令
100xxx    加载/存储单个数据指令
10100x    相对于 PC 寄存器的加法指令,ADR 指令
10101x    相对于 SP 寄存器的加法指令,ADD 指令
1011xx    杂项指令
11000x    多寄存器存储指令,STM、STMIA、STMEA 指令
11001x    多寄存器加载指令,LDM、LDMIA、LDMFD 指令
1101xx    条件分支与中断指令
11100x    无条件分支指令,B 指令
在用 Android NDK 编译代码时,若想默认生成 Thumb 指令而非 ARM 指令,可在编译代码时指定 -mthumb 参数:
$CC app.c -fPIE -S -mthumb -march=armv5te
在生成的汇编代码中,子程序会的开头会自动添加 .thumb_func 伪指令
16 位 Thumb 指令格式解析
以十六进制值 0x4611 为例,分析其指令编码,找出其对应的 Thumb 指令
用计算器查看其二进制编码:

bits[15:10] 的 Opcode 域的值为 0b010001,对应指令分类中的特殊数据指令、分支与交换指令系列,具体的指令格式:

bits[9:6] 的 Opcode 域的值为 0b1000,对应 MOV 低位寄存器指令,格式:


MOV<c> <Rd>, <Rm>
对应的指令的位域分布:

指令伪代码描述:
d = UInt(D:Rd);
m = UInt(Rm);
setflags = FALSE;
if d == 15 && InitBlock() && !LastInitBlock()
    then UNPREDICTABLE;
1
2
3
4
5
d 等于 15 被定义为不可预知行为,所以,bits[7:3] 不能为 0b10101。Rm 寄存器为源寄存器,取值为 0b0010(表示 R2 寄存器)。Rd 为目标寄存器,取值为 0b001(表示 R1 寄存器)
综上,这条指令的完整格式为 MOV R1, R2
验证(此处为大端序):

32 位 Thumb 指令编码
32 位的 Thumb 指令又称 Thumb-2 指令
Thumb 指令分为 16 位和 32 位两个版本,如何区分?若一段汇编代码中包含 Thumb、Thumb-2、ARM 三种类型的指令,处理器在执行代码时要用什么方法正确区分它们?Thumb 与 ARM 间的切换采用 CPSR 的 T 标志位,在执行 BX、BLX 指令时,处理器模式的切换依赖于执行地址的最低位(bit[0])。Thumb 与 Thumb-2 间的指令识别依赖于指令编码格式的设计准则
ARM 规定:一条 Thumb 指令的 bits[15:11](高五位)有如下取值时,表示这是一条 Thumb-2 指令:
0b11101:高三位全为 1,当第四位为 0 时,第五位必须为 1(因为 0b11100 在 16 位 Thumb 指令中被无条件分支指令占用)
0b11110:高四位全为 1
0b11111:高四位全为 1
32 位的 Thumb 指令用两个 16 位表示:

第一个 16 位的高三位永远是 1,然后依次是第一个 16 位的 bits[12:11] 的 op1 域、bits[10:4] 的 op2 域,及第二个 16 位的 bit[15] 的 op 域,它们组合后的不同值表示不同的 32 位 Thumb 指令。完整的指令类别定义:
op1    op2         op    指令类别
01    00xx0xx        多寄存器加载与存储指令
01    00xx1xx        寄存器加载与存储指令
01    01xxxxx        数据处理(寄存器移位)指令
01    1xxxxxx        协处理器指令
10    x0xxxxx    0    数据处理(立即数修改)指令
10    x1xxxxx    0    数据处理(二进制立即数)指令
10        1    分支与杂项指令
11    000xxx0        存储单数据指令
11    001xxx0        高级 SIMD 与结构化加载存储指令
11    00xx001        字节加载指令
11    00xx011        半字加载指令
11    00xx101        字加载指令
11    00xx111        未定义
11    010xxxx        数据处理(寄存器)指令
11    0110xxx        乘法、乘积与绝对差指令
11    0111xxx        长型乘法、长型乘积与除法指令
11    1xxxxxx        协处理器指令
可看出,32 位的 Thumb 指令比 16 位的丰富很多。在用 armeabiv7-a 及以上版本的指令集时,要想编译生成 32 位的 Thumb 代码,要指定 -mthumb 参数。在生成的代码中,编译器会根据场景选择用 16 位或 32 位指令,结果通常是在一段 Thumb 指令的汇编代码中同时包含 16 位与 32 位的 Thumb 指令
执行如下命令,可为 app2.c 生成 Thumb-2 汇编指令:

用 IDA Pro 打开生成的 app2,定位到 main() 处,会看到生成的代码中有 MOVT.W、LDRD.W 这种带 .W 后缀的指令,这种指令格式 Thumb-2 特有

32 位 Thumb 指令格式解析
以十六进制数 0xF0814100 为例,分析其指令编码,找到其对应的 Thumb-2 指令(自己添加0xF0814100是机器码,下面例子是转为汇编指令的过程2021.4.2)
用计算器查看其二进制编码:

bits[12:11] 的 op1 域值为 0b10,bits[10:4] 的 op2 域值为 0b0001000,对应的指令类别为数据处理(立即数修改)指令。其指令格式:

第一个 16 位的 bits[8:5] 的 op 域值为 0b0100,第二个 16 位的 bits[11:8] 的 Rd 域值为 0b0001,对应的指令为 EOR(立即数指令)。EOR 指令格式:

EOR<S><c> <Rd>, <Rn>, #<const>
对应的指令二进制位域分布:

可看到,此指令的格式较复杂。Rd 的值为 0b0001,表示 R1 寄存器;Rn 的值为 0b001,表示也是 R1 寄存器。这里重要的是如何计算 const 的值
EOR 的伪代码描述:
if Rd == '1111' && S == '1'
    then SEE TEQ (immediate);
d = UInt(Rd);
n = UInt(Rn);
setflags = (S == '1');
(imm32, carry) = ThumbExpandImm_C(i:imm3:imm8, APSR.C);
if BadReg(n)
    then UNPREDICTABLE;
1
2
3
4
5
6
7
8
imm32 的值就是 const 的值,它的计算由 ThumbExpandImm_C() 完成。传入的参数有两个,一个是由 i、imm3、imm8 三个域组成的 12 位的值,另一个是 APSR 的 C 标志。这里主要看前一个参数的取值。i 的值为 0,imm3 域为第二个 16 位的 bits[14:12],值为 0b100,imm8 域为第二个 16 位的 bits[7:0],值为 0b00000000,合起来就是 0b010000000000
ARM 指令参考手册中,ThumbExpandImm_C() 的伪代码:
// ThumbExpandImm_C()
// ==================

(bits(32), bit) ThumbExpandImm_C(bits(12) imm12, bit carry_in)

if imm12<11:10> == '00' then
    case imm12<9:8> of
        when '00'
            imm32 = ZeroExtend(imm12<7:0>, 32);
        when '01'
            if imm12<7:0> == '00000000'
                then UNPREDICTABLE;
            imm32 = '00000000' : imm12<7:0> : '00000000' : imm12<7:0>;
        when '10'
            if imm12<7:0> == '00000000'
                then UNPREDICTABLE;
            imm32 = imm12<7:0> : '00000000' : imm12<7:0> : '00000000';
        when '11'
            if imm12<7:0> == '00000000'
                then UNPREDICTABLE;
            imm32 = imm12<7:0> : imm12<7:0> : imm12<7:0> : imm12<7:0>;
    carry_out = carry_in;
else
    unrotated_value = ZeroExtend('1':imm12<6:0>, 32);
    (imm32, carry_out) = ROR_C(unrotated_value, UInt(imm12<11:7>));
return (imm32, carry_out);
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
根据 imm12 的高两位判断,其值为 0b01,会执行 else 语句处的指令,ZeroExtend() 将 1 与 imm12 的低七位组合后,扩展到 32 位,即 unrotated_value 的值是一个 32 位的整数且只有第八位为 1,其他位均为 0。接着,ROR_C() 进行循环右移,伪代码:
// ROR_C()
// =======

(bits(N), bit) ROR_C(bits(N) x, integer shift)
    assert shift != 0;
    m = shift MOD N;
    result = LSR(x, m) OR LSL(x, N-m);
    carry_out = result<N-1>;
    return (result, carry_out);
1
2
3
4
5
6
7
8
9
将 unrotated_value 的值循环右移 shift 位。shift 的值为 imm12 的高五位,为 0b01000,即 8。循环右移 8 位后,第八位的 1 到了最高位(第三十一位),其他全部为 0,结果为 0x80000000
综上,0xF0814100 对应的 Thumb-2 指令为 EOR R1, R1, #0x80000000
rasm2 命令不支持对 Thumb-2 指令数据进行汇编,但支持对 Thumb-2 指令反汇编,可执行如下命令验证上述结果:

上述举例是机器码反汇编转为汇编指令

ubuntu是linux系统下的反汇编工具

自己添加汇编指令eor  r1,r1,0x80000000 转机器码81 F0 00 41 ,体会立即数转换过程

之所以传入 0x81F00041 而非 0xF0814100,是因为 Thumb-2 虽是 32 位,但读取和解析仍以 16 位进行,对应的小端字节序的数据与 32 位的情况有所不同
————————————————
版权声明:本文为CSDN博主「zlmm741」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zlmm741/article/details/105209734

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
原作者:宛城布衣。 本文件已移除PDF签名,如内容有误,欢迎大家指正。 目录 前言 i 目录 I ARM7TDMI(-S)指令集及汇编1 ARM 处理器寻址方式2 寄存器寻址2 立即寻址2 寄存器偏移寻址2 寄存器间接寻址3 基址寻址3 多寄存器寻址4 堆栈寻址4 块拷贝寻址5 相对寻址5 指令集介绍7 ARM 指令集7 指令格式7 第 2 个操作数7 #immed_8r 7 Rm8 Rm,shift8 条件码9 ARM 存储器访问指令 11 LDR 和 STR 11 LDM 和 STM14 SWP 17 ARM 数据处理指令19 数据传送指令20 MOV 20 MVN 20 算术逻辑运算指令20 ADD 20 SUB21 RSB 21 ADC 21 SBC 21 RSC 22 AND 22 ORR22 EOR22 IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII BIC 23 第 I 页常用 ARM 指令集及汇编 Ver:1010 比较指令23 CMP 23 CMN23 TST24 TEQ24 乘法指令25 MUL25 MLA25 UMULL25 UMLAL26 SMULL 26 SMLAL 26 ARM 跳转指令27 B27 BL27 BX 27 ARM 协处理器指令28 CDP28 LDC29 STC 29 MCR30 MRC30 ARM 杂项指令31 SWI 31 MRS 32 MSR 33 ARM指令34 ADR 35 ADRL35 LDR36 NOP37 Thumb 指令集39 Thumb 指令集与 ARM 指令集的区别 39 Thumb 存储器访问指令 40 LDR 和 STR 41 PUSH 和 POP 43 LDMIA 和 STMIA 43 Thumb 数据处理指令 45 数据传送指令46 MOV 46 MVN 46 NEG47 算术逻辑运算指令47 ADD 47 IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII 第 II 页常用 ARM 指令集及汇编 Ver:1010 SUB48 ADC 49 SBC 49 MUL50 AND 50 ORR50 EOR51 BIC 51 ASR51 LSL52 LSR 52 ROR53 比较指令53 CMP 53 CMN54 TST54 Thumb 跳转指令 55 B 55 BL55 BX 55 Thumb 杂项指令 56 SWI 56 Thumb指令 57 ADR 57 LDR57 NOP58 伪指令59 符号定义伪指令59 GBLA、GBLL、GBLS 59 LCLA、LCLL、LCLS60 SETA、SETL、SETS 61 RLIST61 CN 62 CP62 DN、SN62 FN63 数据定义伪指令63 LTORG64 MAP64 FIELD 65 SPACE66 DCB 66 DCD 和 DCDU67 DCDO 67 IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII 第 III 页常用 ARM 指令集及汇编 Ver:1010 DCFD 和 DCFDU68 DCFS 和 DCFSU68 DCI69 DCQ 和 DCQU69 DCW 和 DCWU 70 报告伪指令70 ASSERT 70 INFO 71 OPT 71 TTL 和 SUBT 72 汇编控制伪指令73 IF、ELSE 和 ENDIF73 MACRO 和 MEND 74 WHIL 和 WEND 75 杂项伪指令76 ALIGN 77 AREA78 CODE16 和 CODE32 79 END 80 ENTRY80 EQU 81 EXPORT 和 GLOBAL 81 IMPORT 和 EXTERN 82 GET 和 INCLUDE 83 INCBIN83 KEEP83 NOFP 84 REQUIRE 84 PEQUIRE8 和 PRESERVE8 84 RN 84 ROUT85 ARM指令86 ADR 86 ADRL86 LDR86
ARM指令集是一种广泛使用的指令集架构,它主要用于移动设备、嵌入式系统和低功耗服务器等领。这个指令集架构的发展可追溯到1980年代初,但只有1990年代后期开始得到广泛采用和推广。现在,ARM指令集已经成为了全球最流行的指令集之一。 常用的ARM指令集包括ARMv6、ARMv7和ARMv8等。它们的主要区别在于处理器的架构和指令集的扩展。其中,ARMv6是一种比较老的架构,适用于早期的移动设备和嵌入式系统。而ARMv7则是一种更为先进的架构,支持更多的指令集扩展和一些新的特性。ARMv8则是未来的趋势,它支持更多的指令集扩展,包括虚拟寄存器、硬件虚拟化和安全性增强等功能。 除了ARM指令集,汇编语言也是ARM编程的重要组成部分。常用的ARM汇编语言包括ARM汇编Thumb汇编两种。ARM汇编是一种较为底层的汇编语言,它使用32位指令来完成数据处理和控制流操作。而Thumb汇编则是一种更加轻量级的汇编语言,它使用16位指令来完成相同的操作。 针对ARM指令集和汇编语言,有许多文档和教程可供参考。其中最常用的包括ARM体系结构参考手册、ARM汇编编程指南和ARM编程手册等。这些文档都提供了详细的指令集和汇编语言说明,以及实用的编程示例和技巧。同时,还有一些在线资源和社区,如ARM开发者中心和ARM社区等,提供丰富的资讯和交流平台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值