기존코드의 문제가 있다
// main.c
#include <stdio.h>
int asm_main();
int main()
{
int ret = asm_main();
printf("result : %d\n", ret);
}
; stackframe.asm
.model flat
public _asm_main
.code
_asm_main:
; _add(1, 2)
push 2
push 1
call _add
add esp, 8
ret
_add:
; 만약 여기서 스택을 한 번 사용해버린다면??
push 100 ; esp가 변경되게 된다
; 이러면 빌드에러가 되고 고정된 어떤 변수를 esp를 기억하게 하자
mov eax, dword ptr[esp+4]
add eax, dword ptr[esp+8]
ret
end
esp
레지스터의 값을 임시로 담고 있을 레지스터를 ebp
(Extened Base Pointer)레지스터라 한다
; ...
_add:
mov ebp, esp ; ebp에 esp값을 기억한다
push 100 ; 스택을 사용해도 초기 esp값을 기억하고 있음
mov eax, dword ptr[ebp+4] ; 이제 ebp를 사용하자
add eax, dword ptr[ebp+8]
ret
end
여기서 또 드는 의문점?
만약에 함수 호출전에 ebp를 쓰고있었다면??
함수 호출시 ebp값을 미리다른데로 옮겨두자
; ...
_add:
push ebp ; ebp값을 스택에 미리올려둔다(ebp가 사용중일지 모르기 때문)
mov ebp, esp
push 100
mov eax, dword ptr[ebp+4]
add eax, dword ptr[ebp+8]
pop ebp ; 기존값을 복원해 준다
ret
end
좀 정리하자면
_func:
; 함수 호출시 시작되는 구문
push ebp
mov ebp, esp
; ... (함수내용)
; 함수 호출 종료 시 구문
pop ebp
ret
end
위와 같은 호출코드를 Stack Frame이라 한다
지역변수는 어떻게 처리할까?
_func:
push ebp
mov ebp, esp
sub esp, 8 ; 지역변수 할당(esp에서 8만큼 뺀다를 기억)
move dwrod ptr[ebp-4], 100 ; 변수에 100 삽입
move dwrod ptr[ebp-8], 200 ; 변수에 200 삽입
pop ebp
ret
end
지역변수의 파괴는?
_func:
push ebp
mov ebp, esp
sub esp, 8
move dwrod ptr[ebp-4], 100
move dwrod ptr[ebp-8], 200
mov esp, ebp ; esp의 값을 ebp로 옮겨달라(지역변수를 없애달라)
pop ebp
ret
end
보통 함수의 구성은 이렇게 된다.
_func:
; 여기를 함수 Prologue라 한다
push ebp
mov ebp, esp
sub esp, [필요한 지역변수 크기]
; 함수 내부
; 여기를 함수 Epilogue라 한다
mov esp, ebp
pop ebp
ret
end
앞선 코드를 개선해보자
; stackframe.asm
.model flat
public _asm_main
.code
_asm_main:
; _add(1, 2)
push ebp
mov ebp, esp
; 지역변수는 없었기에 생략함
push 2
push 1
call _add
add esp, 8
mov esp, ebp
pop ebp
ret
x = -8
_add:
push ebp
mov ebp, esp
; add에서는 지역변수를 쓴다고 가정하자
sub esp, 8
mov dwrod ptr (-4)[ebp], 100 ; (-4)[ebp] == [ebp-4]와 동일한 표현
mov dwrod ptr x[ebp], 200 ; 이렇게 변수를 사용하여 표현해도 사용가능
mov eax, dword ptr[esp+4]
add eax, dword ptr[esp+8]
mov esp, ebp
pop ebp
ret
end
// sample.c
int add(int a, int b)
{
int c = 0;
int d = 0;
c = a + b;
return c;
}
int main()
{
int n = add(1, 2);
}
위 코드의 어셈블리를 예측해보자
; sample_asm.asm
.model flat
public _add
public _main
.code
_add:
; 함수 프롤로그
push ebp
mov ebp, esp
sub esp, 8 ; 지역변수 8바이트
mov dword ptr[ebp-4], 0
mov dword ptr[ebp-8], 0
; 연산과정
mov eax, dword ptr[ebp+8]
add eax, dword ptr[ebp+12]
mov dword ptr[ebp-4], eax
mov eax, dword ptr[ebp-4]
; 함수 애필로그
mov esp, ebp
pop ebp
ret
_main:
push ebp
mov ebp, esp
; sub esp, 4 ; 이렇게도 가능하지면 아무 레지스터값이나 스택에 하나 추가해도 동일함
push ecs ; 지역변수가 4바이트일 경우 이게 더 빠름.
push 2
push 1
call _add
add esp, 8
mov eax, dword ptr[ebp-4],
; mov eax, 0 ; return 0 의 표현인데 이거보다 아래가 빠르다
xor eax, eax ; return 0을 이렇게도 표현가능
mov esp, ebp
pop ebp
ret
end
과연 sample_asm.asm를 빌드하면 빌드가 될까?
-> Nope! main을 정의하지 않음
- 내가 만든 .asm파일의 main을 정의하는 방법
-
_mainCRTStartup
을 함수 추가
-
- /entry 옵션을 사용해 시작함수를 지정 :
$ ml sample_asm.asm /link /entry:main
- /entry 옵션을 사용해 시작함수를 지정 :
-