2.3.2未进行代码优化的Keil编译: thumb模式

让我们用下面的指令讲例程用Keil的thumb模式来编译一下。

armcc.exe –thumb –c90 –O0 1.c

我们可以在IDA里得到下面这样的代码: Listing 2.10:Non-optimizing Keil + thumb mode + IDA

  1. .text:00000000 main
  2. .text:00000000 10 B5 PUSH {R4,LR}
  3. .text:00000002 C0 A0 ADR R0, aHelloWorld ; "hello, world"
  4. .text:00000004 06 F0 2E F9 BL __2printf
  5. .text:00000008 00 20 MOVS R0, #0
  6. .text:0000000A 10 BD POP {R4,PC}
  7. .text:00000304 68 65 6C 6C +aHelloWorld DCB "hello, world",0 ; DATA XREF: main+2

我们首先就能注意到指令都是2bytes(16bits)的了,这正是thumb模式的特征,BL指令作为特例是2个16bits来构成的。只用16bits没可能加载printf()函数的入口地址到PC寄存器。所以前面的16bits用来加载函数偏移的高10bits位,后面的16bits用来加载函数偏移的低11bits位,正如我说过的,所有的thumb模式下的指令都是2bytes(16bits)。但是这样的话thumb指令就没法使用更大的地址。就像上面那样,最后一个bits的地址将会在编码指令的时候省略。总的来讲,BL在thumb模式下可以访问自身地址大于±2M大的周边的地址。

至于其他指令:PUSH和POP,它们跟上面讲到的STMFD跟LDMFD很类似,但这里不需要指定SP寄存器,ADR指令也跟上面的工作方式相同。MOVS指令将函数的返回值0写到了R0里,最后函数返回。

2.3.3开启代码优化的Xcode(LLVM)编译: ARM模式

Xcode 4.6.3不开启代码优化的情况下,会产生非常多冗余的代码,所以我们学习一个尽量小的版本。

开启-O3编译选项

Listing2.11:Optimizing Xcode(LLVM)+ARM mode

  1. __text:000028C4 _hello_world
  2. __text:000028C4 80 40 2D E9 STMFD SP!, {R7,LR}
  3. __text:000028C8 86 06 01 E3 MOV R0, #0x1686
  4. __text:000028CC 0D 70 A0 E1 MOV R7, SP
  5. __text:000028D0 00 00 40 E3 MOVT R0, #0
  6. __text:000028D4 00 00 8F E0 ADD R0, PC, R0
  7. __text:000028D8 C3 05 00 EB BL _puts
  8. __text:000028DC 00 00 A0 E3 MOV R0, #0
  9. __text:000028E0 80 80 BD E8 LDMFD SP!, {R7,PC}
  10. __cstring:00003F62 48 65 6C 6C +aHelloWorld_0 DCB "Hello world!", 0

STMFD和LDMFD对我们来说已经非常熟悉了。

MOV指令就是将0x1686写入R0寄存器里。这个值也正是字串”Hello world!”的指针偏移。

R7寄存器里放入了栈地址,我们继续。

MOVT R0, #0指令时将R0的高16bits写入0。这是因为普通情况下MOV这条指令在ARM模式下,只对低16bits进行操作。需要记住的是所有在ARM模式下的指令都被限定在32bits内。当然这个限制并不影响2个寄存器直接的操作。这也是MOVT这种写高16bits指令存在的意义。其实这样写的代码会感觉有点多余,因为MOVS R0,#0x1686这条指令也能把高16位清0。或许这就是相对于人脑来说编译器的不足。

ADD R0,PC,R0指令把R0寄存器的值与PC寄存器的值进行相加并且保存到R0寄存器里面,用来计算"Hello world!"这个字串的绝对地址。上面已经介绍过了,这是因为代码是PIC(Position-independent code)的,所以这里需要这么做。

BL指令用来调用printf()的替代函数puts()函数。

GCC将printf()函数替换成了puts()。因为printf()函数只有一个参数的时候跟puts()函数是类似的。

printf()函数的字串参数里存在特殊控制符(例如 “%s”,“”,需要注意的是,程序里字串里没有“”,因为在puts()函数里这是不需要的)的时候,两个函数的功效就会不同。

为什么编译器会替换printf()到puts()那?因为puts()函数更快。

puts()函数效率更快是因为它只是做了字串的标准输出(stdout)并不用比较%符号。

下面,我们可以看到非常熟悉的"MOV R0, #0"指令,用来将R0寄存器设为0。