- 在Xcode中通过写汇编代码来调试汇编指令,具体步骤如下所示:
- 首先在xcode工程中创建一个
arm.s文件
;
- 首先在xcode工程中创建一个
- 其次需创建一个头文件,公开.s文件中的函数供外界调用;
-
汇编代码文件是以.s结尾的;
-
.text
表示函数实现在代码段; -
.global _test
表示开启外界调用test函数的权限,如果不加会报错找不到_test函数; -
在arm.h文件中公开声明的是test函数,但在arm.s汇编文件中实现的是_test函数,这是由汇编底层机制决定的,汇编底层最终去获取的是_test;
-
汇编代码调用如下所示:
Snip20210224_63.png -
当断点停在第20行,点击左下方的向下箭头,进入汇编代码内部;
-
现在我们可以通过LLDB命令来做汇编调试了,在汇编调试之前先介绍几个LLDB命令
-
si(step int)
:汇编指令单步执行; -
c(continue)
:跳过函数;
指令
ret指令
- 格式:ret
- 含义:表示函数返回;
- 本质:将lr寄存器中的值赋值给pc寄存器,ret返回时会执行pc中的值所代表的函数;
mov指令
- 格式: mov 目标寄存器 , 源操作数;
- 含义:将源操作数移动(写入)到目标寄存器中;
- 汇编代码如下:
.text
.global _test
_test:
mov x0,#0x8
ret
- LLDB调试结果:
- 可以看到x0寄存器确实通过mov指令,存储了0x0000000000000008这个值;
- 井号0x8 我们称之为立即数,立即数的写法前面加上井号;
- 汇编代码修改后如下:
.text
.global _test
_test:
mov x0,#0x8
mov x1,x0
ret
- LLDB调试结果:
-
mov x0,#0x8
指令执行之后 x0 = 0x0000000000000008; - si 汇编代码单步执行,则会执行
mov x1,x0
指令,将x0中的值,赋值给x1; - x1中的值也等于 0x0000000000000008;
add指令
- 格式:add 目标寄存器, 操作数1,操作数2;
- 含义:将操作数1与操作数2相加,然后赋值给目标寄存器;
- 汇编代码如下所示:
.text
.global _add
_add:
mov x0,#0x1
mov x1,#0x2
add x3,x0,x1
ret
- 调用代码为:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
add();
return YES;
}
- LLDB调试结果:
- register read x0 读取x0寄存器中的值为0x0000000000000001;
- si 单步执行
mov x1,#0x2
指令 即寄存器x1赋值为0x0000000000000002; - si 单步执行
add x3,x0,x1
指令 即x3=x0+x1,最终x3中的值为0x0000000000000003; - 将汇编代码修改如下所示:
.text
.global _add
_add:
add x0,x0,x1
ret
- 调用代码为:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int sum = add(3,5);
NSLog(@" add = %d",sum);
return YES;
}
- LLDB调试结果如下:
- 当断点停在第2行时,读取x0与x1寄存器中的值分别为3和5,就是外界传进来的参数值,
说明add函数的参数被x0与x1两个寄存器接收了
,验证了iOS ARM64汇编02-- 寄存器 中的阐述; - si 单步执行完
add x0,x0,x1
指令,此时x0寄存器中的值为8; - 放开断点,我们看到控制台中的打印结果为 add = 8; 说明add函数的汇编实现将x0寄存器中的值默认为函数的返回值,返回给外界了,验证了iOS ARM64汇编02-- 寄存器 中的关于x0寄存器的阐述;
sub指令
- 格式:sub 目标寄存器, 操作数1,操作数2;
- 含义:将操作数1与操作数2相减然后赋值给目标寄存器;
- 汇编代码如下:
.text
.global _sub
_sub:
sub x0,x0,x1
ret
- 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int num = sub(5,2);
NSLog(@" num = %d",num);
return YES;
}
- LLDB调试结果如下:
- 当断点停在第2行时,读取x0与x1寄存器中的值分别为5和2,就是外界传进来的参数值,
说明sub函数的参数被x0与x1两个寄存器接收了
,验证了iOS ARM64汇编02-- 寄存器 中的阐述; - si 单步执行完
sub x0,x0,x1
指令,此时x0寄存器中的值为3; - 放开断点,我们看到控制台中的打印结果为 num = 3,说明sub函数的汇编实现将x0寄存器中的值默认为函数的返回值,返回给外界了,验证了iOS ARM64汇编02-- 寄存器 中的关于x0寄存器的阐述;
cmp指令
- 格式:cmp 操作数1,操作数2;
- 含义:将操作数1与操作数2进行比较,就是相减,同时更新cpsr寄存器中条件标志位的值;
- cmp是compare的缩写,顾名思义就是比较的意思即比较指令;
- 汇编代码如下所示:
.text
.global _test
_test:
mov x0,#0x3
mov x1,#0x5
cmp x0,x1
ret
- 外界调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
test();
return YES;
}
- LLDB调试结果如下:
- 在执行
cmp x0,x1
指令之前 通过register read
命令可以看到cpsr = 0x60000000
; - 在执行完
cmp x0,x1
指令之后,cpsr = 0x80000000`; -
cmp x0,x1
x0-x1 = -2其结果为负数;说明cpsr的N位(负数位)等于1;
- 从这里可以看出
cmp指令
的执行结果会影响cpsr中的条件标志位N位上的值;
b指令
格式:b 目标地址;
含义:B指令是最简单的跳转指令,一旦遇到B指令,程序会无条件跳转到B之后所指定的目标地址处执行;
- 汇编代码如下所示:
.text
.global _test
_test:
b mycode
mov x0,#0x5
mycode:
mov x0,#0x8
ret
- 外界调用代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
test();
return YES;
}
- LLDB调试结果如下:
- 可以看到执行
b mycode
指令后直接跳转到mycode,然后执行mov x0,#0x8
指令;而mov x0,#0x5
指令没有执行; - 上面介绍的是最简单的b指令,b指令还可以携带条件的,携带条件就会涉及到条件域的概念,下面介绍来条件域相关知识:
- 介绍几种最常见的条件域如下所示:
-
EQ
: equal 相等; -
NE
:not equal 不相等; -
GT
:great than 大于; -
LT
:less than 小于; -
GE
:great or equal 大于等于; -
LE
:less equal 小于等于;
-
- 汇编代码如下所示:
.text
.global _test
_test:
mov x0,#0x3
mov x1,#0x5
cmp x0,x1
beq mycode
mov x0,#0x8
ret //必须加上ret 否则会继续执行下面mycode的相关指令
mycode:
mov x0,#0x9
ret
-
cmp x0,x1
指令,比较x0与x1,将两者相减,其结果会影响cpsr寄存器中条件标志位; -
beq mycode
指令,会根据cpsr寄存器中条件标志位,判断是否满足条件,然后决定执行下一步的汇编指令; - 由上面的cmp指令与带条件的b指令两者之间的配合使用,很像C代码中的if-else逻辑结构,现在我们写一段简单的关于if-else的C代码,看看底层的汇编是怎么实现的?- OC代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int a = 5;
int b = 8;
if (a == b) {
printf("a = b");
}else{
NSLog(@" a != b");
}
return YES;
}
- 汇编代码分析如下:
- 图中的w8与w9,分别存储的就是a和b的值;
- if-else的汇编实现就是结合cmp指令与带条件的b指令;
bl指令
- 格式:bl 目标地址;
- 含义:BL指令是另一个跳转指令,是带返回的跳转指令,所以在跳转之前,它会先将当前标记位的下一条指令存储在寄存器lr(x30)中,然后跳转到标记处开始执行代码,当遇到ret时,会将lr(x30)中存储的地址重新加载到PC寄存器中,使得程序能返回标记位的下一条指令继续执行;
- 汇编代码如下:
.text
.global _test
//mycode相当于一个内部函数
mycode:
mov x0,#0x1
mov x1,#0x2
add x2,x0,x1
ret
_test:
mov x6,#0x6
bl mycode
mov x4,#0x4
mov x5,#0x5
ret
- 汇编代码分析如下:
- 在执行
bl mycode
指令之前:
- 在执行
bl mycode
指令时:
- 可以看到
bl mycode
指令的下一个指令地址是 0x10288d9d4 - 当执行
bl mycode
指令时,首先链接寄存器lr会将当前标记位的下一条指令存储起来,看到lr中的存储值发生了变化,变成了 0x00000001026899d4, 然后跳转到mycode执行; - 当mycode执行到ret指令时,再去调用lr寄存器中存储地址,回到0x10288d9d4;
- bl指令 相当于 函数调用;
ldr指令 -- 加载内存 load
- 格式:ldr 目标寄存器 , [寄存器]
- 含义:根据右侧寄存器 --> 内存地址 --> 加载内容数据 按目标寄存器的位数加载写入目标寄存器,即从内存地址中读取数据,存到目标寄存器中,至于怎么读取数据,读多少字节数据与目标寄存器有关;
- 汇编代码如下:
.text
.global _test
_test:
ldr x0,[x1]
ret
- 外界调用代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int a = 8;
test();
return YES;
}
- LLDB调试如下:
-
p &a
获取变量a的地址值 0x000000016d88712c; -
register write x1 0x000000016d88712c
将a的地址值写入x1寄存器; - 执行
ldr x0,[x1]
指令 即将x1寄存器中的内存地址为首地址读取内存8个字节的内存数据,存入x0寄存器中去; -
register read x0
所以 x0中存储值为0x0000000000000008; - 汇编代码修改如下:
.text
.global _test
_test:
ldr w0,[x1]
ret
- 唯一差别的地方是:将x1寄存器中的内存地址为首地址读取内存4个字节的内存数据,存入w0寄存器中去,因为w0寄存器是32位-存储4个字节的数据;
- 关于ldr 加载内存指令的相关写法即含义:
LDR x0, [x1] ;将存储器地址为x1的字数据读入寄存器x0
LDR x0, [x1, x2] ;将存储器地址为x1+x2的字数据读入寄存器x0
LDR x0, [x1, #8] ;将存储器地址为x1+8的字数据读入寄存器x0
LDR x0, [x1, x2]! ;将存储器地址为x1+x2的字数据读入寄存器x0,并将新地址x1+x2写入x1
LDR x0, [x1, #8]! ;将存储器地址为x1+8的字数据读入寄存器x0,并将新地址x1+8写入x1
LDR x0, [x1], x2 ;将存储器地址为x1的字数据读入寄存器x0,并将新地址x1+x2写入x1
LDR x0, [x1, x2, LSL#2]! ;将存储地址为x1+x2*4的字数据写入寄存器x0,并将新地址x1+x2*4写入x1
LDR x0. [x1], x2, LSL#2 ;将存储地址为x1的字数据写入寄存器x0,并将新地址x1+x2*4写入x1
ldur指令 -- 加载内存 load
- ldur指令与ldr指令用法完全相同,唯一的区别在于
指令LDR x0, [x1, #立即数]
,当ldr指令时,立即数是正数,当ldur指令时,立即数是负数; ldr x0, [x1, #0x8]
ldur x0, [x1, #-0x8]
ldp指令 加载内存 load
- 格式:ldp 寄存器1,寄存器2,[寄存器3];
- 含义:读取寄存器3 --> 内存地址 --> 内存数据,按字节顺序放入寄存器1和寄存器2中;
- ldp中p是pair的缩写,pair -- 对,一对;
- 汇编代码如下:
.text
.global _test
_test:
ldp w0,w1,[x2,#0x5]
ret
- 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int a = 8;
test();
return YES;
}
- LLDB调试结果如下:
-
p &a
打印变量a的内存地址; -
register write x2 0x000000016f60f12c
将变量a的内存地址写入x2寄存器; - si 单步执行
ldp w0,w1,[x2]
-
x 0x000000016f60f12c
是查看0x000000016f60f12c内存地址中数据内容; - 最后看到w0中放入了0x000000016f60f12c内存地址中前4个字节数据,w1中放入了0x000000016f60f12c内存地址中后4个字节数据;
str指令 写入内存 store
- 格式:str 寄存器1 , [寄存器2];
- 含义:将寄存器1中存储值,写入寄存器2-->内存地址 中去;
- 汇编代码如下:
.text
.global _test
_test:
str x0,[x1]
ret
- 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int a = 8;
test();
return YES;
}
- LLDB调试如下:
-
p &a
打印变量a的内存地址; -
register write x0 3
往寄存器x0中写入值3; -
register write x1 0x000000016ee0312c
往寄存器x1中写入变量a的内存地址0x000000016ee0312c; - si 单步执行
str x0,[x1]
,即将x0=3,写入x1= 0x000000016ee0312c的内存地址中,最终看到 a = 3;
stur指令写入内存 store
- stur指令与str指令用法完全相同,唯一的区别在于
指令str x0, [x1, #立即数]
,当str指令时,立即数是正数,当stur指令时,立即数是负数;
str x0, [x1, #0x8]
stur x0, [x1, #-0x8]
stp指令 写入内存 store
- 格式:stp 寄存器1,寄存器2,[寄存器3];
- 含义:将寄存器1与寄存器2中的数据 分别写入寄存器3-->内存地址 中去;
网友评论