본문 바로가기

dis & asm

disasmble 기초 정리 (2)


눈이 너무 피곤하네여~ 밖에 잠깐 나갔다 왔는데 벌써 벚꽃이 피었습니다.
흠... 근데 난 집에 짱박혀서 뭐 하는 건지... ㅋㅋ


전 문서에서 계속 이어 갑니다..

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;


80483e7:   51                      push   %ecx

ecx 레지스터를 스택에 넣었습니다.  이게 뭘까여?
gdb 로 확인해 보면

(gdb) x $ecx
0xbfdbf2b0:    0x00000001  <--- 1값이 들어있네요

여기서 감이 오신분들도 있으실겁니다.

1 값은 바로 argc 값입니다.

제가 인자를 주면 ecx 값이 변합니다.
.


80483e8:   83 ec 24                sub    $0x24,%esp

sub 뺄셈이죠

즉 esp 값을 0x24 만큼 빼는 겁니다.

초보분들이 헤깔릴 수 있는 부분인데  여기서 뺀다는 건

스택이 커지는 겁니다.

다시 한번 말하지만 스택은 밑으로 증가합니다.


현재 ebp , esp 위치를 확인해 보겠습니다.

(gdb) p $ebp
$1 = (void *) 0xbfdbf298

(gdb) p $esp
$2 = (void *) 0xbfdbf270
 
ebp 에서 0x28 만큼 떨어진 곳에 esp 가 있습니다.

mov %esp , %ebp  <---  esp 와 ebp 는 같은 위치에 있습니다.
push %ecx  <--- 4바이트 감소 esp
sub  $0x24 , %esp <----- 0x24 바이트 감소 esp

위 문장은 처음에 ebp와 esp를 같은 위치에 놓고 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)


c언에 에서 보면
    int a, b;
    int result;

위의 문장은 스택에  a , b , result 변수를 할당하는 것입니다.

하지만 실제로 어셈블러에서는 위의 문장에서는 아무 일도 일어나지 않습니다.

이미 위에서  sub    $0x24,%esp  스택을 증가 시켰습니다.

이 말은 이미 컴파일시에 스택의 길이는 정해졌다고 이해 할 수 있습니다.
그래서 어셈블러 입장에서는 특별히 할 일이 없는 것입니다.


    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)


movl   $0xa,-0x8(%ebp)  <---  0xa 를    ebp 에서 -0x8 떨어진 곳에 값을 넣었습니다.
movl   $0x5,-0xc(%ebp)  <---  0x5 를    ebp 에서 -0xc 떨어진 곳에 값을 넣었습니다.

스택에 값을 넣었습니다.

위의 문장에서 기준점을  ebp 로 잡았습니다.
물론 esp 값을 기준으로 값을 넣을 수도 있지만 단점이 있죠

예를 들어 push $eax <--- eax값을 스택에 넣었습니다.
그러면 esp 값을 감소되고 위의 문장을 다시 컨트롤 할려면 -4 를 더해주어야 합니다.

즉 esp 값은 계속 변할 수 있어 컨트롤 하기 힘들어 집니다.
하지만 ebp 값은 함수내에서는 값이 변하지 않습니다.
그래서 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)


sum 함수는 인자를 2개 받고 int 값을 리턴하는 함수 입니다.
또 하나씩 분석해 보겠습니다.

mov    -0xc(%ebp),%eax <---  ebp 에서 -0xc 떨어진 주소의 값을 eax 레지스터에 넣습니다.
무슨 값인지 다들 아시죠?  (  movl   $0x5,-0xc(%ebp) )  <-- 위에서 본 거 입니다.

mov    %eax,0x4(%esp)  <--- eax 안에 있는 값을 0x4(%esp) 에 넣습니다.

그럼 0x4(%esp) 안에는 5가 들어가 있습니다.

굳이 지금 기존 값을 복사해서 스택에 넣은 이유는 ?

c언어를 잘 이해 하시는 분들은 금방 아시겠지만

우리가 sum에서 인자로 넘기는 방식은 call by value 입니다.

즉 기존 값을 복사해서 옮기는 겁니다.  이건 c언어 쪽이라 자세한 설명은 책을 보면 나와 잇습니다.


 8048400:   8b 45 f8                mov    -0x8(%ebp),%eax
 8048403:   89 04 24                mov    %eax,(%esp)

이것도 마찬가지 입니다.

지금 스택 모양이 그려지세요?  어셈 문장을 분석하면서 스택이 머릿속에 그려져야 합니다.

이게 잘 않되신 분들은 집에서 꼭 실습해 보세요 

프로그래밍은 직접 해봐야 이해가 빠르죠

드디어 함수를 부릅니다.


 call   80483c4 <sum>  <--  call 이라는 새로운 명령어가 나왔습니다.

즉 80483c4로 점프하라는 명령어 입니다.

그러면 80483c4로 이동해서 문장을 해석하게 됩니다.

80483c4 여기는 당연히 sum의 시작 주소입니다.

단순히 call 명령어가 점프만 할까요?

함수가 끝나면 다시 main으로 돌아가야 하는데  돌아갈 주소를 모릅니다.


call 이 해석되면서  스택에 다음 주소가 저장됩니다.

0x08048406 <main+44>:    call   0x80483c4 <sum>
0x0804840b <main+49>:    mov    %eax,-0x10(%ebp)

