(Win32 : WindowsProgramming-5) Stack Frame

Posted by : at

Category : win32   WindowsProgramming


기존코드의 문제가 있다

// 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을 정의하는 방법
      1. _mainCRTStartup 을 함수 추가
      1. /entry 옵션을 사용해 시작함수를 지정 : $ ml sample_asm.asm /link /entry:main

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links