函數調用與匯編指令的關系


寫一段簡單的C代碼分析其背后與匯編指令的關系

最近在看hotspot的代碼,hotspot解釋器會將字節碼翻譯成匯編指令,所以要先復習下這個基礎
這篇講的太泛了,看 這篇吧,是一步一步有圖對應的

C代碼

#include <stdio.h>

int  main(int args, char** argv){
	printf("%d", add1(100, 200, 500, 600));
}

int add1(int i, int j, int k, int m){
	return i + j + k + m;
}

gcc編譯驗證執行結果:

gcc -g2 FunctionInvokedAssembly.c -o FunctionInvokedAssembly
./FunctionInvokedAssembly  
#1400

gcc編譯成匯編代碼

gcc -S -o FunctionInvokedAssembly.s FunctionInvokedAssembly.c

匯編代碼如下:

	.file	"FunctionInvokedAssembly.c"
	.section	.rodata
.LC0:
	.string	"%d"
	.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	%edi, -4(%rbp)
	movq	%rsi, -16(%rbp)
	movl	$600, %ecx
	movl	$500, %edx
	movl	$200, %esi
	movl	$100, %edi
	movl	$0, %eax
	call	add1
	movl	%eax, %esi
	movl	$.LC0, %edi
	movl	$0, %eax
	call	printf
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.globl	add1
	.type	add1, @function
