본문 바로가기
운영체제/[ecourse] Windows Programming

1-3. Calling Convention

by 헛둘이 2022. 9. 3.

본 글은 코드누리의 Windows Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.

https://www.ecourse.co.kr/

 

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

 

 

 

함수 인자 전달 방식 - 함수에 인자를 전달해야 한다면?
  1. 레지스터를 이용한 인자 전달 방식
  2. 스택을 이용한 인자 전달 방식

 


레지스터를 이용한 인자 전달 방식

 

.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

 

 

 

 


스택을 파괴하는 방법
  1. 호출자 파괴
  2. 피호출자 파괴

 

 

 

 

 


호출자 파괴

 

.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);
}

4번째 줄에 add esp,8로 호출자가 스택을 정리하는 게 보인다

함수 호출 규약 어셈블리에서의 함수명 스택 파괴 방식
__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, ...) { }

함수 이름이 _func..로 되어 있는걸 볼 수 있다.

 

 

 

 

 

 


어셈블리 언어에서 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

댓글