0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

x86 アセンブリ言語 関数呼び出し callとjmpの違い

Posted at

コンパイル

C言語ソース

test.c
#include <stdio.h>

void foo() {
    printf("hello from foo\n");
}

int main() {
    foo();
    printf("back in main\n");
    return 0;
}

アセンブリ言語へコンパイル

C言語コードをアセンブリ言語にコンパイルするために、gccコマンドを使用します。

gcc -S -masm=intel test.c -o test.s

アセンブリ言語ソース

コンパイルされたアセンブリは次のようになっています:

test.s
	.file	"test.c"
	.intel_syntax noprefix
	.text
	.section .rdata,"dr"
.LC0:
	.ascii "hello from foo\0"
	.text
	.globl	foo
	.def	foo;	.scl	2;	.type	32;	.endef
	.seh_proc	foo
foo:
	push	rbp
	.seh_pushreg	rbp
	mov	rbp, rsp
	.seh_setframe	rbp, 0
	sub	rsp, 32
	.seh_stackalloc	32
	.seh_endprologue
	lea	rax, .LC0[rip]
	mov	rcx, rax
	call	puts
	nop
	add	rsp, 32
	pop	rbp
	ret
	.seh_endproc
	.section .rdata,"dr"
.LC1:
	.ascii "back in main\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	push	rbp
	.seh_pushreg	rbp
	mov	rbp, rsp
	.seh_setframe	rbp, 0
	sub	rsp, 32
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	call	foo
	lea	rax, .LC1[rip]
	mov	rcx, rax
	call	puts
	mov	eax, 0
	add	rsp, 32
	pop	rbp
	ret
	.seh_endproc
	.def	__main;	.scl	2;	.type	32;	.endef
	.ident	"GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r3) 14.2.0"
	.def	puts;	.scl	2;	.type	32;	.endef

動作結果

C:\Users\nanashi\test>gcc test.s -o test.exe
C:\Users\nanashi\test>test
hello from foo
back in main

C:\Users\nanashi\test>

jmpへ書き換える

次にfoo()関数の呼び出しをjmp命令を使って行う方法を示します。変更前のアセンブリでは、foo()関数はcall命令で呼ばれていますが、これをjmp命令に変更します。

書き換え箇所

変更前.s
	call	foo
変更後.s
	lea	rax, [rip + back_here]
	push	rax
	jmp	foo;

back_here:

動作結果

変更後も、以前と同じ出力が得られます。

C:\Users\nanashi\test>gcc test.s -o test.exe
C:\Users\nanashi\test>test
hello from foo
back in main

C:\Users\nanashi\test>

call模倣部分の説明

lea rax, [rip + back_here]

jmp 命令foo関数に飛んだ後、foo関数内の処理が終わったら、back_here ラベルに戻ります。
※調べたところ、back_hereは絶対番地ではなくripからの差分(オフセット)であり、rip+back_hereの結果はback_hereの場所になるらしいのですが、頭がこんがらがり、自分でも理解が怪しいです。

詳しい方がおられましたらご教示下さい。1時間ほど考え込みましたが、理解したようなしてないような感じです。

push rax

次に、計算した戻り先アドレス(back_hereの位置)をスタックにプッシュします。このアドレスは、foo関数から戻った後に実行されるべき命令の場所になります。

jmp foo

最後に、foo関数へ跳びます。ここで、jmp命令は単純にfoo関数に移るだけです。call命令のように戻り先アドレスを自動でスタックに積まないので、戻り先を手動でスタックへ入れる必要があるのです。

(今日は熱を出して会社を休んだのに自分は一体何をしているのでしょう。。。)

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?