절차적 함수 호출(Procedure Call) 지원 CPU 모델
Stack Frame 구조
함수 호출 과정에서 할당되는 메모리 블록을 스택 프레임이라고 한다.
함수가 return되면 해당 스코프 내의 지역변수에 접근할 수 없다. 만약 주소를 알고 있다 하더라도, 다른 호출 스택에 의해 덮힐 수 있기 때문에 매우 불안정한 값을 참조하게 된다.
sp레지스터
메모리 공간의 이름이 stack 메모리인 것은 자료구조 stack의 last in first out 특성 때문이다.
stack메모리는 데이터를 쌓거나 반환하기 위해 현재 어느 위치에서 데이터 연산을 할지 지정하는 포인터가 필요하다.
이를 sp 레지스터라고 한다.
x86에서는 esp, x64에서는 rsp라는 이름을 사용한다.

보는 것 처럼 함수에 진입하면서 스택 공간을 esp를 통해 확보하고 있다.
호출이 완료된 뒤에는 이전의 호출 스택으로 복귀하기 위한 기준이 필요하다. 이를 Frame Pointer 레지스터가 한다.
프레임 포인터(Frame Pointer) 레지스터
함수에 진입하면, 현재 스택 프레임의 기준을 정하기 위해 프레임 포인터(Frame Pointer) 레지스터를 사용한다.
이 프레임 포인터는 함수 호출 시 스택 포인터(ESP 또는 RSP) 값을 복사하여 설정되며, 이후 지역 변수 및 매개변수에 안정적으로 접근하는 기준점으로 사용된다.
또한 함수가 종료될 때 호출 이전 상태로 복귀하기 위해, 함수 진입 시 호출자의 프레임 포인터 값을 스택에 push하여 저장하고, 반환 시 이를 다시 pop하여 복구한다.
이를 통해 스택 프레임 간의 연속성과 추적 가능성을 유지할 수 있다.

