IT评测·应用市场-qidao123.com技术社区

标题: iOS汇编教程(2) [打印本页]

作者: 罪恶克星    时间: 2025-1-21 12:32
标题: iOS汇编教程(2)
你可以在ARM文档里了解更多关于ARM调用约定的信息。苹果也在文档【iOS开发调用约定】内有做过详细形貌。
OK,前戏已足,是时候开始真正的码代码了~
创建工程

在这个汇编教程里,你不会创建一个应用,但照旧得使用Xcode工程来说明此中含义。启动Xcode,再新建工程,选择iOS\应用\单个view的工程( iOS\Application\Single View Application)点击下一步,工程最终如下:


末了点击下一步,选择一个保存你工程的地址。
1 + 1

你要做的第一件事就是写一个两个数相加后并返回的函数。没有比这更简单的事了。
事实上,你可以先写一个C语言函数,因为Objective-C会是我们的复杂度增长。在Supporting Files里打开main.m,并把下面的代码贴到文件内:
int addFunction(int a, int b) {
int c = a + b;
return c;
}
现在保证你选择的Scheme是iOS装备(或者你的装备名),比如,如果你连上了装备就是xx的iPhone。选择Scheme是装备的缘故起因是如许才会用ARM汇编,而选择模拟器的话就会用x86的方式汇编。Scheme选择后的类似于如许:

现在去Product\Generate Output\Assembly File生成汇编码(其实现在Xcode7是Product\Perform Action\Assemble “main.m”)。Xcode会展示一个奇怪的文件。在文件最顶部,你可以看到很多以.section开头的行。如果有那说明你生成成功了!
   注意:你默认运行的Scheme是Debug配置。在Debug模式下,编译器不会开启优化。你想看到无优化版本的汇编,如许你才能真正看清发生了什么。
  文件中搜刮_addFunction。你会发现像以下的代码:
.globl _addFunction .align 2 .code 16 @ @addFunction .thumb_func _addFunction _addFunction: .cfi_startproc Lfunc_begin0: .loc 1 13 0 @ main.m:13:0 @ BB#0: sub sp, #12 str r0, [sp, #8] str r1, [sp, #4] .loc 1 14 18 prologue_end @ main.m:14:18 Ltmp0: ldr r0, [sp, #8] ldr r1, [sp, #4] add r0, r1 str r0, [sp] .loc 1 15 5 @ main.m:15:5 ldr r0, [sp] add sp, #12 bx lr Ltmp1: Lfunc_end0: .cfi_endproc
这代码看起来让人有点沮丧,但其实没那么丢脸懂。首先,所有以.号开始的行都不是汇编指令而是作用于汇编器的。你可以忽略所有如许的代码。
这些以冒号为结束的行,比方:_addFunction:和Ltmp0:,被称为标签。他们是这部分的汇编代码名字。称为_addFunction:标签现实上是这个函数的入口。
这个标签是必须的,如许其他代码可以通过使用addFunction标签来路由函数而不需要知道函数的位置。当最终应用二进制生成的时候,将这些标签转换成现实内存地址是链接器的工作。
注意到编译器通常在函数名的前面添加一个下划线,这也是一个约定。其他的所有以L.开头的叫本地标签,这些标签只能用于函数内部。在这个简单的例子里,没有任何一个本地标签真正被使用,但编译器仍旧生成了,因为这个代码并没有做任何编译优化。
注意是以@字符开头的。在汇编代码后边表明上对应的main.c文件行数对我们看懂汇编代码非常有效。
因此,忽略掉表明和标签,重要的代码如下:
_addFunction: @ 1: sub sp, #12 @ 2: str r0, [sp, #8] str r1, [sp, #4] @ 3: ldr r0, [sp, #8] ldr r1, [sp, #4] @ 4: add r0, r1 @ 5: str r0, [sp] ldr r0, [sp] @ 6: add sp, #12 @ 7: bx lr
其实并没有那么难,对不?想知道更多关于指令的信息,可以看看这个文档,或者看其他的中文.
你发现了这个函数很多汇编代码是多余的。因为一开始,我们的编译器就是Debug(调试)模式,没有任何编译器优化的。如果你把编译优化打开,你会得到一个非常精简的代码。
改变 Show Assembly Output For 到选择器到 存档(Xcode7不一样,区别是如果你想获取精简的汇编,需要Edit Scheme成Release模式,另外切换后记得clean下工程)。搜刮_addFunction:,你可以看到如下代码:
_addFunction: add r0, r1 bx lr
这非常简洁! 可以看到仅仅两个指令就写完了这个函数。你大概没想到仅用两个指令就完成了~ 当然,你平时写的函数一样平常更长也更有趣点~
现在你已经有一个以返回到调用者分支为结束的函数。那么作为一个相互关系的另一个函数,调用该函数的调用者呢?
调用函数

