1. 编译依赖外部模块的汇编代码并执行
.global main .extern printf .text main: stmfd sp!,{r11,lr} ldr r0,=str bl printf ldmfd sp!,pc} .data str: .asciz "Hello asm\n" .end
- 输入下面命令编译成目标文件
arm-linux-androideabi-as test.S -o test.o得到test.o目标文件。
- 输入下面命令编译成可执行文件
arm-linux-androideabi-ld test.o ~/Softwares/Android/android-ndk-r9d/platforms/android-19/arch-arm/usr/lib/crtbegin_dynamic.o ~/Softwares/Android/android-ndk-r9d/platforms/android-19/arch-arm/usr/lib/crtend_android.o -l ~/Softwares/Android/android-ndk-r9d/platforms/android-19/arch-arm/usr/lib/libc.so -I /system/bin/linker -o test.out得到可执行文件test.out,将其push到android设备中运行可以打印出Hello asm字符串。
2. PIC位置无关代码分析
位置无关码:cpu取指时,总是相对于本条执行指令的相对地址去取指。比如指行一个ADD指令时,PC要取下一指令的地址,就在原来的基础上+4。这就不管你代码放在存储器的任何位置,只要他们的相对地址没有改变,就能正常执行程序。 位置相关码:可以这样来说,就是cpu每次取指都从绝对位置去取,而不是上面的相对位置。这个绝对地址就是相对起始地址0来说的。这样,就要求你在存放程序时,必须给连接脚本所规定的一样,把代码放到指定位置。我们上面的例子中在给printf传递参数的时候用到了str,即"Hello asm\n"字符串的绝对地址(str是汇编代码中代表字符串的标签,这里只是为了方便大家看,包括所有的编程语言的符号都一样,在实际的机器代码中是没有这些标签和符号的,有的只有数据和地址)。在代码链接成可执行文件的时候str会被分配一个地址,但是如果在加载到内存中的时候内存地址与被分配的地址不一样,那么在运行的过程中就会出错。
下面我们举一个位置无关的例子给大家看,看下面的C代码:
#include <stdio.h> int main(void) { printf("Hello CrossCompile\n"); return 0; }gcc编译生成可执行文件后我们通过ida查看其汇编代码如下图:
可以看到其汇编代码中是如何为printf传递参数的,基本上是下面三条语句
LDR R3,=(aHelloCrosscomp - 0x8298) ADD R3,PC,R3 ; MOV R0,R3 ;而aHelloCrosscomp的定义是在rodata段中,如下图:
上面的语句到底是什么意思呢?仔细分析一下就可以知道, 所谓位置无关的代码就是要访问相对地址,不管可执行文件加载到内存中的任何位置,可执行文件内部的数据和指令之间的相对位置是绝对一致的,我们就可以利用这一点,通过要访问的数据的地址与当前pc值即当前指令地址之间的差值得到这个相对偏移。
那为什么在main函数后面会有"off_82A8 DCD aHelloCrosscomp - 0x8298"这样的一个数据定义呢?原因很简单,因为一条ARM指令只有32位,而aHelloCrosscomp的位置有可能很大,一条指令是放不下的,而地址的相对偏移是固定的,在编译阶段就可以确定,所以编译器直接算好了在text段函数的后面定义了一个临时的内部的数值。一定是通过pc值加上这个相对偏移,得到字符串的最终地址存储在寄存器中,而不能直接拿字符串的32位的地址去操作,一条ARM指令放不下这么多数据。
那为什么是
aHelloCrosscomp - 0x8298呢?这个0x8298是怎么得到的呢?我们看代码中,真正将当前指令与相对偏移计算得到字符串地址的指令是".text:00008290 ADD R3,R3"这条指令,它的地址是0x8290,而
我们根据cpu三级流水线的原理知道,在执行到这条指令的时候,已经去加载后面第二条指令了,这时候pc的值是当前指令地址+0x8才对
,即0x8298。这是非常重要的,要谨记!!
就分析这么多,如有错误,欢迎指正!