sonumb

Calling Convention 본문

개발자 이야기/C/C++

Calling Convention

sonumb 2008. 2. 19. 14:56

작성자 : sonumb (http://sonumb.tistory.com)

E-mail: a

작성일 : 2008년 2월 19일

Contents

  • 0. 서론
  • 1. 컴파일러 세팅
  • 2. 소스파일 
  • 3. 빌드 결과
  • 4. 결론
  • 5. 관련링크 & 참고서적  

0. 서론 [contents]

목적: 함수 호출 시  메모리 변화(구성되는 Stack Frame)와 메모리 해제에 대한 설명.

용어 :
Stack Frame : 함수 호출시 구성되는 스택의 형태 , 보통 실인수(argument value), 리턴 주소, 지역변수로 구성됩니다. 

1. 컴파일러 세팅 [contents]

Release 모드로 맞춰놓고 C/C++의 Optimization 기능을 Disabled.
C/C++의 Output Files의 Assembler output을 Assembly With Source Code (/FAs)로 맞춤.

2. 소스파일 [contents]

   1: #include <stdio.h>
   2: void __cdecl    f1( int a, int b );
   3: void __stdcall  f2( int a, int b );
   4: void __fastcall f3( int a, int b );
   5: /*
   6: * __declspec(naked) f4( void );  // Proto type Declaration 과 Definition 부의 분리 불가!
   7: * // error C2488: 'f4' : 'naked' can only be applied to non-member function definitions    
   8: */
   9: __declspec(naked) /*int */ f4( int c, int d )
  10: {
  11:     int a; //naked 함수내에서 지역변수의 초기화는 당연히 안된다! 이유는 17번째줄코드
  12:     int b; 
  13:  
  14:     __asm{
  15:         push ebp
  16:         mov ebp, esp
  17:         sub esp, __LOCAL_SIZE ; 실질적인 로컬변수 공간할당
  18:         ; '__LOCAL_SIZE''naked' 특성이 있는 함수에만 사용가능.
  19:         ; 모든 로컬 변수와 컴파일러가 생성한 임시 변수들의 총 바이트 
  20:     }
  21:     a=2; // 실질적인 초기화
  22:     b=3;
  23:     __asm{
  24:         mov a, __LOCAL_SIZE
  25:         mov eax, a ; 'return value' 저장.
  26:         mov esp, ebp
  27:         pop ebp
  28:         ret 
  29:     }
  30: }
  31: int main( void )
  32: {
  33:     volatile int a=3, b=2;
  34:     f1(a,b);
  35:     f2(a,b);
  36:     f3(a,b);
  37:     a = f4(a,b);
  38:     
  39:     
  40:     printf("main - a : %d\n" , a);
  41:  
  42:     return a;
  43: }
  44: void __cdecl f1( int a, int b )
  45: {
  46:     volatile int c = 0, d=0 , e=0;
  47:     c = a + b;
  48: }
  49: void __stdcall f2( int a, int b )
  50: {
  51:     volatile int c = 0;
  52:     c = a +b;
  53: }
  54: void __fastcall f3( int a, int b )
  55: {
  56:     volatile int c = 0;
  57:     a = a + b;
  58: }
일단 f1 함수는 cdecl이고 f2는 stdcal,l f3는 fastcall, f4는 naked 함수입니다.
일단 naked 외에 3가지 함수는 별 차이점 없어 보입니다..

naked

naked[각주:1]를 일단 설명하고자 합니다.
naked의 뜻은 `벗겨진`, `발가벗은`이죠..일본 AV를 먼저 떠올리지 마시고 -_-;;;

특징:
calling convention을 프로그래머가 직접 만든다.
인자의 전달 및 인자 해제는 caller가 책임진다. 
함수의 프로토타임 `Declaration`, `definition` 분리가 불가. `정의`만 있어야 한다.
return type의 선언해도 되고 안해도 되고,,

자 위의 코드를 봅시다.
9-30 line을 통해 `정의`를 하고 있습니다. 함수리턴타입을 주석으로 처리 해놨는데요. main함수부에서 호출하여 인자로 받고 있는데도, 위의 코드는 아무 탈 없이 잘 돌아갑니다.
또 보시면 내부의 __LOCAL_SIZE란 심벌을 쓰고 있는데요, 컴파일러가 정의한 상수 입니다. 보통 모든 Local Variable 변수의 크기라고 생각하시면 됩니다.

3. 빌드 결과[contents]

위 소스로 부터 생성된 어셈블리코드
   1: ; Listing generated by Microsoft (R) Optimizing Compiler Version 14.00.50727.762 
   2:  
   3:     TITLE    e:\src\test_call_convetion\test_call_convetion\test.c
   4:     .686P
   5:     .XMM
   6:     include listing.inc
   7:     .model    flat
   8:  
   9: INCLUDELIB OLDNAMES
  10:  
  11: EXTRN    @__security_check_cookie@4:PROC
  12: EXTRN    __imp__printf:PROC
  13: $SG-5    DB    'main - a : %d', 0aH, 00H
  14: PUBLIC    @f3@8
  15: ; Function compile flags: /Odtp
  16: ; File e:\src\test_call_convetion\test_call_convetion\test.c
  17: _TEXT    SEGMENT
  18: _b$ = -12                        ; size = 4
  19: _a$ = -8                        ; size = 4
  20: _c$ = -4                        ; size = 4
  21: @f3@8    PROC
  22: ; _a$ = ecx
  23: ; _b$ = edx
  24:  
  25: ; 55   : {
  26:  
  27:     push    ebp
  28:     mov    ebp, esp
  29:     sub    esp, 12                    ; 0000000cH
  30:     mov    DWORD PTR _b$[ebp], edx
  31:     mov    DWORD PTR _a$[ebp], ecx
  32:  
  33: ; 56   :     volatile int c = 0;
  34:  
  35:     mov    DWORD PTR _c$[ebp], 0
  36:  
  37: ; 57   :     a = a + b;
  38:  
  39:     mov    eax, DWORD PTR _a$[ebp]
  40:     add    eax, DWORD PTR _b$[ebp]
  41:     mov    DWORD PTR _a$[ebp], eax
  42:  
  43: ; 58   : }
  44:  
  45:     mov    esp, ebp
  46:     pop    ebp
  47:     ret    0
  48: @f3@8    ENDP
  49: _TEXT    ENDS
  50: PUBLIC    _f2@8
  51: ; Function compile flags: /Odtp
  52: _TEXT    SEGMENT
  53: _c$ = -4                        ; size = 4
  54: _a$ = 8                            ; size = 4
  55: _b$ = 12                        ; size = 4
  56: _f2@8    PROC
  57:  
  58: ; 50   : {
  59:  
  60:     push    ebp
  61:     mov    ebp, esp
  62:     push    ecx
  63:  
  64: ; 51   :     volatile int c = 0;
  65:  
  66:     mov    DWORD PTR _c$[ebp], 0
  67:  
  68: ; 52   :     c = a +b;
  69:  
  70:     mov    eax, DWORD PTR _a$[ebp]
  71:     add    eax, DWORD PTR _b$[ebp]
  72:     mov    DWORD PTR _c$[ebp], eax
  73:  
  74: ; 53   : }
  75:  
  76:     mov    esp, ebp
  77:     pop    ebp
  78:     ret    8
  79: _f2@8    ENDP
  80: _TEXT    ENDS
  81: PUBLIC    _f1
  82: ; Function compile flags: /Odtp
  83: _TEXT    SEGMENT
  84: _e$ = -12                        ; size = 4
  85: _c$ = -8                        ; size = 4
  86: _d$ = -4                        ; size = 4
  87: _a$ = 8                            ; size = 4
  88: _b$ = 12                        ; size = 4
  89: _f1    PROC
  90:  
  91: ; 45   : {
  92:  
  93:     push    ebp
  94:     mov    ebp, esp
  95:     sub    esp, 12                    ; 0000000cH
  96:  
  97: ; 46   :     volatile int c = 0, d=0 , e=0;
  98:  
  99:     mov    DWORD PTR _c$[ebp], 0
 100:     mov    DWORD PTR _d$[ebp], 0
 101:     mov    DWORD PTR _e$[ebp], 0
 102:  
 103: ; 47   :     c = a + b;
 104:  
 105:     mov    eax, DWORD PTR _a$[ebp]
 106:     add    eax, DWORD PTR _b$[ebp]
 107:     mov    DWORD PTR _c$[ebp], eax
 108:  
 109: ; 48   : }
 110:  
 111:     mov    esp, ebp
 112:     pop    ebp
 113:     ret    0
 114: _f1    ENDP
 115: _TEXT    ENDS
 116: PUBLIC    _f4
 117: ; Function compile flags: /Odtp
 118: _TEXT    SEGMENT
 119: _b$ = -8                        ; size = 4
 120: _a$ = -4                        ; size = 4
 121: _c$ = 8                            ; size = 4
 122: _d$ = 12                        ; size = 4
 123: _f4    PROC
 124:  
 125: ; 11   :     int a; //naked 함수내에서 지역변수의 초기화는 당연히 안된다!
 126: ; 12   :     int b;
 127: ; 13   : 
 128: ; 14   :     __asm{
 129: ; 15   :         push ebp
 130:  
 131:     push    ebp
 132:  
 133: ; 16   :         mov ebp, esp
 134:  
 135:     mov    ebp, esp
 136:  
 137: ; 17   :         sub esp, __LOCAL_SIZE ; 실질적인 로컬변수 공간할당
 138:  
 139:     sub    esp, 8
 140:  
 141: ; 18   :         ; '__LOCAL_SIZE''naked' 특성이 있는 함수에만 사용가능.
 142: ; 19   :         ; 모든 로컬 변수와 컴파일러가 생성한 임시 변수들의 총 바이트 
 143: ; 20   :     }
 144: ; 21   :     a=2; // 실질적인 초기화
 145:  
 146:     mov    DWORD PTR _a$[ebp], 2
 147:  
 148: ; 22   :     b=3;
 149:  
 150:     mov    DWORD PTR _b$[ebp], 3
 151:  
 152: ; 23   :     __asm{
 153: ; 24   :         mov a, __LOCAL_SIZE
 154:  
 155:     mov    DWORD PTR _a$[ebp], 8
 156:  
 157: ; 25   :         mov eax, a ; 'return value' 저장.
 158:  
 159:     mov    eax, DWORD PTR _a$[ebp]
 160:  
 161: ; 26   :         mov esp, ebp
 162:  
 163:     mov    esp, ebp
 164:  
 165: ; 27   :         pop ebp
 166:  
 167:     pop    ebp
 168:  
 169: ; 28   :         ret 
 170:  
 171:     ret    0
 172: _f4    ENDP
 173: _TEXT    ENDS
 174: PUBLIC    _main
 175: ; Function compile flags: /Odtp
 176: _TEXT    SEGMENT
 177: _b$ = -8                        ; size = 4
 178: _a$ = -4                        ; size = 4
 179: _main    PROC
 180:  
 181: ; 32   : {
 182:  
 183:     push    ebp
 184:     mov    ebp, esp
 185:     sub    esp, 8
 186:  
 187: ; 33   :     volatile int a=3, b=2;
 188:  
 189:     mov    DWORD PTR _a$[ebp], 3
 190:     mov    DWORD PTR _b$[ebp], 2
 191:  
 192: ; 34   :     f1(a,b);
 193:  
 194:     mov    eax, DWORD PTR _b$[ebp]
 195:     push    eax
 196:     mov    ecx, DWORD PTR _a$[ebp]
 197:     push    ecx
 198:     call    _f1
 199:     add    esp, 8
 200:  
 201: ; 35   :     f2(a,b);
 202:  
 203:     mov    edx, DWORD PTR _b$[ebp]
 204:     push    edx
 205:     mov    eax, DWORD PTR _a$[ebp]
 206:     push    eax
 207:     call    _f2@8
 208:  
 209: ; 36   :     f3(a,b);
 210:  
 211:     mov    edx, DWORD PTR _b$[ebp]
 212:     mov    ecx, DWORD PTR _a$[ebp]
 213:     call    @f3@8
 214:  
 215: ; 37   :     a = f4(a,b);
 216:  
 217:     mov    ecx, DWORD PTR _b$[ebp]
 218:     push    ecx
 219:     mov    edx, DWORD PTR _a$[ebp]
 220:     push    edx
 221:     call    _f4
 222:     add    esp, 8
 223:     mov    DWORD PTR _a$[ebp], eax
 224:  
 225: ; 38   :     
 226: ; 39   :     
 227: ; 40   :     printf("main - a : %d\n" , a);
 228:  
 229:     mov    eax, DWORD PTR _a$[ebp]
 230:     push    eax
 231:     push    OFFSET $SG-5
 232:     call    DWORD PTR __imp__printf
 233:     add    esp, 8
 234:  
 235: ; 41   : 
 236: ; 42   :     return a;
 237:  
 238:     mov    eax, DWORD PTR _a$[ebp]
 239:  
 240: ; 43   : }
 241:  
 242:     mov    esp, ebp
 243:     pop    ebp
 244:     ret    0
 245: _main    ENDP
 246: _TEXT    ENDS
 247: END
일단 분석 해봅시다.
main 함수를 먼저 봅시다.
f1, 2, 3, 4를 호출한후 printf문을 호출 하고 있습니다.
 
f1을 보도록하죠. 192 line을 보면  함수 인자를 b를 먼저, 그 다음 a를 스택에 넣고 호출하고 있습니다.
그 다음
199: add esp, 8
을 통해 스택을 풀어주고 있네요.
 
f2 및 호출 당한쪽 (callee)에서 해제 하고 있네요.
78: ret 8
// add esp, 8
// ret
// 과 같은 코드 입니다.
f3는 인자에 대한 스택을 쌓지도 않았으니 풀어 줄필요도 없군요!!
 
printf는 가변인자함수입니다. 따라서 인자의 개수는 항상 변합니다. 그러면 이건 어떤 호출규약이 있어야 하죠?
인자의 개수는 정해지지 않으므로, __stdcall과 같이 함수 내부에 stack을 풀어주는 머신인스트럭션이 컴파일되어 들어가지 못합니다.( 인자 개수를 어떻게 알고 감히 들어갈 생각을!)
그러면 당연히 호출하는 쪽(caller)에서 인자의 개수가 정해집니다. 당연히  개수정보를 가지는 쪽은 caller이고, 해제 해야하는 정보도 컴파일해서 머신인스트럭션을 넣을 수 있겠죠.
printf와 같은 가변인자 함수들은 cdecl이나 naked 함수가 되어야 겠죠?!
 
그러면 Windows API 함수를 보면 WINAPI라던가 CALLBACK 같은 매크로를 보게 됩니다.
보통 핸들러같은 callback 함수와 Thread들은 동적(!)으로 호출이되어 실행됩니다. 그러면 호출 시 인자 전달후 인자 처리까지 caller가 신경을 써주지 못하게 됩니다.
그래서 callee가 보통 인자 처리를 담당해야 하기에 위의 매크로들은 __stdcall로 정의가 되어있을 겁니다. 

4. 결론 [contents]

__cdecl: 일반적인 C함수에서 쓰인다. 호출한쪽(caller)에서 전달한한 인자에 대한 처리(unwired)를 합니다.
__stdcall : callback 함수나 handler, Thread에서 쓰인다. 호출 당한쪽(callee)이 전달 받은 인자를 처리합니다.
__fastcall: ecx, edx 를 각각 첫번째 인자, 두번째 인자로 쓴다. 그러면 전달 받은 인자 처리에 대해 전혀 신경쓰지 않아도 됩니다.
naked: 보통 __declspec(naked) 이렇게 씁니다. 함수 스택프레임의 구성과 해제를 프로그래머가 직접해주는 규약


5.관련링크 & 참고서적 [contents]

Windows 구조와 원리 (한빛출판사) - 정덕영 

반응형