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
編譯、執行截圖:
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引數傳遞,最終完成輸出,結果圖如下: