본 글은 코드누리의 Windows Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.
ecourse 온라인 강의 – S/W 교육의 새로운 시도
개강 예정 과정입니다. {"title":"\uac1c\uac15 \uc608\uc815 \uacfc\uc815\uc785\ub2c8\ub2e4.","show_title":"1","post_type":"course","taxonomy":"course-cat","term":"COMMINGSOON","post_ids":"","course_style":"popular","featured_style":"course","masonry
www.ecourse.co.kr
함수 인자 전달 방식 - 함수에 인자를 전달해야 한다면?
- 레지스터를 이용한 인자 전달 방식
- 스택을 이용한 인자 전달 방식
레지스터를 이용한 인자 전달 방식
.model flat
public _asm_main
.code
_asm_main:
mov ecx, 2
mov edx, 1
call _func
ret
_func:
mov eax, ecx
add eax, edx
ret
end
- 레지스터를 사용하기 때문에 속도가 빠르다.
- 하지만 레지스터 개수가 제한적이므로 인자 개수에 한계가 있다.
스택을 이용한 인자 전달 방식
.model flat
public _asm_main
.code
_asm_main:
push 2
push 1
call _func
ret
_func:
mov eax, dword ptr [esp + 4]
add eax, dword ptr [esp + 8]
ret
end
- asm_main에서 스택에 2와 1을 넣고 _func 함수에서 그 값을 사용하는 모습이다.
- 인자 개수에 제한이 없으나 복잡하다.
- 언뜻 보기에는 문제가 없어 보이는데 에러가 발생한다.
- 결과적으로 esp가 최초 asm_main을 호출한 C언어 쪽 코드로 돌아가기 위해서
- 스택에서 돌아갈 주소를 꺼내야 하는데
- asm_main에서 push한 1, 2가 스택에서 정리되지 않아서 esp가 이 인자들을 가리키고 있었음
- 이 인자들을 정리하기 위해서 add esp, 8을 추가한다. (스택은 낮은 주소로 자라므로)
.model flat
public _asm_main
.code
_asm_main:
push 2
push 1
call _func
add esp, 8 // esp 위치를 8만큼 올려서 esp가 돌아갈 주소를 가리키도록 함
ret
_func:
mov eax, dword ptr [esp + 4]
add eax, dword ptr [esp + 8]
ret
end
스택을 파괴하는 방법
- 호출자 파괴
- 피호출자 파괴
호출자 파괴
.model flat
public _asm_main
.code
_asm_main:
push 2
push 1
call _func
add esp, 8 // 함수 호출자가 스택을 정리(파괴)한다.
ret
_func:
mov eax, dword ptr [esp + 4]
add eax, dword ptr [esp + 8]
ret
end
- 위에서 했던 방식과 동일.
- 함수 호출자가 함수를 파괴한다.(caller)
- 함수가 많아질 경우 그만큼 코드가 많아지는 단점이 있다.
피호출자 파괴
.model flat
public _asm_main
.code
_asm_main:
push 2
push 1
call _func
ret
_func:
mov eax, dword ptr [esp + 4]
add eax, dword ptr [esp + 8]
ret 8 // 정리할 스택 크기만큼 적어준다
end
- ret 뒤에 정리할 스택의 크기만큼 적어주면 된다.
- 피호출자가 스택을 정리(파괴)한다.
C언어는 그러면 어떤 방식으로 스택을 파괴할까?
#include <stdio.h>
int asm_main();
int add(int a, int b) { return a + b; } // 실험용 C 함수
int main()
{
int n = asm_main();
int result = add(1, 2); // 실험용 C 함수 사용
printf("result : %d\n", n);
}
함수 호출 규약 | 어셈블리에서의 함수명 | 스택 파괴 방식 |
__cdecl | _f | 호출자 파괴 |
__stdcall | _f@인자크기 | 피호출자 파괴 |
__fastcall | @f@인자크기 | 피호출자 파괴 |
- __stdcall의 경우 WinAPI에서 주로 사용하며, 피호출자에서 스택을 정리할 수 있도록 함수명에 인자 크기를 같이 적어 보낸다.
- __fastcall의 경우 함수 인자가 2개라면 레지스터에 넣어 보내며, 3개 이상인 경우 스택을 사용한다.
- C언어에서는 별다른 언급이 없으면 __cdecl로 적용한다.
- 함수 리턴값과 함수 이름 사이에 함수 호출 규약을 적을 수 있다. ex) int __cdecl add(int a, int b) { ... }
- 가변인자 함수는 컴파일시점에 인자가 몇 개 올지 모르므로 함수 이름을 정할 수 없다.
- 그래서 가변인자 함수는 __stdcall이든 __fastcall이든 전부 __cdecl로 변환시킨다.
void __cdecl func1(int n, ...) { }
void __stdcall func2(int n, ...) { }
void __fastcall func3(int n, ...) { }
어셈블리 언어에서 c함수 호출하는 방법
#include <stdio.h>
int asm_main();
void __cdecl func1(int a, int b) { printf("func1"); }
void __stdcall func2(int a, int b) { printf("func2"); }
void __fastcall func3(int a, int b) { printf("func3"); }
void __fastcall func4(int a, int b, int c, int d) { printf("func4"); }
int main()
{
int n = asm_main(); // 어셈블리 파일에서 불러올 함수
printf("result : %d\n", n);
}
- 어셈블리에서 func1 ~ func4를 호출하려면 함수 호출규약에 따라 정확하게 호출해야 한다.
.model flat
public _asm_main
extern _func1: proc
extern _func2@8: proc
extern @func3@8: proc
extern @func4@16: proc
.code
_asm_main:
// func1
push 2
push 1
call _func1
add esp, 8
// func2
push 2
push 1
call _func2@8
// func3
mov edx, 2
mov ecx, 1
call @func3@8
// func4
push 4
push 3
mov edx, 2
mov ecx, 1
call @func4@16
ret
end
C 표준 함수와 WinAPI를 호출하는 방법
.model flat
public _asm_main
extern _func1: proc
extern _printf: proc
extern _MessageBoxA@16: proc
.data
S1 DB "Hello", 10, 0
.code
_asm_main:
//printf("Hello");
push offset S1
call _printf
add esp, 4
//MessageBoxA(0, "Hello", "Hello", 0);
push 0
push offset S1
push offset S1
push 0
call _MessageBoxA@16
ret
end
- 위와 마찬가지로 함수 호출규약에 유의하여 사용해야 한다
'운영체제 > [ecourse] Windows Programming' 카테고리의 다른 글
1-5. C++과 MASM (0) | 2022.09.05 |
---|---|
1-4. Stack Frame (0) | 2022.09.04 |
1-2. MASM 기본 문법 (0) | 2022.09.01 |
1-1-2. 어셈블리 빌드 방법 (0) | 2022.09.01 |
1-1-1. 인라인 어셈블리와 MASM (0) | 2022.09.01 |
댓글