ios 汇编教程

注:本文由破船译自:raywenderlich
转自破船http://beyondvincent.com/2013/06/19/ios%E6%B1%87%E7%BC%96%E6%95%99%E7%A8%8B%EF%BC%9Aarm/。

为自己方便查找转自这里

SpeakAssemblySmall

你说的是汇编吗?

我们写的Objective-C代码,最终会被转换为机器代码 —— 由ARM处理器能识别的1和0组成。实际上,在机器代码之间,还有一门人类可以阅读的语言 —— 汇编语言。

了解汇编,可以深入到你的代码里面进行调试和优化的探索,并有助于你对Objective-C运行时(runtime)的理解,同时也能满足你内心的好奇!

在这篇iOS汇编教程中,你能学到:

  • 什么是汇编 —— 以及为什么需要关注它。
  • 如何阅读汇编 —— 特别是由Objective -C生成的汇编。
  • 在调试的时候如何使用assembly view —— 遇到一个bug或者crash,看看到底是怎么回事,这非常有用。

为了有效吸收本文内容,建议本文的读者对象为已经熟悉Objective-C编程了。当然,你也应该要知道一些简单的计算机科学相关概念,例如栈、CPU以及它们是如何运行的。如果你对CPU不太熟悉,建议在阅读本文之前,先看看这里的内容:微处理器的工作原理

目录[分两篇文章翻译]:

iOS汇编教程:ARM(1)

  • 开始:什么是汇编
  • 函数调用约定
  • 创建工程
  • 加法(addFunction)

iOS汇编教程:ARM(2)

  • 函数的调用
  • Objective -C 汇编
  • Obj-C 消息发给了谁
  • 你现在可以进行逆向工程了
  • 何去何从

——————————————————————-

iOS汇编教程:ARM(1)

开始:什么是汇编

Objective-C是一门高级语言。编译器会将你的Objective-C代码编译为汇编语言代码:一门低级语言,不过还不是最低级的语言。

这些汇编会被汇编器(assembler)组装为机器代码——CPU可以识别的0和1。好在一般开发者并没有必要考虑机器代码,不过有时候详细的了解汇编,会非常有用。

SpeakAssembly

每一个汇编指令都会告诉CPU执行一个相关任务,例如“对两个数字执行加(add)操作”,或“从某个内存地址加载数据”。

除了主存外 ——如 iPhone 5有1GB的主存、Mac电脑可能会有8GB —— CPU还有少许的存储部件,称之为寄存器,寄存器的访问速度非常快,一个寄存器就像一个变量一样,可以存储单个值。

所有的iOS设备(实际上,现如今,几乎所有的移动设备)使用的CPU都是基于ARM架构。 ARM芯片使用的指令集是RISC(精简指令集),该指令集非常的精简,并且易读(比x86的指令集精简多了)。

一个汇编指令(或者语句)看起来如下所示:

 
 
  1. mov r0, #42

上面的这行汇编指令,涉及到好多命令(或操作)。mov的作用是对数据进行移动。在ARM汇编指令中,目标是第一个,所以,上面的指令是将值42移动到寄存器r0中。再来看看下面的代码:

 
 
  1. ldr r2, [r0]
  2. ldr r3, [r1]
  3. add r4, r2, r3

上面汇编指令的作用是首先将寄存器r0和r1中的值装载到寄存器r2和r3中,然后对寄存器r2和r3中的值进行加(add)操作,加的结果存放到r4中。

很容易看懂吧!

函数调用约定

要想理解汇编代码,首先重要的事情就是理解代码之间的交互——意思是一个函数调用另一个函数的方式。这包括了参数如何传递以及如何从函数返回结果——称之为调用的约定。编译器必须严格的遵守相关标准进行代码编译,这样生成的代码,才能够相互兼容。

上面讨论过,寄存器是的存储空间非常少,并且靠近CPU——用来存储当前使用的一些值。ARM CPU有16个寄存器:r0到r15。每个寄存器为32bit。调用约定规定了这些寄存器的特定用途。如下:

  •  r0 – r3:存储传递给函数的参数值。
  •  r4 – r11:存储函数的局部变量。
  • r12:是内部过程调用暂时寄存器(intra-procedure-call scratch register)。
  • r13:存储栈指针(sp)。在计算机中,栈非常重要。这个寄存器保存着栈顶的指针。这里可以看到更多关于栈的信息:Wikipedia
  • r14:链接寄存器(link register)。存储着当被调用函数返回时,将要执行的下一条指令的地址。
  • r15:用作程序计数器(program counter)。存储着当前执行指令的地址。每条执行被执行后,该计数器会进行自增(+1)。

