1. 编译依赖外部模块的汇编代码并执行
.global main
.extern printf
.text
main:
stmfd sp!, {r11, lr}
ldr r0, =str
bl printf
ldmfd sp!, {r11, pc}
.data
str:
.asciz "Hello asm
"
.end
上面代码调用到了libc库中的printf函数。
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
"字符串的绝对地址(str是汇编代码中代表字符串的标签,这里只是为了方便大家看,包括所有的编程语言的符号都一样,在实际的机器代码中是没有这些标签和符号的,有的只有数据和地址)。在代码链接成可执行文件的时候str会被分配一个地址,但是如果在加载到内存中的时候内存地址与被分配的地址不一样,那么在运行的过程中就会出错。
所以一般在我们编程的过程中都会加上一个编译参数-fPIC,生成位置无关的代码。
下面我们举一个位置无关的例子给大家看,看下面的C代码:
#include
int main(void)
{
printf("Hello CrossCompile
");
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, PC, R3"这条指令,它的地址是0x8290,而我们根据CPU三级流水线的原理知道,在执行到这条指令的时候,已经去加载后面第二条指令了,这时候pc的值是当前指令地址+0x8才对,即0x8298。这是非常重要的,要谨记!!
就分析这么多,如有错误,欢迎指正!