iOS汇编教程:ARM(2)
感谢唐巧抽出时间对本文进行double-check。
本文是iOS汇编教程:ARM第二篇。
iOS汇编教程:ARM目录如下[共分为两篇]:
iOS汇编教程:ARM(1)
- 开始:什么是汇编
- 函数调用约定
- 创建工程
- 加法(addFunction)
iOS汇编教程:ARM(2)
- 函数的调用
- Objective -C 汇编
- Obj-C 消息发给了谁
- 你现在可以进行逆向工程了
- 何去何从
——————————————————————–
iOS汇编教程:ARM(2)
函数的调用
首先,给函数addFunction函数添加一个属性(attribute) ,告诉编译器不要进行特定的优化处理。通过上一篇文章,你已经看到编译器可以对代码进行优化,移除掉不需要的指令,另外,编译器甚至可以移除掉函数的调用,直接把被调用函数的相关代码进行内嵌到调用函数中。
例如,编译器可能会在调用函数中适当的添加add指令,而不是调用addFunction本身。实际上,现如今的编译器已经非常的智能了,针对类似addFunction这样的函数,编译器本身就可以进行加法操作,而不用在代码中添加一条add指令。
本文中,我们不希望编译器对代码进行优化——把代码进行内嵌处理。现在回到工程的main.m文件中,并按照如下方式修改addFunction:
__attribute__((noinline)) int addFunction(int a, int b) {int c = a + b;return c; }
紧接着,在该函数下面添加另外一个函数:
void fooFunction() {int add = addFunction(12, 44);printf("add = %i", add); }
如上代码所示,fooFunction通过调用addFunction来计算12+44,然后将结果打印出来。这里使用C函数printf进行打印,而没有使用Objective-C的NSLog(NSLog要稍微复杂一点)。
接着再次选择Xcode中的Product\Generate Output\Assembly File,并确保输出设置为Archiving。然后搜索_fooFunction,会看到如下一些内容:
提醒:在Scheme中一定要选择iOS Device,不要选择模拟器。
_fooFunction: @ 1:push {r7, lr} @ 2:movs r0, #12movs r1, #34 @ 3:mov r7, sp @ 4:bl _addFunction @ 5:mov r1, r0 @ 6:movw r0, :lower16:(L_.str-(LPC1_0+4))movt r0, :upper16:(L_.str-(LPC1_0+4)) LPC1_0:add r0, pc @ 7:blx _printf @ 8:pop {r7, pc}
上面的代码中,涉及到了一些还没有介绍过的指令,不用担心,它们都不复杂。下面我们就分别来看看上面代码中的指令都做了什么操作:
1、这里的指令作用跟之前介绍的add sp, #12类似——r7和lr被“pushed”到栈中,也就是说栈指针(sp)被减去8(因为r7和lr都是4个字节)。需要注意,通过这条指令,栈指针被递减,两个值也被存储到栈中!需要存储r7是因为在这个函数中,该寄存器会被覆盖,而之后又需要还原最初的值;而存储lr寄存器中的值是因为在函数结束时,要使用。
注意:lr是寄存器(Link Register, LR——R14寄存器)。
2、这两个指令属于move(mov)指令集中的一个。有时候你会看到movs,而有时候则会看到mov,或者其它类似的名称。它们都是把一个值装载到寄存器中。你可以把数据从一个寄存器“mov”到另外一个寄存器,例如mov ro, r1指令,将把r1中的数据装载到r0中,而r1中的数据不会改变。
上面的两行汇编指令中,会将定义在函数中的两个常量装载到r0和r1中。注意,需要将这两个常量装载到r0和r1中,才能够被addFunction正确的使用。
3、 在调用函数的时候,应该将栈指针保存起来,而这里使用r7来保存栈指针(r7是可以用来存储局部变量存储器中的一个)。可能你已经注意到,在该函数中剩下的代码里面并没有再次使用到栈指针或者r7,所以这条指令在这里是多余的——有时候,即使开启了编译器的优化,但还是不能做到最佳优化。
4、这条指令(bl)对函数进行调用。请记住被调用函数需要的参数已经存储到相关的寄存器中了(r0和r1)。这条指令的执行一般被当做一个分支(branch)。可以理解为执行带链接的分支,也就是说,在跳转到分支之前,会将lr(link register)的值设置为当前函数中将要执行的下一条指令,当从分支(被调函数)中返回时,通过lr中的值可以知道当前函数执行到哪里了。
5、当addFunction函数执行完毕,返回后,执行的第一条指令——将addFunction的返回值(存储在r0中)保存起来,以供后续的printf使用。也就是利用mov将r0中的值存储到r1中。
6、printf的的第一个参数是一个字符串。这里使用了3条指令将指向字符串首地址的指针装载到r0寄存器中。这个字符串存储在二进制文件的数据段“data segment”中,不过该字符串的准确位置在二进制文件被链接之前是不知道的。
字符串其实是在由main.m文件生成的目标文件(object file)中的数据段里。如果你在汇编代码中搜索L_.str,就能找到这个字符串。这三个指令中的前两个作用是装载这个常量的地址(减去本地标签加4后的地址)。
第三条指令中将程序计数器(pc)的值加到r0中。因此,现在r0已保存着字符串的地址,也不用考虑L_.str在二进制文件中的确切位置。
下面的这个图演示了内存的布局。其中L_.str – (LPC1_0 + 4)的改变并不用对r0进行改动。
7、这条指令(blx)调用printf函数。这跟bl指令有明显的区别。blx中的x标示交换“exchange”,意思是如果有必要,处理器将对指令集模式进行切换。
现在的ARM处理器有两种模式:ARM和Thumb。Thumb指令是16位的宽度,而ARM指令是32位的宽度。Thumb指令比较少,不过使用Thumb指令意味着代码容量更小,以及更利于CPU缓存。
因此,使用Thumb尺寸得到的好处就是让你的代码更少。这里可以看到更多的Thumb信息:Wikipedia。
- mov r0, r1 => r0 = r1
- mov r0, #10 => r0 = 10
- ldr r0, [sp] => r0 = *sp
- str r0, [sp] => *sp = r0
- add r0, r1, r2 => r0 = r1 + r2
- add r0, r1 => r0 = r0 + r1
- push {r0, r1, r2} => 将 r0, r1 和 r2push到栈中.
- pop {r0, r1, r2} => 将3个值从栈中pop出来,并存放到r0, r1 和 r2中.
- b _label => pc = _label
- bl _label => lr = pc + 4; pc = _label
- (int)addValue:(int)a toValue:(int)b {int c = a + b;return c; }
"-[ViewController addValue:toValue:]":adds r0, r3, r2bx lr
首先看到的是一个标签(label)名称——”–[ViewController addValue:toValue:]“,这个名称包含类名和完整的Objective-C方法名称。
int ViewController_addValue_toValue(id self, SEL _cmd, int a, int b) {int c = a + b;return c; }
这就是为什么a和b两个参数分别存储到r2和r3的原因。可能你之前已经听说过前两个参数了(经常使用self吧)。
为了观察Objective-C方法是如何被调用的,现在将如下方法添加到ViewController中:
- (void)foo {int add = [self addValue:12 toValue:34];NSLog(@"add = %i", add); }
重新生成汇编文件,然后寻找“–[ViewController foo]“:,应该能看到类似如下的代码:
"-[ViewController foo]": @ 1:push {r7, lr} @ 2:movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4)) LPC1_0:add r1, pc @ 3:ldr r1, [r1] @ 4:movs r2, #12movs r3, #34 @ 5:mov r7, sp @ 6:blx _objc_msgSend @ 7:mov r1, r0 @ 8:movw r0, :lower16:(L__unnamed_cfstring_-(LPC1_1+4))movt r0, :upper16:(L__unnamed_cfstring_-(LPC1_1+4)) LPC1_1:add r0, pc @ 9:blx _NSLog @ 10:pop {r7, pc}
同样,这与之前C语言产生的汇编代码非常相似,我们也来看看具体都做了些什么:
3、如果在汇编文件中搜索L_OBJC_SELECTOR_REFERENCES_,会看到如下内容:
L_OBJC_SELECTOR_REFERENCES_: .long L_OBJC_METH_VAR_NAME_
7、调用addValue:toValue:的返回值被存放在r0中。这里的指令将这个结果值保持到r1中。在接下来调用NSLog函数时会用到这个值。
8、将NSLog用到的第一个字符串参数装载到r0中。这跟之前介绍的用C函数里面调用printf一样。
9、这是一个分支(branch),以带链接跳转和根据情况切换指令集的模式来调用NSLog方法。
10、从栈中pop出两个值,并放入r7和pc寄存器中。这跟之前一样,从foo方法中返回。
Obj-C 消息发给了谁
id objc_msgSend(id self, SEL _cmd, ...)
在方法执行期间,第一个参数是self。在方法中写的一些代码,例如self.someProperty,其中self就是来自自objc_msgSend方法中的self参数。
- (void)foo {int add = (int)objc_msgSend(self, NSSelectorFromString(@"addValue:toValue:", 12, 34);NSLog(@"add = %i", add); }
当一个Objective-C方法被编译的时候,上面用C写的等效方法签名应该是这样的:
int ViewController_addValue_toValue(id self, SEL _cmd, int a, int b)
对此为什么会这样,现在应该不会感觉到奇怪——这样的签名是为了与objc_msgSend相匹配!也就是说当objc_msgSend在查找并跳转到对应方法时,所有的这些参数都应该在正确的地方。
这里可以看到更多关于objc_msgSend相关内容:文章1,文章2。
你现在可以进行逆向工程了
根据上面对ARM汇编的介绍,你应该可以能够知道为什么有些代码被breaking、crashing或者没有正确的执行。
通过观察相关的汇编代码,可以更加清楚的获知到引起bug的详细步骤。
我建议使用HopperApp对这些库进行分析。该软件能够对二进制文件进行反汇编——这样你就可以看库中的内容了——这样做是没有问题的!!!例如,打开UIKit,就可以看到每个方法都做了什么。如下图所示:
最后,函数调用之后,r0没有改动过,所以被调用函数的返回值就是调用函数的返回值。
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation {return [self _doesTopViewControllerSupportInterfaceOrientation:interfaceOrientation]; }
cool!很容易吧!虽然大多数方法都比上面的这个要复杂,不过你可以根据汇编指令拼凑出一些代码,进而快速的确定这些代码做了些什么。
这篇关于iOS汇编的教程向你介绍了一些运行在iOS设备中的ARM汇编指令核心概念。你应该学习到了C和Objective-C相关的一些调用约定。
通过本文介绍的知识,当你的程序在使用系统库crash时,你可以对所有能看到的随机代码进行分析。当然,你也可以通过汇编指令来准确的分析你自己写的方法。
如果你希望更加深入的了解ARM,请看这里:Raspberry Pi。这里的涉及到的小型设备都拥有ARM处理器,跟iOS设备非常相似,同时也有许多教程可以教你如何对这些设备进行编程。
iOS汇编教程:ARM(2)相关推荐
- 汇编为什么分段执行总是执行不了_iOS汇编教程(六)CPU 指令重排与内存屏障...
系列文章 iOS 汇编入门教程(一)ARM64 汇编基础 iOS 汇编入门教程(二)在 Xcode 工程中嵌入汇编代码 iOS 汇编入门教程(三)汇编中的 Section 与数据存取 iOS 汇编教程 ...
- iOS 逆向之ARM汇编
最近对iOS逆向工程很感兴趣. 目前iOS逆向的书籍有: <Hacking and Securing IOS Applications>, <iOS Hacker's Handboo ...
- linux 编译汇编,linux下的汇编教程
linux下的汇编教程 第一部分 Linux下ARM汇编语法尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针.设置页表.操作 ARM的协处理器等.初 ...
- Unity3D for iOS初级教程:Part 2/3
Unity3D for iOS初级教程:Part 2/3 这篇教材是来自教程团队成员 Christine Abernathy, 他是Facebook的开发支持团队的工程师. 欢迎来到Unity3D f ...
- iOS开发教程:Storyboard全解析-第二部分
如果你想了解更多Storyboard的特性,那么你就来对了地方,下面我们就来接着上次的内容详细讲解Storyboard的使用方法. 在上一篇<iOS开发教程:Storyboard全解析-第一部分 ...
- charles抓包ios抓拍教程
charles抓包ios抓拍教程_百度搜索 https://www.jianshu.com/p/724ef9d3efb6 https://www.cnblogs.com/junhuawang/p/72 ...
- 性能比拼!超详细的Tengine GEMM矩阵乘法汇编教程
点击我爱计算机视觉标星,更快获取CVML新技术 Tengine 是OPEN AI LAB 针对前端智能设备开发的软件开发包,核心部分是一个轻量级,模块化,高性能的AI 推断引擎,并支持用DLA.GPU ...
- ios绘图教程(原文http://www.cocoachina.com/industry/20140115/7703.html)
os开发者平台 Cocos引擎中文官网 H5小游戏编辑器 退出chengtanze 首页 资讯 问答 论坛 Cocos2d-x 开发者中心 新手入门 专题 新闻日历 开发者通道 排行榜 代码库 图书库 ...
- ARM官方汇编与ARM GNU汇编中的伪操作
以下内容源于网络资源的学习与整理,如有侵权请告知删除. 参考博客 (1)嵌入式Linux ARM汇编 (2)GNU ARM 汇编基础 - wanli1024 - 博客园 (3)GNU ARM 汇编简介 ...
最新文章
- Python Scrapy
- 在Fabric ChainCode中导入第三方包(以状态机为例)
- oracle eco 开放接口,问题:关于ECO,ECN的API或者INTERFACE
- 如何用Jmeter做压力测试
- 将maven项目托管到github
- MySQL(一)SQL执行流程与MySQL架构
- android 中 四舍五入的method */
- linux nginx线程池,nginx使用线程池提升9倍性能
- 绿得发娇的企业即时通讯软件
- java nurbs几何库_NURBS曲线与曲面
- ASP.NET大闲话:ashx文件有啥用
- mysql econnreset_javascript - 节点Js mysql(和mysql2)ECONNRESET - 堆栈内存溢出
- 昆明北大附中2021高考成绩查询,北大附中云南实验学校2021年招生代码
- nginx配置虚拟主机-端口号区分/域名区分
- 由我国科学家研制的计算机,由我国科学家研制的系列超级计算机综合技术处于国际领先水平,2015年11月,全球超级计算机...
- 第三方支付牌照(支付业务许可证)
- 学习笔记:Self-Paced Learning
- SpringCloud调用接口流程
- 日本僧人问道弘法寺当家师
- crm客户管理系统的功能有哪些?
热门文章
- 愿2014年02月14日,所有情人情人节快乐!
- uniapp之常用提示弹框
- python恢复默认设置_pycharm重置设置,恢复默认设置的方法
- AS Gradle 8.0 配置 + Realm 使用
- c语言中strcat作用,c语言中strcat的用法
- FDTD script command(源/监视器)
- 9月第1周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!
- 我们没得拼爹,只能拼命,但拿什么来拼命?
- java内部错误2203_Win7系统安装Java出现内部错误2203怎么办
- centos7 gateone安装