여기서 call 바로 밑에 있는 주소 0x0804840b 가 스택에 저장됩니다.
그래야지 sum 함수가 끝나고 나서 이 주소를 보고 다시 main으로 돌아 갈 수 있습니다.

sum 함수를 어셈이 어떻게 표현하는지 보겠습니다.

int sum(int a, int b)
{
 80483c4:   55                      push   %ebp
 80483c5:   89 e5                   mov    %esp,%ebp
 80483c7:   83 ec 10                sub    $0x10,%esp
    int sum;

    sum=a+b;
 80483ca:   8b 55 0c                mov    0xc(%ebp),%edx
 80483cd:   8b 45 08                mov    0x8(%ebp),%eax
 80483d0:   01 d0                   add    %edx,%eax
 80483d2:   89 45 fc                mov    %eax,-0x4(%ebp)

    return sum;
 80483d5:   8b 45 fc                mov    -0x4(%ebp),%eax
}
 80483d8:   c9                      leave
 80483d9:   c3                      ret



80483c4:   55                      push   %ebp
 80483c5:   89 e5                   mov    %esp,%ebp
 80483c7:   83 ec 10                sub    $0x10,%esp

이제 위 3개문장은 이해 하시겠죠?  스택을 초기화 하고 스택을 감소 시킵니다.
중복으로 설명은 안하겠습니다.

중간에 있는 sum=a+b도 특별히 설명하게 없네요
이것도 위에서 설명했으니 똑같이 이해 하시면 됩니다.


 80483d5:   8b 45 fc                mov    -0x4(%ebp),%eax   <-- sum 값을 eax 레지스터에 저장합니다.


중요한 부분이 나왔습니다.

 80483d8:   c9                      leave
 80483d9:   c3                      ret

이제 sum 함수가 끝나고 main으로 돌아가는 방법입니다.

leave 명령어는

mov %ebp, %esp
pop %ebp

2문장을 함축해 놓은 명령어 입니다.

처음 함수 시작시 함수 초기화 문장 기억나시죠? 뭔가 연관이 있다고 생각 않하세여?
지금 위의 문장은 함수 초기화 문장과 정 반대로 실행되고 있습니다.

즉 기존 스택 포인터를 되돌리고 있는 문장입니다.
mov %ebp , %esp <--- 스택을 다시 초기화 시킵니다. 즉 스택이 작아집니다.

pop %ebp <---  현재 esp 값은 처음 push %ebp 요 위치 입니다.

어려울 수 있으니 다시 정리 해 보겠습니다.

call 명령어가 실행되면  스택에  call 명령어 다음 주소가 저장됩니다.
예를 들어

1)
0x100    :   0x500 <-- (0x500 이 call 다음 명령어) 

다음 sum 초기화 문장을 시작합니다.
push %ebp

2)
0x100    :   0x500 <-- (0x500 이 call 다음 명령어)
0x9c      :   (기존 ebp 주소저장)

mov %esp , %ebp

3)
0x100    :   0x500 <-- (0x500 이 call 다음 명령어)
0x9c      :   (기존 ebp 주소저장)  <--- esp ==  ebp

sum 함수가 시작되고 ..........................
이제 함수가 실행되고 끝나는 과정입니다.

leave 명령어를 풀어 써 보면
1  ~~    mov %ebp  , %esp
2  ~~   pop %ebp

4 - 1)
(esp가 다시 ebp 주소와 같아 집니다)

0x100    :   0x500 <-- (0x500 이 call 다음 명령어)
0x9c      :   (기존 ebp 주소저장)  <--- esp ==  ebp  

4 - 2)

0x100    :   0x500 <-- (0x500 이 call 다음 명령어)    <--- pop 을 했으니 esp 는 여길 가리킵니다.
0x9c      :   (기존 ebp 주소저장)  <--- 기존 ebp 주소값이 ebp에 저장됩니다.  그럼 ebp는 기존 주소값을 가집니다.


이제 ret 명령어를 보겠습니다.


leave 명령어 까지 실행하면 esp는 main 다음 주소를 가리키고 있습니다.
그럼 esp 가 가리키고 있는 이 주소를 eip에 넣어야 합니다.

ret 명령어가 실행되면 esp 주소값을 eip에 넘겨줍니다.

ret 도 실제로는 pop %eip 입니다. (eip 레지스터는 사용자가 컨트롤 불가능 합니다.)

그러면 다음 명령어가 call 다음 명령어를 가리키고 있겠죠?

처음에는 잘 이해가 않되실 겁니다.  이건 꼭 gdb를 이용해서

값을 비교 해 보시면 되면 이해가 훨씬 빠릅니다.


결론은 함수시작시 초기화 문은  함수가 끝나는 문장에서
다시 원상복귀하기 위해서 쓰는 문장입니다.

call 밑의 함수는 크게 어려운 문장이 없습니다.

위에서 다 설명한 문장을 그대로 이해하시면 됩니다.


프로그래밍은 실천입니다. 외울려고 하시지 마세요



ps)
글을 쓴지 꽤 시간이 지났네요 .. 손가락이 마비가 올거 갔습니다.
나름대로 쉽게 쓸려고 했는데 잘 됐는지 모르겠습니다.