예전 글을 보니 역시 나도 보면서 정리가 깔끔하지 못했습니다.
다시 정리해서 글을 올려봅니다.
!! 저도 초보라서 틀릴수 있습니다. 고수분들 중에 문서에 틀린부분이 있으면 지적해 주세요 !!
예제)
int sum(int a, int b)
{
int sum;
sum=a+b;
return sum;
}
int main()
{
int a, b;
int result;
a=10;
b=5;
result=sum(a, b);
printf("result = %d\n", result);
return 0;
}
간단한 프로그램 입니다.
이걸 disasmble 해 보겠습니다.
저 같은 경우는 disasmble 할때 objdump 를 이용하여 파일을 추출한걸 열고
gdb로 디버깅하여 서로 비교해 가면서 합니다.
gcc -g -o sum sum.c <-- 컴파일 하고
objdump -S sum > sum.txt <-- 바이너리파일에서 어셈코드를 추출합니다.
vi sum.txt <--- 열어봅니다.(....)
gdb sum <-- gdb 로 디버깅 합니다.
프로그램이 실행되면 메모리에 올라갑니다.
밑에 그림은 프로세스(프로그램 실행상태)가 메모리에 저장된 구조 입니다.
위 그림에서 하나씩 살펴 보겠습니다.
# Argument of environnement 세그먼트(쉽게 말해 부분 , 영역 이라고 보시면 됩니다.)
여기에 들어가는 자료는 인자값과 환경변수입니다.
인자값은 int main(int argc, char *argv[]) <--- argc, argv 입니다.
환경변수는 일종의 설정 파일입니다.
도스를 사용하신 분들은 쉽게 이해 하실텐데 autobat <--- 이 파일 아시죠
처음 실행시 배치 파일입니다.
리눅스 콘솔에서 env 치시면 설정된 환경변수값들이 나옵니다.
자세한 설명은 "리눅스 환경변수" 검색해 보시길...
# stack 세그먼트
여기서 가장 중요한 부분이죠
스택은 지역변수가 저장되는 영역입니다.
변수가 증가하면 스택이 커지고 변수가 없어지면 스택은 다시 작아집니다.
커졌다 작아졌다 하는거죠
위 그림에서 보듯이 스택이 자라는 방향은 밑으로 자랍니다.
이건 disasm 하면서 자세히 설명하겠습니다.
# heap 세그먼트
동적할당 영역입니다.
c언어에 malloc을 사용하면 여기에 할당이 됩니다.
스택과 달리 사용자가 할당하면 사용자가 해제를 시켜야 합니다.
프로그램이 종료될때까지 해제되지 않습니다.
뭐 이건 c언어를 해보신 분들이면 특별히 설명할게 없네요
# BSS 세그먼트
초기화 되지 않은 전역변수가 여기에 할당됩니다.
# DATA 세그먼트
초기화 된 전역변수나 static 변수가 할당됩니다.
static 변수는 값을 주지 않아도 자동으로 초기화 되죠?
그러니 data 세그먼트에 들어가는 겁니다.
BSS 세그먼트 와 DATA 는 정의된 값은 컴파일때 값이 들어가 있습니다
# CODE 세그먼트
다른 책들중에 TEXT 세그먼트 로 불리는 경우도 있습니다. 같은 말입니다.
소스 코드가 들어가 있습니다.
실제 소스 코드가 들어가 있는게 아니라 컴파일된 소스 코드입니다.
즉 2진수라는 말이죠 예를 들어
a=10; ---> 0110 0110 1101 제가 그냥 숫자 아무 숫자나 넣은 겁니다. ;;
프로그램 실행시 code 주소를 읽어 cpu는 해석하는 겁니다.
자 이제 코드를 분석해 보겠습니다.
gcc 버전에 따라 어셈블러가 다르게 표현될수 있습니다.
제껀 gcc 4.3.2 최신버전..ㅡㅡ
main 함수부터..
/************************************************************/
080483da <main>:
int main()
{
80483da: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483de: 83 e4 f0 and $0xfffffff0,%esp
80483e1: ff 71 fc pushl -0x4(%ecx)
80483e4: 55 push %ebp
80483e5: 89 e5 mov %esp,%ebp
80483e7: 51 push %ecx
80483e8: 83 ec 24 sub $0x24,%esp
int a, b;
int result;
a=10;
80483eb: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%ebp)
b=5;
80483f2: c7 45 f4 05 00 00 00 movl $0x5,-0xc(%ebp)
result=sum(a, b);
80483f9: 8b 45 f4 mov -0xc(%ebp),%eax
80483fc: 89 44 24 04 mov %eax,0x4(%esp)
8048400: 8b 45 f8 mov -0x8(%ebp),%eax
8048403: 89 04 24 mov %eax,(%esp)
8048406: e8 b9 ff ff ff call 80483c4 <sum>
804840b: 89 45 f0 mov %eax,-0x10(%ebp)
printf("%d\n", result);
804840e: 8b 45 f0 mov -0x10(%ebp),%eax
8048411: 89 44 24 04 mov %eax,0x4(%esp)
8048415: c7 04 24 f0 84 04 08 movl $0x80484f0,(%esp)
804841c: e8 d7 fe ff ff call 80482f8 <printf@plt>
return 0;
8048421: b8 00 00 00 00 mov $0x0,%eax
/************************************************************/
코드가 꽤 길어 보이는데 이해하는데 어려움이 없을 겁니다.
하나씩 분석해 봅니다.
080483da <main>: <-- main 의 시작 주소 입니다.
0x80483da <- 이부분은 코드가 저장된 주소값입니다.
위에 메모리 구조중에 0x80483da 는 CODE 세그먼트에 저장된 영역입니다.
프로그램이 실행되면서 CODE 세그먼트값을 읽습니다. 순서대로 읽죠
gdb 에서 info r 실행하면
%eip 라는 레지스터가 있습니다.
eip 레지스터에 들어있는 값이 CODE 세그먼트에 저장되어 있는 다음 실행 주소값입니다.
예를 들어
80483da: 8d 4c 24 04 lea 0x4(%esp),%ecx <--- 현재 여기를 실행하고 있음
80483de: 83 e4 f0 and $0xfffffff0,%esp
위의 상태라면 eip 값은 그 다음 주소값인 80483de 값을 가지고 있습니다.
cpu가 위의 문장을 처리하면 eip가 그 다음 주소를 가지고 있기 때문에 다음에 무엇을
실행해야 되는지 알 수 있습니다.
참고로 eip 값은 사용자가 마음대로 바꿀수 없습니다.
왜냐면 eip 주소를 바꾸면 프로그램 실행이 엉뚱한데로 이동하겠죠?
(해킹에 관심있는 분들은 아마 이쪽에 대해 잘 아실겁니다.)
80483da: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483de: 83 e4 f0 and $0xfffffff0,%esp
80483e1: ff 71 fc pushl -0x4(%ecx)
사실 이부분이 gcc 버전에 따라 차이가 많이 났습니다.
main 초기화 부분입니다.
그냥 넘어가도 됩니다. ( 저도 잘 몰라서.... ㅡㅡ)
80483e4: 55 push %ebp
80483e5: 89 e5 mov %esp,%ebp
80483e7: 51 push %ecx
80483e8: 83 ec 24 sub $0x24,%esp
int a, b;
int result;
드디어 본격적으로 분석 시작이죠
80483e4: 55 push %ebp
80483e5: 89 e5 mov %esp,%ebp
함수 프롤로그라고 부릅니다.
함수가 시작하면 위에 있는 코드가 가장 먼저 시작한다는 말입니다.
main도 엄연히 함수입니다.
위의 두 문장은 스택을 이해하고 있어야 가능합니다.
여기서 ebp 레지스터와 esp 레지스터가 있습니다.
esp 레지스터는 스택의 맨끝에 위치하는 레지스터입니다.
예를 들어 현재 스택의 위치는 0x100 번지입니다. 여기에 20 의 값이 저장되어 있습니다.
push %ebp <-- ebp 는 스택의 기준을 가리키고 있습니다. ( 말이 이상한데...)
push <-- 스택에 값을 넣습니다. 여기서 값이란 ebp 값을 말하는 겁니다.
현재 ebp 값이 30 이 들어있으면
0x100 | 20 |
|---------------------- |
0x9c | 30 | <------------------ ESP
| |
요렇게 됩니다. 즉 esp에는 0x9c가 저장되어 있겠죠?
처음에 제가 스택은 밑으로 증가된다고 말했습니다.
그리고 push를 하면 뒤에 레지스터 크기에 맞춰 값이 감소합니다.
ebp 는 레지스터 크기가 32bit 입니다 즉 4바이트 인거죠
그래서 -4바이트 감소되는 것입니다.
실제 gdb로 보면
(push %ebp 실행 전)
(gdb) p $ebp
$1 = (void *) 0xbf808558
(gdb) p $esp
$2 = (void *) 0xbf8084ec
(push %ebp 실행 후)
(gdb) p $esp
$4 = (void *) 0xbf8084e8 <--- -4 만큼 감소 했습니다.
(gdb) x $esp
0xbf8084e8: 0xbf808558 <--- ebp 값도 저장되어 있네요
잠깐 쉬었다 다시 올리겠습니다.