X86_64平台下32位汇编语言调用C库函数程序的汇编与链接

百般思念 提交于 2020-02-28 23:42:27

 声明:转载请注明原链接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选项指定架构后可以解决这个问题。继续试验

这下链接运行都没有问题了。问题解决!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!