OpenEuler中C与汇编的混合编程(选做)
作业要求
- 在X86_64架构下实践2.5中的内容,提交代码和实践截图
- 把2.5的内容在OpenEuler中重新实践一遍,提交相关代码和截图
- 实验内容要经过答辩才能得到相应分数
实践过程——在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