add1:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	%edx, -12(%rbp)
	movl	%ecx, -16(%rbp)
	movl	-8(%rbp), %eax
	movl	-4(%rbp), %edx
	addl	%eax, %edx
	movl	-12(%rbp), %eax
	addl	%eax, %edx
	movl	-16(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	add1, .-add1
	.ident	"GCC: (Ubuntu 4.8.5-4ubuntu8) 4.8.5"
	.section	.note.GNU-stack,"",@progbits

匯編用到的一些寄存器及一些指令

  • eax, ebx, ecx, edx, esi, edi, ebp(rbp), esp(rbp)等都是X86 匯編語言中CPU上的通用寄存器的名稱。
  • rbp 調用函數的棧幀棧底地址
  • rsp 被調函數的棧幀棧底地址
  • eip:寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令后,從EIP寄存器中讀取下一條指令的內存地址,然后繼續執行
  • 減少esp(rsp)寄存器的值表示擴展棧幀
  • X86-64中,所有寄存器都是64位,相對32位的x86來說,標識符發生了變化,比如:從原來的%ebp變成了%rbp。為了向后兼容性,%ebp依然可以使用,不過指向了%rbp的低32位。
  • X86-64有16個64位寄存器,分別是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。%rax 作為函數返回值使用。%rsp 棧指針寄存器,指向棧頂。%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函數參數,依次對應第1參數,第2參數...%rbx,%rbp,%r12,%r13,%14,%15 用作數據存儲,遵循被調用者使用規則,簡單說就是隨便用,調用子函數之前要備份它,以防他被修改。%r10,%r11 用作數據存儲,遵循調用者使用規則,簡單說就是使用之前要先保存原值

一條call指令,完成了兩個任務:

  • 將調用函數(main)中的下一條指令入棧,被調函數返回后將取這條指令繼續執行,64位rsp寄存器的值減8
  • 修改指令指針寄存器rip的值,使其指向被調函數的執行位置

寄存器圖示

63              31             0
+------------------------------+
|%rax           |%eax          | 返回值
+------------------------------+
|%rbx           |%ebx          | 被調用保護者
+------------------------------+
|%rcx           |%ecx          | 第四個參數
+------------------------------+
|%rdx           |%edx          | 第三個參數
+------------------------------+
|%rsi           |%esi          | 第二個參數
+------------------------------+
|%rdi           |%edi          | 第一個參數
+------------------------------+
|%rbp           |%ebp          | 被調用者保護
+------------------------------+
|%rsp           |%esp          | 堆棧指針
+------------------------------+
|%r8            |%r8d          | 第五個參數
+------------------------------+
|%r9            |%r9d          | 第六個參數
+------------------------------+
|%r10           |%r10d         | 調用者保護
+------------------------------+
|%r11           |%r11d         | 調用者保護
+------------------------------+
|%r12           |%r12d         | 被調用者保護
+------------------------------+
|%r13           |%r13d         | 被調用者保護
+------------------------------+
|%r14           |%r14d         | 被調用者保護
+------------------------------+
|%r15           |%r15d         | 被調用者保護
+------------------------------+

棧幀

           +-------------------+
           |                   |
           |                   |
           | other frames      |
           |                   |
           |                   |
           +-------------------+
           |                   |
           |                   |
           | last frame        |
           |                   |
           |                   |
           +-------------------+
           | argument 1        |
           +-------------------+
           | argument 2        |
           +-------------------+
           | return address    |
           +-------------------+
%ebp->     | last frame %ebp   |
           +-------------------+
           |                   |
           |                   |
           | current frame     |
           |                   |
           |                   |
           +-------------------+
%esp->     |                   |
           +-------------------+

入口函數是main,然后調用各個子函數。在對應機器語言中,GCC把過程轉化成棧幀(frame),簡單的說,每個棧幀對應一個過程。X86-32典型棧幀結構中,由%ebp指向棧幀開始,%esp指向棧頂。

gcc邊調試邊反編譯匯編代碼

gdb FunctionInvokedAssembly
> b main
> r
>  disassemble /rm
Breakpoint 1, main (args=1, argv=0x7fffffffdf48) at FunctionInvokedAssembly.c:11
11		printf("%d", add1(100, 200, 500, 600));
(gdb) disassemble /rm
Dump of assembler code for function main:
9	int  main(int args, char** argv){
   0x00000000004004fd <+0>:	55	push   %rbp
   0x00000000004004fe <+1>:	48 89 e5	mov    %rsp,%rbp
   0x0000000000400501 <+4>:	48 83 ec 10	sub    $0x10,%rsp
   0x0000000000400505 <+8>:	89 7d fc	mov    %edi,-0x4(%rbp)
   0x0000000000400508 <+11>:	48 89 75 f0	mov    %rsi,-0x10(%rbp)

10	//  printf("%d", add1(100, 200, 500, 600, 700, 800, 900, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13));
11		printf("%d", add1(100, 200, 500, 600));
=> 0x000000000040050c <+15>:	b9 58 02 00 00	mov    $0x258,%ecx
   0x0000000000400511 <+20>:	ba f4 01 00 00	mov    $0x1f4,%edx
   0x0000000000400516 <+25>:	be c8 00 00 00	mov    $0xc8,%esi
   0x000000000040051b <+30>:	bf 64 00 00 00	mov    $0x64,%edi
   0x0000000000400520 <+35>:	b8 00 00 00 00	mov    $0x0,%eax
   0x0000000000400525 <+40>:	e8 13 00 00 00	callq  0x40053d <add1>
   0x000000000040052a <+45>:	89 c6	mov    %eax,%esi
   0x000000000040052c <+47>:	bf f4 05 40 00	mov    $0x4005f4,%edi
   0x0000000000400531 <+52>:	b8 00 00 00 00	mov    $0x0,%eax
   0x0000000000400536 <+57>:	e8 b5 fe ff ff	callq  0x4003f0 <printf@plt>

12	}
   0x000000000040053b <+62>:	c9	leaveq 
   0x000000000040053c <+63>:	c3	retq   

End of assembler dump.

> info register
rax            0x4004fd	4195581
rbx            0x0	0
rcx            0x400570	4195696
rdx            0x7fffffffdf58	140737488346968
rsi            0x7fffffffdf48	140737488346952
rdi            0x1	1
rbp            0x7fffffffde60	0x7fffffffde60
rsp            0x7fffffffde50	0x7fffffffde50
r8             0x7ffff7dd0d80	140737351847296
r9             0x7ffff7dd0d80	140737351847296
r10            0x0	0
r11            0x0	0
r12            0x400400	4195328
r13            0x7fffffffdf40	140737488346944
r14            0x0	0
r15            0x0	0
rip            0x40050c	0x40050c <main+15>
eflags         0x206	[ PF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

參考

X86-64寄存器和棧幀
函數調用過程探究
X86 Opcode and Instruction Reference
你會swap嗎,按值傳遞還是按引用?
寄存器理解 及 X86匯編入門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM