간단한 함수 호출이 어떻게 이루어지는지 어셈블리어로 해석해 본다. C를 사용하여 두 수를 더하는 함수를 다음과 같이 만들어 본다.
위와 같은 함수는 어셈블리어로 어떻게 번역될까.
기계어로 바꾸면
위의 코드 중에서
는 함수를 만들면 기본적으로 추가되는 코드이다. 따라서 inline assembly 를 사용할 때는 위의 코드는 필요가 없다. 위의 코드를 제외하고 inline asm 을 사용하면 다음과 같이 나타낼 수 있다.
db(define byte) 명령어를 이용하여 기계어를 직접 입력하는 것도 가능하다.
다음과 같이 해도 된다. 어짜피 메모리상에는 바이트 단위로 표현된다.
다음과 같이 AddNum 을 호출하였을 때 어떤일이 일어나는지 알아본다.
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바이트 씩 줄어든다.
두 개의 값을 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 를 더해야 하는지 알 수 있다.
함수호출이 끝나고 복귀하는 부분의 코드는 다음과 같다.
함수호출이 끝나면 인수로 넘겨줬던 값(0x00000001,0x00000002)은 더 이상 필요하지 않다. 따라서 스택의 위치를 재조정 해야 한다. 값을 지우는 대신, 스택 위치만 조정해서 다음에 스택을 사용할 때 위로 덮어쓰기가 되도록 한다. 값을 지우는 것은 0 으로 채우는 것으로, 명령을 주어야 한다. 그러나 그럴 필요 없이 다음에 덮어쓰기 할 수 있도록 자리만 비워 주면 된다. 이러한 위치조정을 esp 에 8을 더하는 것으로 대신하는 것이다. (스택은 위주소에서 아래주소로 자라므로 스택 위치를 위 주소로 올리면 그 자리는 비우는 것이 된다.)
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
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
8B EC
8B 45 08
03 45 0C
5D
C3
위의 코드 중에서
push ebp
mov ebp, esp
pop ebp
ret
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을 더하는 것으로 대신하는 것이다. (스택은 위주소에서 아래주소로 자라므로 스택 위치를 위 주소로 올리면 그 자리는 비우는 것이 된다.)
'기술탐구' 카테고리의 다른 글
[전자기학] 전계, 전기장 (Electric Field) 의 뜻. (115) | 2009.04.12 |
---|---|
[전자기학] 전기장에서 유전율(permittvity)의 뜻. (244) | 2008.10.30 |
[asm] IA-32 PUSH, POP 명령어 해석 (4) | 2008.09.08 |
[asm] IA-32 MOV 명령어 해석 [3] (0) | 2008.09.07 |
[asm] IA-32 MOV 명령어 해석 [2] (0) | 2008.09.07 |