1. 程式人生 > 其它 >OpenEuler中C與彙編的混合程式設計(選做)

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