OpenEuler中C与汇编的混合编程(选做)

作业要求

  1. 在X86_64架构下实践2.5中的内容,提交代码和实践截图
  2. 把2.5的内容在OpenEuler中重新实践一遍,提交相关代码和截图
  3. 实验内容要经过答辩才能得到相应分数

实践过程——在x86_64架构下实现

电脑为x86_64架构,首先在ubuntu中实现一遍。

1. 将C代码编译成汇编代码

a.c:

#include <stdio.h>

extern int B();

int A(int X, int y)
{
	int d,e,f;
	d = 4; e = 5; f = 6;
	f = B(d,e);
}

编译为a.s,用cc -m32 -S a.c -o a.s

a.s:

	.file	"a.c"
	.text
	.globl	A
	.type	A, @function
A:
.LFB0:
	.cfi_startproc
	endbr32
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	pushl	%ebx
	subl	$20, %esp
	.cfi_offset 3, -12
	call	__x86.get_pc_thunk.ax
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	movl	$4, -20(%ebp)
	movl	$5, -16(%ebp)
	movl	$6, -12(%ebp)
	subl	$8, %esp
	pushl	-16(%ebp)
	pushl	-20(%ebp)
	movl	%eax, %ebx
	call	B@PLT
	addl	$16, %esp
	movl	%eax, -12(%ebp)
	nop
	movl	-4(%ebp), %ebx
	leave
	.cfi_restore 5
	.cfi_restore 3
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	A, .-A
	.section	.text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
	.globl	__x86.get_pc_thunk.ax
	.hidden	__x86.get_pc_thunk.ax
	.type	__x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB1:
	.cfi_startproc
	movl	(%esp), %eax
	ret
	.cfi_endproc
.LFE1:
	.ident	"GCC: (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 4
	.long	 1f - 0f
	.long	 4f - 1f
	.long	 5
0:
	.string	 "GNU"
1:
	.align 4
	.long	 0xc0000002
	.long	 3f - 2f
2:
	.long	 0x3
3:
	.align 4
4:

2. 用汇编语言实现简单函数,用C调用

s.s

	.global get_esp, get_ebp
get_esp:
	movl	%esp, %eax
	ret
get_ebp:
	movl	%ebp, %eax
	ret

s.c:

#include <stdio.h>

int main()
{
	int ebp, esp;
	ebp = get_ebp();
	esp = get_esp();
	printf("ebp=%8x   esp=%8x\n",ebp, esp);
}

对s.s和s.c进行混合编译,将C代码和汇编代码混合编译。
使用gcc -m32 s.s s.c -o s就可。程序会打印当前eax和ebp寄存器的值。
编译、运行截图:

3. 用汇编代码实现mysum()函数,用C调用

mysum.s:

	.text
	.global mysum, printf	#mysum被调用,调用printf
	
mysum:
#	首先建立栈帧
	pushl	%ebp
	movl	%esp, %ebp
#	mysum函数代码
	movl	8(%ebp), %eax	# AX = x
	addl	12(%ebp), %eax	# AX += y
	
	movl	%ebp, %esp
	pop	%ebp
	ret

mysum.c:

#include <stdio.h>

int main()
{
	int a,b,c;
	a = 2019; b = 1320;
	c = mysum(a,b);
	printf("c=%d\n", c);
}

编译运行截图:
使用gcc -m32 mysum.s mysum.c -o mysum进行编译。

4. 使用汇编调用C函数

printf.s:

	.text
	.global sub, a, b, printf
sub:
	pushl	%ebp
	movl	%esp, %ebp
	
	pushl	b
	pushl	a
	pushl	$fmt
	call	printf
	addl	$12, %esp
	
	movl	%ebp, %esp
	popl	%ebp
	ret
	
	.data
fmt:	.asciz	"a=%d,b=%d\n"

printf.c:

#include <stdio.h>

int a,b;
int main()
{
	a = 13; b = 20;
	sub();
	return 0;
}

编译运行截图:

实践过程——在openEuler下实现和问题解决

由于openEuler中没有支持编译运行32位代码的包,所以只能在64位下实现。(需要修改汇编代码)

1. 将C代码转为汇编代码

2. 用汇编语言实现简单函数,用C调用


以上两步没有出现任何问题,ubuntu中32位和64位的代码在openEuler中兼容。


3. 用汇编代码实现mysum()函数,用C调用

在这一步,若不修改源汇编代码,编译时出现问题,如图:

发现push和pop出现了问题。修改了很久,都没有解决。尝试过修改为pushl等,都会出错,出现段错误等。
后来我查看了一些openEuler中编译出的汇编代码,如a.s中的代码。观察发现发现在64位openEuler中,使用的sp和bp寄存器不是esp和ebp,而是rsp和rbp。
如图:


我将自己的汇编代码中的所有ebp,esp修改为rbp,rsp,然后再编译,出现以下报错:

原来,rbp和rsp的位数是64位,不能用pushl和popl(l后缀为32位。)直接使用push和pop,就可以按照其大小进行入栈和出栈了。
修改后的mysum.s:

	.text
	.global mysum, printf	#mysum被调用,调用printf
	
mysum:
#	首先建立栈帧
	push	%rbp
	mov	%rsp, %rbp
#	mysum函数代码
	movl	8(%rbp), %eax	# AX = x
	addl	12(%rbp), %eax	# AX += y
	
	mov	%rbp, %rsp
	pop	%rbp
	ret

最后成功编译运行,结果运行结果出现问题:

估计也是位数导致的问题。
通过cgdb,我查看了运行中对应的数据存放的位置,重新计算了a和b在内存中的位置和ebp说存放位置之间的差值,然后修改了代码。


(查看其值所在内存地址)


差值为24和28,修改代码后,在此测试,程序得到了正确结果。

最后的mysum.s代码为:

	.text
	.global mysum, printf	#mysum被调用,调用printf
	
mysum:
#	首先建立栈帧
	push	%rbp
	mov	%rsp, %rbp
#	mysum函数代码
	mov	24(%rbp), %rax	# AX = x
	add	28(%rbp), %rax	# AX += y
	
	mov	%rbp, %rsp
	pop	%rbp
	ret

4. 使用汇编调用C函数

通过尝试,发现只修改寄存器为64位无法解决问题,仍然提示段错误,通过cgdb调试发现是在调用printf时出现的。
自己编写一个hello.c,调用printf函数,查看原理。
hello.c:

#include <stdio.h>

int main()
{
	int a,b;
	a=13;
	b=20;
	printf("a=%d,b=%d\n",a,b);
	return 0;
}

gcc -S hello.c -o hello.s得到hello.c的汇编代码,以下为hello.s截图

发现和ubuntu中32位不同,openEuler64位中,printf函数的参数不光是存在栈中的,而是还分别在esi,edx,edi中。
修改后的代码:

	.text
	.global sub, a, b, printf
sub:
	push	%rbp
	mov	%rsp, %rbp
	
	push	a
	push	b
	movl	-8(%rbp),%esi
	movl	-16(%rbp),%edx	
	movl	$fmt,%edi

	call	printf
	
	add	$16, %rsp
	mov	%rbp, %rsp
	pop	%rbp
	ret
	
	.data
fmt:	.asciz	"a=%d,b=%d\n"

通过修改代码,重新实现64位中的printf参数传递,最终完成输出,结果图如下:

代码链接

https://gitee.com/Ressurection20191320/code/tree/master/IS/MixedProgramming

posted @ 2021-12-05 21:37  20191320  阅读(112)  评论(0编辑  收藏  举报