首先,你需要给addFunction函数添加一个让编译器不做优化的属性。你已经发现如果我们开启优化,那么代码会移撤除不须要的指令,甚至连函数调用都会被移除,或者大概直接将函数作为内联函数使用。
比方,编译器大概直接用add指令代替函数调用。现实上,编译器是非常强大智能的,它大概直接帮你计算好了相加后的值,连add指令都不需要生成。
这个教程,我们不盼望编译器做优化或者将函数内联。回到main.m文件,修改函数成如下:
attribute((noinline))
int addFunction(int a, int b) {
int c = a + b;
return c;
}
继承在下方添加另一个函数如下:
void fooFunction() { int add = addFunction(12, 34); printf(“add = %i”, add); }
fooFunction函数简单地用addFunction让12和34相加并且打印出值。这里使用的是C语言的printf而不是Objective-C的NSLog函数的缘故起因是后者的汇编结果更加复杂。
再一次生成汇编代码,搜刮_fooFunction,你可以看到如下代码:
fooFunction: @ 1: push {r7, lr} @ 2: movs r0, #12 movs r1, #34 @ 3: mov r7, sp @ 4: bl addFunction @ 5: mov r1, r0 @ 6: movw r0, :lower16L.str-(LPC1_0+4)) movt r0, :upper16L.str-(LPC1_0+4)) LPC1_0: add r0, pc @ 7: blx _printf @ 8: pop {r7, pc}
这里引入了一些教程之前没有先容过的指令,但不用担心,他们并不复杂,我们来看:

以上是对ARM指令集大抵的先容。还有很多其他指令集,但这些是开始理解指令集最重要的的指令。让我们来用伪代码快速追念一下代码做的事变:
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 r2 入栈 pop {r0, r1, r2} => 栈顶出三个, 并赋值给r0, r1 and r2. b _label => pc = _label bl _label => lr = pc + 4; pc = _label
哇哦~ 现在你可以读懂一些ARM汇编代码了~

Objective-C 汇编

至此,你看到的函数都是C语言的。Objective-C代码要复杂点,不外让我们来检验一下。在ViewController.m代码中添加以下代码实现:

int c = a + b;
return c;
}
让我们再次重复之前精简的汇编方式,搜刮addValue:toValue:函数,可以看到:
“-[ViewController addValue:toValue:]”:
adds r0, r3, r2
bx lr
首先你会注意到标署名字。这次便署名字包罗了类名及全部的方法名。
如果你和之前的addFunction汇编代码相比较,你会发现两个入参存储在了r2及r3而不是r0和r1。为什么呢?
OK,因为Objective-C函数在C函数的基础上多传了两个隐式的参数。addValue:toValue:方法语法上和以下方法雷同:
int ViewController_addValue_toValue(id self, SEL _cmd, int a, int b) {
int c = a + b;
return c;
}
这就是为什么a和b变量分别存储在r2和r3内了。现在你大概知道了前两个隐式参数的含义了,你总是可以使用self这个变量。
但是,_cmd大概之前你没有见过。像self变量一样,在Objective-C代码中它是可获取的,而且代表着当前函数的selector。你一样平常从不会用到它,这就是你为何没听过的缘故起因了。
为了看清Objective-C函数是如何被调用的,我们在ViewController中添加如下代码:

int add = [self addValue:12 toValue:34];
NSLog(@“add = %i”, add);
}
生成代码并找到该方法,你可以看到类似下面的代码(Xcode7生成的有点不一样了):
“-[ViewController foo]”: @ 1: push {r7, lr} @ 2: movw r1, :lower16L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4)) movt r1, :upper16L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4)) LPC1_0: add r1, pc @ 3: ldr r1, [r1] @ 4: movs r2, #12 movs r3, #34 @ 5: mov r7, sp @ 6: blx objc_msgSend @ 7: mov r1, r0 @ 8: movw r0, :lower16L__unnamed_cfstring-(LPC1_1+4)) movt r0, :upper16L__unnamed_cfstring_-(LPC1_1+4)) LPC1_1: add r0, pc @ 9: blx _NSLog @ 10: pop {r7, pc}
再次,和之前我们看到的C语言函数差不多。分解它:
如你所见,当生成汇编代码时,C函数和Objective-C没有多大差异。两者的主要差异在于,Objective-C隐式的通报了两个参数,且selector是在保存在数据段内的。
Objective-C函数实行过程

你已经大抵看到了objc_msgSend函数,你大概也在Crash日记内见过它。这个函数是Objective-C运行时的核心。运行时是胶合Objective-C应用的代码,包括所有的内存管理方法及类处置惩罚。
每一次Objective-C函数调用,都需要objc_msgSendC函数来派发消息。它会去对应的对象方法列表内搜刮方法的实现。objc_msgSend函数署名如下:
末了

自我先容一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提拔技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初志也很简单,就是盼望能够帮助到想自学提拔又不知道该从何学起的朋侪,同时减轻大家的负担。



既有得当小白学习的零基础资料,也有得当3年以上经验的小伙伴深入学习提拔的进阶课程,根本涵盖了95%以上Android开发知识点!岂论你是刚入门Android开发的新手,照旧盼望在技术上不断提拔的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋侪可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包罗大厂面经、学习条记、源码讲义、实战项目、讲解视频,并且会连续更新!
学习资料》,初志也很简单,就是盼望能够帮助到想自学提拔又不知道该从何学起的朋侪,同时减轻大家的负担。**
[外链图片转存中…(img-sGMtg3pe-1714872002863)]
[外链图片转存中…(img-JG4HypXy-1714872002863)]
[外链图片转存中…(img-u80VDDAu-1714872002863)]
既有得当小白学习的零基础资料,也有得当3年以上经验的小伙伴深入学习提拔的进阶课程,根本涵盖了95%以上Android开发知识点!岂论你是刚入门Android开发的新手,照旧盼望在技术上不断提拔的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋侪可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包罗大厂面经、学习条记、源码讲义、实战项目、讲解视频,并且会连续更新!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4