[asm] 간단한 함수 호출

간단한 함수 호출이 어떻게 이루어지는지 어셈블리어로 해석해 본다. C를 사용하여 두 수를 더하는 함수를 다음과 같이 만들어 본다.

int AddNum(int a, int b)
{
  return a+b;
}



위와 같은 함수는 어셈블리어로 어떻게 번역될까.

push ebp
mov ebp, esp
mov eax, [ebp+8]
add eax, [ebp+12]
pop ebp
ret

기계어로 바꾸면

55
8B EC
8B 45 08
03 45 0C
5D
C3

위의 코드 중에서
push ebp
mov ebp, esp

pop ebp
ret

는 함수를 만들면 기본적으로 추가되는 코드이다. 따라서 inline assembly 를 사용할 때는 위의 코드는 필요가 없다. 위의 코드를 제외하고 inline asm 을 사용하면 다음과 같이 나타낼 수 있다.

int AddNum(int a, int b)
{
  __asm
  {
    mov eax, [ebp+8]
    add eax, [ebp+12]
  }
}





db(define byte) 명령어를 이용하여 기계어를 직접 입력하는 것도 가능하다.

int AddNum4(int a, int b)
{
  __asm
  {
     db 0x8B, 0x45, 0x08
     db 0x03, 0x45, 0x0C
  }
}




다음과 같이 해도 된다. 어짜피 메모리상에는 바이트 단위로 표현된다.

int AddNum(int a, int b)
{
  __asm
  {
    db 0x8B, 0x45, 0x08, 0x03, 0x45, 0x0C
  }
}




다음과 같이 AddNum 을 호출하였을 때 어떤일이 일어나는지 알아본다.

#include <stdio.h>
#include <conio.h>

int AddNum(int a, int b)
{
  return a+b;
}

int main(int argc, char* argv[])
{
  int sum;
  sum = AddNum(1,2);
  printf("%d\n",sum);
  getch();
  return 0;
}


C에서 인수는 스택을 통해서 호출되는 함수에 전달된다. 따라서 넘겨주는 값인 1 과 2 가 push 명령어를 통해서 스택에 넣게 된다. 그리고 함수를 호출하게 되는데, call 명령은 다음 실행할 주소를 스택에 넣고 함수를 호출한다. 그리고 호출이 끝나면 스택을 해제한다.

사용자 삽입 이미지

위 그림은 C++Builder6 의 Debug 화면이다. 왼쪽 아래는 메모리를 덤프한것이고 오른쪽 아래는 스택을 나타낸다. 메모리 덤프 화면을 보면 메모리의 00401175 번지에 8B 45 08 이라는 값이 있는 것을 알 수 있다. 화살표를 따라가보면, mov eax, [ebp+0x08] 을 기계어로 번역한 값인 8B 45 08 이 해당 번지에 있는 것을 알 수 있다. 그 뒤에 나오는 값인 03 45 0C 는 add eax, [ebp+0x0c] 를 기계어로 바꾼 값이다.

스택부분에 있는 4개의 값을 주목해 볼 필요가 있다. 스택 부분에서 왼쪽은 주소, 오른쪽이 값이다. AddNum 함수를 호출하기 위해서는 인수인 1, 2 를 스택에 push 해야 한다. 오른쪽부터 차례대로 push 해준다. 스택은 주소가 낮아지는 방향으로 자라므로 0012FF84 번지에 00000002 값이 있고, 4바이트 줄어든 주소인 0012FF80 번지에 00000001 이 있는 것을 알 수 있다.  push  를 하면 무조건 4바이트 씩 줄어든다.

주소
0012FF84 00000002
0012FF80 00000001
0012FF7C 0040118A
0012FF78 0012FF8C

두 개의 값을 push 한후 call 을 하게 되는데 함수를 호출하기 전에 복귀후 실행할 명령이 있는 주소인 0040118A 를 스택에 push 한다. 그 AddNum 을 호출한다. AddNum에서는 push ebp 를 해서 ebp 값을 push 한다.

이제, 스택에 위치한 값을 읽어서 계산을 해야 한다. 이 위치를 계산하기 위해서 ebp 를 이용한다.
mov eax, [ebp+0x08] 을 보면 ebp 에 0x08 을 더한 위치에서 4바이트를 읽어 eax 에 보내는 것을 알 수 있다.
add eax, [ebp+0x0c] 는 eax 에 있는 값인 00000001 과 ebp+0x0c 에 있는 값인 00000002 를 더해서 eax 에 보내는 명령이다. 스택의 구조를 보면 왜 0x08 0x0c 를 더해야 하는지 알 수 있다.

함수호출이 끝나고 복귀하는 부분의 코드는 다음과 같다.

add esp, 0x08

함수호출이 끝나면 인수로 넘겨줬던 값(0x00000001,0x00000002)은 더 이상 필요하지 않다. 따라서 스택의 위치를 재조정 해야 한다. 값을 지우는 대신, 스택 위치만 조정해서 다음에 스택을 사용할 때 위로 덮어쓰기가 되도록 한다. 값을 지우는 것은 0 으로 채우는 것으로, 명령을 주어야 한다. 그러나 그럴 필요 없이 다음에 덮어쓰기 할 수 있도록 자리만 비워 주면 된다. 이러한 위치조정을 esp 에 8을 더하는 것으로 대신하는 것이다. (스택은 위주소에서 아래주소로 자라므로 스택 위치를 위 주소로 올리면 그 자리는 비우는 것이 된다.)