这里可以看到更多相关ARM 调用约定的内容:this document from ARM。苹果公司也给出了一份文档详细介绍了在iOS开发中的调用约定: calling convention used for iOS development

下面我们就从代码上开始真正的认识汇编。

创建工程

打开Xcode,File\New\New Project,选择iOS\Application\Single View Application,然后点击Next,工程的配置如下:

01-Create-the-project

  • Product name: ARMAssembly
  • Company Identifier: 一般为反向的DNS标示
  • Class Prefix: 空白
  • Devices: iPhone
  • Use Storyboards: No
  • Use Automatic Reference Counting: Yes
  • Include Unit Tests: No

点击 Next 选择工程存储的位置——完成工程的创建。

加法(addFunction)

下面我们写一个加法函数:对两个数进行相加,然后返回结果。这里我们先用C语法写,后面再介绍用OC来写(OC稍微复杂一点)。在工程的Supporting Files目录中打开main.m文件,然后将下面的函数拷贝并粘贴到文件的顶部。

 
 
  1. int addFunction(int a, int b) {
  2. int c = a + b;
  3. return c;
  4. }

现在将Xcode中的scheme设置为为设备构建:选中iOS Device作为scheme target(如果你将设备连接到电脑中,会现实<你的设备名称>,如“Matt Galloway的iPhone 5”)——这样选择之后,生成的汇编就是针对ARM的,而不是针对x86(模拟器使用)。Xcode的选择效果如下图所示:

02-Select-iOS-Device-scheme

 

然后选择:Product\Generate Output\Assembly File。过一会之后,Xcode会生成一个文件,这个文件里面有很多行都有下划线__。在文件的顶部,好多行都是以.section开头。接着选中Show Assembly Output For中的Running

  注意:默认情况下,使用的是debug scheme中的设置信息,所以默认选中的就是Running。在debug模式下,编译器对代码没有做优化处理——首先观察没有进过优化处理的汇编,更利于理解代码具体都发生了什么。

