2.3.4 开启代码优化的Xcode(LLVM)编译thumb-2模式

在默认情况下,Xcode4.6.3会生成如下的thumb-2代码

Listing 2.12:Optimizing Xcode(LLVM)+thumb-2 mode

  1. __text:00002B6C _hello_world
  2. __text:00002B6C 80 B5 PUSH {R7,LR}
  3. __text:00002B6E 41 F2 D8 30 MOVW R0, #0x13D8
  4. __text:00002B72 6F 46 MOV R7, SP
  5. __text:00002B74 C0 F2 00 00 MOVT.W R0, #0
  6. __text:00002B78 78 44 ADD R0, PC
  7. __text:00002B7A 01 F0 38 EA BLX _puts
  8. __text:00002B7E 00 20 MOVS R0, #0
  9. __text:00002B80 80 BD POP {R7,PC}
  10. ...
  11. __cstring:00003E70 48 65 6C 6C 6F 20 +aHelloWorld DCB "Hello world!",0xA,0

BL和BLX指令在thumb模式下情况需要我们回忆下刚才讲过的,它是由一对16-bit的指令来构成的。在thumb-2模式下这条指令跟thumb一样被编码成了32-bit指令。非常容易观察到的是,thumb-2的指令的机器码也是从0xFx或者0xEx的。对于thumb和thumb-2模式来说,在IDA的结果里机器码的位置和这里是交替交换的。对于ARM模式来说4个byte也是反向的,这是因为他们用了不同的字节序。所以我们可以知道,MOVW,MOVT.W和BLX这几个指令的开始都是0xFx。

在thumb-2指令里有一条是“MOVW R0, #0x13D8”,它的作用是写数据到R0的低16位里面。

MOVT.W R0, #0的作用类似与前面讲到的MOVT指令,但它可以工作在thumb-2模式下。

还有些跟上面不同的地方,比如BLX指令替代了上面用到的BL指令,这条指令不仅将控制puts()函数返回的地址放入了LR寄存器里,并且讲代码从thumb模式转换到了ARM模式(或者ARM转换到thumb(根据现有情况判断))。这条指令跳转到下面这样的位置(下面的代码是ARM编码模式)。

  1. __symbolstub1:00003FEC _puts ; CODE XREF: _hello_world+E
  2. __symbolstub1:00003FEC 44 F0 9F E5 LDR PC, =__imp__puts

可能会有细心的读者要问了:为什么不直接跳转到puts()函数里面去?

因为那样做会浪费内存空间。

很多程序都会使用额外的动态库(dynamic libraries)(Windows里面的DLL,还有*NIX里面的.so,MAC OS X里面的.dylib),通常使用的库函数会被放入动态库中,当然也包括标准C函数puts()。

在可执行的二进制文件里(Windows的PE里的.exe文件,ELF和Mach-O文件)都会有输入表段。它是一个用来引入额外模块里模块名称和符号(函数或者全局变量)的列表。

系统加载器(OS loader)会加载所有需要的模块,当在主模块里枚举输入符号的时候,会把每个符号正确的地址与相应的符号确立起来。

在我们的这个例子里,__imp__puts就是一个系统加载器加载额外模块的32位的地址值。LDR指令只需要把这个值加载到PC里面去,就可以控制程序流程到puts()函数里去。

所以只需要在系统加载器里的时候,一次性的就能将每个符号所对应的地址确定下来,这是个提高效率的好方式。

外加,我们前面也指出过,我们没办法只用一条指令并且不做内存操作的情况下就将一个32bit的值保存到寄存器里,ARM并不是唯一的模式的情况下,程序里去跳入动态库中的某个函数里,最好的办法就是这样做一些类似与上面这样单一指令的函数(称做thunk function),然后从thumb模式里也能去调用。

在上面的例子(ARM编译的那个例子)中BL指令也是跳转到了同一个thunk function里。尽管没有进行模式的转变(所以指令里不存在那个”X”)。

函数的序幕,是像下面这样的代码片段:

  1. push ebp
  2. mov ebp, esp
  3. sub esp, X

这些指令做了什么:将寄存器EBP的值入栈,将ESP赋值给EBP,在栈中分配空间, 用来保存局部变量。

在函数执行过程中,EBP是固定的,可以用来访问局部变量和函数参数。也可以使用 ESP,但在函数运行过程中,ESP会变化,使用起来不方便。

  1. mov esp, ebp
  2. pop ebp
  3. ret 0