Objective-C 是以 C 语言为基础的,
PC 上,在 C 语言中对空指针进行操作,
程序会由于越界访问而出现保护错进而崩溃。
原因需要从源代码中寻找,
下面是 objc_msgSend 的 arm 版汇编代码片段:
在 arm 的函数调用过程中,
一般用 r0-r4 传递参数,
用 r0 传递返回值。
对应 objc_msgSend,第一个参数为 self,返回值也是 self,都放在 r0(a1)中。
/********************************************************************
* idobjc_msgSend(id self, SEL op, ...)
* On entry: a1 is the message receiver,
* a2 is the selector
********************************************************************/
ENTRY objc_msgSend
# check whether receiver is nil
teq a1, #0 //语句一:判断self是不是空
moveq a2, #0 //如果语句一判断self是空,那么也要把SEL置空,否则不执行这句
bxeq lr //如果语句一判断self是空就返回到调用 objc_msgSend 的地方继续执行
teq 指令说明:
TEQ Rn, Operand2
The TEQ instruction performs a bitwise(逐位,按位) Exclusive OR operation on the value in Rn and the value of Operand2.
逐位和0异或,判断是不是0
测试 self 是否为空。
moveq 指令说明:
如果self为空,则将 selector 也设置为空。
bx 指令说明:
在 arm 中 bx lr 用来返回到调用子程序的地方(即:返回到调用者),此处是:如果 self 为空,就返回到调用 objc_msgSend 的地方继续执行。
总之:
如果传递给 objc_msgSend 的 self 参数是 nil,该函数不会执行有意义的操作,直接返回。
ARM汇编指令
1.条件执行后缀:
在ARM汇编指令后面加条件执行后缀,用来决定这条语句是否会被执行
mov r0, r1: 相当于C语言中的r0 = r1;
moveq r0, r1:
如果eq后缀成立,则直接执行mov r0, r1;
如果eq不成立则本句代码直接作废,相当于没有。
类似于C语言中 if (eq) {r0 = r1;}
条件后缀执行注意点:
1)、条件后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果。
2)、条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。
2.比较指令 CMP
• CMP指令:比较两个操作数,并把结果存入CPSR供下一句语句使用
CMP R0,R1; 比较R0,R1;等价于 sub r2, r0, r1 (r2 = r0 - r1)
CMN R0,R1; 等价于 add r0, r1
tst r0, #0xf; 测试r0的bit0~bit3是否全为0
teq: TEQ是对2个数,进行EOR(异或)
注意:比较指令用来比较2个寄存器中的数,比较指令不用后加s后缀就可以影响cpsr中的标志位。
3.跳转语句 B/BL/BX
在 ARM 程序中有两种方法可以实现程序流程的跳转:
• 使用专门的跳转指令 B
• 直接向程序计数器PC写入跳转地址值,这几乎是任何一种CPU必备的,PC表示CPU当 前执行语句位置,改变PC的值,相当于实现程序跳转,类似C语言的Return 语句。用 MOV PC,LR,这里可以在任意4G的空间进行跳转。
• 与MOV PC,XXX能在4G空间跳转不同,B语句只能32M空间跳转(因为偏移量是一个 有符号26bit的数值=32M)
B指令(Branch)表示无条件跳转。例:B main ; 跳转到标号为main的代码处
BL指令(Branch with Link)表示带返回值的跳转,跳转前把返回地址放入lr中,以便返回。例:BL delay ; 执行子函数或代码段 delay,delay可以为C函数。
BL比B多做一步,在跳转前,BL会把当前位置保存在R14(即LR寄存器),当跳转代码结束后,用MOV PC,LR指令跳回来,这实际上就是C语言执行函数的用法。汇编里调子程序都用BL执行完子函数后,用MOV PC,LR跳回来。