在生成的文件中搜索_addFunction,会看到类似如下的代码:

 
 
  1. .globl _addFunction
  2. .align 2
  3. .code 16 @ @addFunction
  4. .thumb_func _addFunction
  5. _addFunction:
  6. .cfi_startproc
  7. Lfunc_begin0:
  8. .loc 1 13 0 @ main.m:13:0
  9. @ BB#0:
  10. sub sp, #12
  11. str r0, [sp, #8]
  12. str r1, [sp, #4]
  13. .loc 1 14 18 prologue_end @ main.m:14:18
  14. Ltmp0:
  15. ldr r0, [sp, #8]
  16. ldr r1, [sp, #4]
  17. add r0, r1
  18. str r0, [sp]
  19. .loc 1 15 5 @ main.m:15:5
  20. ldr r0, [sp]
  21. add sp, #12
  22. bx lr
  23. Ltmp1:
  24. Lfunc_end0:
  25. .cfi_endproc

上面的代码看起来有点凌乱,实际上也不难以读懂。我们来看看,首先,所有以”.”开头的代码行都不是汇编指令,我们可以忽略所有这些以”.”开头的代码行。

代码中以冒号结尾的的代码行(例如_addFunction:和Ltim0: ),我们称之为标签(label)。这些标签的作用是给汇编代码片段指定相关的名字.名为_addFunction:的标签,实际上是一个函数的入口点.

这个标签(_addFunction: )是必须有的:别的代码调用addFunction函数时,并不需要知道该函数具体在什么地方,通过简单的一个符号或标签就可以进行调用.在最终生成程序二进制文件时,链接器会把这个标签转换到实际的地址.

我们需要注意的时,编译器总是会在函数名前面添加一个下划线——这仅仅是一个约定。另外,其他所有的标签都是以L开头——这些通常称为局部标签(local label),只会在函数内部使用。在上面的代码中,虽然没有实际用到局部标签,不过编译器还是为我们生成了一些——之所以会生成这些没有被使用到的局部标签,是由于代码还没有做任何的优化处理。

注释是以@字符开头。通过上面的分析,这样一来,忽略掉注释和标签,代码看起来如下所示:

 
 
  1. _addFunction:
  2. @ 1:
  3. sub sp, #12
  4. @ 2:
  5. str r0, [sp, #8]
  6. str r1, [sp, #4]
  7. @ 3:
  8. ldr r0, [sp, #8]
  9. ldr r1, [sp, #4]
  10. @ 4:
  11. add r0, r1
  12. @ 5:
  13. str r0, [sp]
  14. ldr r0, [sp]
  15. @ 6:
  16. add sp, #12
  17. @ 7:
  18. bx lr

下面我们来看看代码中每部分汇编都做了什么:

1、首先,在栈(stack)创建临时存储所需要的空间。栈提供了许多内存供函数使用。ARM中的栈是向下延伸的,也就是说,在栈上创建一些空间,需要从栈指针开始减去(subtract)一些空间。在这里,预留了12个字节。

2、r0和r1用来存储传递给调用函数的参数值。如果函数有4个参数,那么会把r2和r3当做第三个和第四个参数。如果函数的参数超过了4个,或者携带的参数不适合使用32位的寄存器(例如很大的数据结构),那么可以通过栈来传递这些参数。

在这里,两个参数被保存到栈中。这是由存储寄存器(str)指令完成的。

上面的指令可以指定一个偏移量,用来应用在某个值上面。所以[sp, #8]的意思是存储至“栈指针寄存器+8的地方”,因此,str r0, [sp, #8]的作用是:将寄存器r0中的内容存储到栈指针(加8)指向的内存地址.

3、将刚刚保存到栈中的值读取至相同的寄存器中(r0和r1)。这里,的ldr指令与str指令刚好相反,ldr(load register)会把指定内存位置中的的内容加载到寄存器中。ldr和str的语法非常相似:ldr r0, [sp, #8]的作用是“将栈指针加8后指向的地址内容加载到r0寄存器中”。

这里你可能会感觉到奇怪,为什么ro和r1寄存器中的值刚刚保存,马上又将其加载回来,答案是:这两行代码是冗余的,可以去掉!如果编译器做了优化处理,那么这些冗余的代码会被忽略掉.

4、这是该函数中最终的要一个指令:执行加操作。该执行的意思是:将r0和r1中的内容进行相加,然后把结果放到r0中。

add指令可以是两个参数,也可以是三个参数.如果指定三个参数,那么第一个参数就被当做目标寄存器,剩下的两个则为源寄存器.因此,这里的指令可以写成这样:add r0, r0, r1。

5、同样,编译器生成了一些冗余代码:将加的结果存储到栈中,接着立即从栈中读取回来。

6、终止函数的地方:将栈指针指向调用addFunction函数时的最初地方。addFunction开始于:sp减去12的地方:预留了12个字节。现在将12加回去即可。这里必须确保栈指针的正确操作,否则栈指针会指向错误的地方。

最后,执行bx指令会回到调用函数的地方.这里的寄存器lr是链接寄存器(link register),该存储器存储着将要执行的下一条指令。注意,addFunction返回之后,r0寄存器会存储着该函数相加的结果值——这也是调用约定中的一部分:函数的返回值永远都被存储在r0寄存器中。除非一个寄存器不够存储,这是可以使用r1-r3。

上面就是所有相关addFunction的介绍,并不复杂吧?预知关于这些指令的更多内容,请看这里: ARM website.

重申一下,上面的方法有好多冗余的地方:这是由于编译器处于debug模式,不会对代码做优化处理.如果对代码进行了优化处理,会看到生成的汇编代码非常的少。

选中Show Assembly Output For中的Archiving。然后搜索_addFunction:,会看到如下指令(只有这些):

 
 
  1. _addFunction:
  2. add r0, r1
  3. bx lr

这看起来非常简洁:只需要两条指令就完成了addFunction函数的功能。当然,在实际开发中,一个函数一般都会有好多指令。

现在,这个addFunction已经返回到调用的函数那里了.下面我们就来看看关于调用的函数的相关信息.

下面的内容会在第二篇文章中翻译:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值