현재 상황은 C()를 호출하고 C내에서 ebp를 push한 상황이다.
현재 ESP는 00AFFDAC인데, 이 위치에는 00affdc0 = EBP값이 들어있는 것을 볼 수 있다.
이는 함수를 빠져나오며 복구하는 것을 볼 수 있다. 그 아래는 f71090이 들어있는데 이는 코드의 흐름으로 복구할 EIP 값이다.
함수 호출 인자에 의한 실행의 이동
다시 살펴보는 메모리 구조와 프로그램 카운터
코드 영역에는 프로그램이 실행할 코드들이 들어있다.
코드의 실행흐름을 저장하기 위해 pc reigster를 사용한다. (x86-64에서는 각각 eip, rip)
CPU Cycle을 돌면서 Fetch 연산 시에 자동적으로 pc 값이 증가한다.
함수 호출과 함수 종료
함수를 호출하면 call하는 함수의 주소에 따라 실행 흐름이 이동한다.
그리고 함수를 call하면서 stack에 pc레지스터 값이 백업되어 함수가 return(종료)될 때 이를 활용해 복구한다.
함수 호출 시 인자는 stack에 push한다. 그리고 호출한 함수 내의 frame pointer를 기준으로 접근하여 이를 사용한다.
함수 호출 규약(Calling Convention)
함수에 인자를 전달하는 방법과, 스택 프레임을 반환하는 방법에는 기본적으로 두 가지가 존재한다.
이러한 방법을 약속해 놓은 것을 함수 호출 규약이라고 한다.
https://learn.microsoft.com/ko-kr/cpp/cpp/argument-passing-and-naming-conventions?view=msvc-170
인수 전달 및 명명 규칙
자세한 정보: 인수 전달 및 명명 규칙
learn.microsoft.com
32bit OS의 경우 아래의 __cdecl과, __stdcall이 대표적이다.
| 항목 | __cdecl | __stdcall |
| 스택 정리 주체 | 호출자(caller) | 피호출자(callee) |
| 인자 전달 순서 | 오른쪽 → 왼쪽 (Right to Left) | 오른쪽 → 왼쪽 (Right to Left) |
| 함수 이름 변환 (Name Mangling) | _함수명 (언더스코어 접두사) | _함수명@인자크기 (언더스코어 + @ + 바이트 수) |
| 가변 인자 함수 지원 | 지원 (e.g., printf) | 지원하지 않음 |
| 기본 규약 | C/C++의 기본 규약 (MSVC 기준) | Windows API 함수에 주로 사용됨 |
| 호환성 | 유연함, C 라이브러리에서 주로 사용 | 주로 Windows 시스템 콜 또는 WinAPI |
| 스택 사용 방식 | 호출자가 정리하므로 동일 함수 여러 번 호출 시 코드 중복 발생 | 피호출자가 정리하므로 오버헤드 감소 |
void __stdcall B(int a, int b)
{
00701020 55 push ebp
00701021 8B EC mov ebp,esp
00701023 83 EC 10 sub esp,10h
int _a = a, _b = b, c = 0, d = 0;
00701026 8B 45 08 mov eax,dword ptr [a]
00701029 89 45 F8 mov dword ptr [_a],eax
0070102C 8B 4D 0C mov ecx,dword ptr [b]
0070102F 89 4D FC mov dword ptr [_b],ecx
00701032 C7 45 F4 00 00 00 00 mov dword ptr [c],0
00701039 C7 45 F0 00 00 00 00 mov dword ptr [d],0
A(_a, _b);
00701040 8B 55 FC mov edx,dword ptr [_b]
00701043 52 push edx
00701044 8B 45 F8 mov eax,dword ptr [_a]
00701047 50 push eax
00701048 E8 B3 FF FF FF call A (0701000h)
0070104D 90 nop
}
0070104E 8B E5 mov esp,ebp
00701050 5D pop ebp
00701051 C2 08 00 ret 8
void __cdecl C(int a, int b)
{
00701060 55 push ebp
00701061 8B EC mov ebp,esp
00701063 83 EC 08 sub esp,8
int _a = a, _b = b;
00701066 8B 45 08 mov eax,dword ptr [a]
00701069 89 45 F8 mov dword ptr [_a],eax
0070106C 8B 4D 0C mov ecx,dword ptr [b]
0070106F 89 4D FC mov dword ptr [_b],ecx
B(_a, _b);
00701072 8B 55 FC mov edx,dword ptr [_b]
00701075 52 push edx
00701076 8B 45 F8 mov eax,dword ptr [_a]
00701079 50 push eax
0070107A E8 A1 FF FF FF call B (0701020h)
0070107F 90 nop
}
00701080 8B E5 mov esp,ebp
00701082 5D pop ebp
00701083 C3 ret
C(a, b);
007010D0 8B 45 FC mov eax,dword ptr [b]
007010D3 50 push eax
007010D4 8B 4D F8 mov ecx,dword ptr [a]
007010D7 51 push ecx
007010D8 E8 83 FF FF FF call C (0701060h)
007010DD 83 C4 08 add esp,8
return 0;
007010E0 33 C0 xor eax,eax
// main
보는 것 처럼 __stdcall은 00701071 줄에서 ret 8을 통해 피호출자에서 스택프레임을 정리하고,
__cdecl은 007010DD줄에서 add esp 8을 통해 호출자에서 스택프레임을 정리하고 있음을 확인할 수 있다.
https://learn.microsoft.com/ko-kr/cpp/build/x64-calling-convention?view=msvc-170
x64 호출 규칙
기본 x64 호출 규칙의 세부 정보에 대해 알아봅니다.
learn.microsoft.com
x64는 보다 복잡하다.
'C, C++ > 뇌를 자극하는 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
| [뇌를 자극하는 윈도우즈 시스템 프로그래밍] 12장 쓰레드의 생성과 소멸 (1) | 2025.07.07 |
|---|---|
| [뇌를 자극하는 윈도우즈 시스템 프로그래밍] 11장 쓰레드의 이해 (0) | 2025.06.10 |
| [뇌를 자극하는 윈도우즈 시스템 프로그래밍] 9장 스케줄링 알고리즘과 우선순위 (2) | 2025.06.08 |
| [뇌를 자극하는 윈도우즈 시스템 프로그래밍] 8장 IPC 2 (0) | 2025.06.08 |
| [뇌를 자극하는 윈도우즈 시스템 프로그래밍] 6장 - 커널 오브젝트와 오브젝트 핸들 (0) | 2025.05.20 |