声明:转载请注明原链接http://my.oschina.net/u/1167407/blog/484426
今天在看《Professional Assembly Language》一书的第四章的Using C Library Functions in Assembly一节时,由于我使用的是64位的Linux系统,所以遇到了一些问题,其中有些挺有用的信息。所以,记录下来以免遗忘。
Using C Library Functions in Assembly这一小节介绍了如何在汇编程序中调用C的库函数。书中给出的示例代码如下:
.section .data
output:
.asciz “The processor Vendor ID is ‘%s’\n”
.section .bss
.lcomm buffer, 12
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $buffer, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $buffer
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
接下来使用gnu as汇编这段程序
提示push的后缀有问题。其原因是汇编器将其当做了64位代码处理,在源程序的开头加入.code32语句。再次汇编,结果如下图,成功。
然后进行将汇编生成的目标文件和C库进行动态链接。运行程序,发现问题。
于是,我就想干脆就直接将寄存器都改为64位的算了,估计就可以了。说干就干,修改源代码如下:
.section .data
output:
.asciz "The processor Vendor ID is ‘%s’\n"
.section .bss
.lcomm buffer, 12
.section .text
.globl _start
_start:
movq $0, %rax
cpuid
movq $buffer, %rdi
movq %rbx, (%rdi)
movq %rdx, 4(%rdi)
movq %rcx, 8(%rdi)
pushq $buffer
pushq $output
call printf
addq $16, %rsp
pushq $0
call exit
再次汇编、链接和运行。
这里我们可以看到一个奇怪的现象,字符串The processor Vendor ID is并没有输出,而只是输出了buffer中的内容。
于是我们写一个如下的C语言程序,来对比一下,查找原因。
#include <stdio.h>
int a;
int main(){
a=3222;
printf("%d\n",a);
return 0;
}
用GCC编译出汇编代码,如下
.file "helloworld.c"
.comm a,4,4
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $3222, a(%rip)
movl a(%rip), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Gentoo 4.8.4 p1.6, pie-0.6.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
运行程序如下图,可以看出程序似乎并没有使用栈来传递参数,而且程序运行正常。
查找资料得知由于x64位架构寄存器增多,GCC(其他编译器暂时还没有试验)开始默认使用类似于_fastcall的调用约定。约定如下:
rdi,rsi,rdx,rcx,r8,r9 用作函数参数,依次对应第1个参数,第2个参数……,第6个参数,多于6个参数时,多的部分从右往左依次压入栈中
接下来编写一个传递更多参数的程序验证以上说法
#include <stdio.h>
int a,b,c,d,e,f,g;
int main(){
a=1111;
b=2222;
c=3333;
d=4444;
e=5555;
f=6666;
g=7777;
printf("%d %d %d %d %d %d %d\n",a,b,c,d,e,f,g);
return 0;
}
其对应的汇编文件如下
.file "helloworld.c"
.comm a,4,4
.comm b,4,4
.comm c,4,4
.comm d,4,4
.comm e,4,4
.comm f,4,4
.comm g,4,4
.section .rodata
.LC0:
.string "%d %d %d %d %d %d %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1111, a(%rip)
movl $2222, b(%rip)
movl $3333, c(%rip)
movl $4444, d(%rip)
movl $5555, e(%rip)
movl $6666, f(%rip)
movl $7777, g(%rip)
movl g(%rip), %edi
movl f(%rip), %esi
movl e(%rip), %r9d
movl d(%rip), %r8d
movl c(%rip), %ecx
movl b(%rip), %edx
movl a(%rip), %eax
movl %edi, 8(%rsp)
movl %esi, (%rsp)
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Gentoo 4.8.4 p1.6, pie-0.6.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
可以看到除了g和f被压到了栈上,其余参数是通过edi,esi,edx,ecx,r8d,r9d这6个寄存器(都是32位的)传入的。
接下来回到最初的问题中,我们可以用以上方式将参数传递给printf函数解决之前的问题。
但我仍然不死心,想要将书上的代码直接成功运行。于是,用readelf工具查看添加了.code32代码的源文件汇编后得到的目标文件。
注意到Class: ELF64这一行,尽管加上了.code32,汇编器仍然将其作为64位代码进行汇编,而我们却将这个目标文件和32位C库进行链接,于是出现了两部分不匹配的现象,所以运行可执行文件时会提示bash: ./test32: Accessing a corrupted shared library的错误。
接下来就是要想办法让gnu as汇编出ELF32格式的目标文件。查命令手册知道用选项--32即可。这次我直接使用书上给的代码(不加.code32),汇编链接,结果如下:
这次链接时出错了,继续查找资料知道了通过添加-m elf_i386选项指定架构后可以解决这个问题。继续试验
这下链接运行都没有问题了。问题解决!
来源:oschina
链接:https://my.oschina.net/u/1167407/blog/484426