본문 바로가기

공부

함수 호출 과정, PLT와 GOT

리눅스 함수 호출 과정이 어떻게 될까?


간단한 printf 함수로 테스트를 해봤습니다.


#include <stdio.h>

int main(){ printf("test~~~"); }

다음 프로그램을 작성 후 gdb로 까보면..


(gdb) disas main

Dump of assembler code for function main:

   0x0804841b <+0>: lea    ecx,[esp+0x4]

   0x0804841f <+4>: and    esp,0xfffffff0

   0x08048422 <+7>: push   DWORD PTR [ecx-0x4]

   0x08048425 <+10>: push   ebp

   0x08048426 <+11>: mov    ebp,esp

   0x08048428 <+13>: push   ecx

   0x08048429 <+14>: sub    esp,0x4

   0x0804842c <+17>: sub    esp,0xc

   0x0804842f <+20>: push   0x80484d0

   0x08048434 <+25>: call   0x80482f0 <printf@plt>

   0x08048439 <+30>: add    esp,0x10

   0x0804843c <+33>: mov    ecx,DWORD PTR [ebp-0x4]

   0x0804843f <+36>: leave  

   0x08048440 <+37>: lea    esp,[ecx-0x4]

   0x08048443 <+40>: ret    

End of assembler dump.


다음과 같이 plt 영역의 printf 주소를 찾아볼 수 있습니다.

저 주소에는 뭐가 들어있을까요?


(gdb) x/3i 0x80482f0

   0x80482f0 <printf@plt>: jmp    DWORD PTR ds:0x804a00c

   0x80482f6 <printf@plt+6>: push   0x0

   0x80482fb <printf@plt+11>: jmp    0x80482e0


음 뭔가 또 점프를 하는 것을 볼 수 있습니다. 다시 따라가보면..


(gdb) x/1x 0x804a00c

0x804a00c <printf@got.plt>: 0x080482f6


이번엔 got에 왔네요;

지금까지의 flow를 그려보면 다음과 같습니다.

그림 넘넘 잘그리네용

아무튼 다시 결국 되돌아와선 0x80482e0에 다시 점프하게되는데! 

이 0x80482e0이 dl_runtime_resolve라고 하는 영역입니다. 

여기서 dl은 dynamic linker라고 하는데 좀있다가 자세하게 설명 드릴거구, 일단은 dl_runtime_resolve는 공유 라이브러리에서 특정 함수의 주소를 가져오는 역할을 하는구나 하고만 생각해주시면 될 것 같아요.


그림이 점점 더러워지네요 아무튼 dl_runtime_resolve로 점프하게되면 got 위치에 특정 함수 주소를 찾아서 저장시켜줍니다. 특정 함수라 함은 여기서 printf()가 되겠지요..


이 과정이 수행되고 나면 got 영역에 삽입된 함수를 호출하게 됩니다.


여기까지 정리해서 설명해드리면..


우선 제일 처음 나왔던 printf의 PLT 주소는 GOT영역의 주소를 가리키고 있습니다.


그 GOT 영역으로 트레이싱하게 되면 또 다시 어떤 주소가 들어있는데 이 주소는 다시 PLT 영역으로 돌아오게됩니다. 이는 dl_runtime_resolver로 점프하기 위함이죠.


인자 하나를 가지고 dl_runtime_resolver로 점프하게되면 got의 위치에 함수의 주소를 찾아 저장하고 그 함수를 호출한 뒤 다시 복귀하게 됩니다.


왜 이런 과정을 하는걸까?


같은 함수 호출을 여러 번 하게 되는 경우 일일이 라이브러리 주소를 가져와야 하기 때문에 got에 라이브러리 주소를 저장함으로써 이를 해결하는 것이라 보시면 됩니다.


실제로 printf 함수를 두 번 호출하게 되면 어떻게 될까용


root@ubuntu:~/study# cat pltgot.c 

#include <stdio.h>

int main(){ printf("test~~~"); printf("re-hi!”); }


(gdb) disas main

Dump of assembler code for function main:

   0x0804841b <+0>: lea    0x4(%esp),%ecx

   0x0804841f <+4>: and    $0xfffffff0,%esp

   0x08048422 <+7>: pushl  -0x4(%ecx)

   0x08048425 <+10>: push   %ebp

   0x08048426 <+11>: mov    %esp,%ebp

   0x08048428 <+13>: push   %ecx

   0x08048429 <+14>: sub    $0x4,%esp

   0x0804842c <+17>: sub    $0xc,%esp

   0x0804842f <+20>: push   $0x80484e0

   0x08048434 <+25>: call   0x80482f0 <printf@plt>

   0x08048439 <+30>: add    $0x10,%esp

   0x0804843c <+33>: sub    $0xc,%esp

   0x0804843f <+36>: push   $0x80484e8

   0x08048444 <+41>: call   0x80482f0 <printf@plt>

   0x08048449 <+46>: add    $0x10,%esp

   0x0804844c <+49>: mov    -0x4(%ebp),%ecx

   0x0804844f <+52>: leave  

   0x08048450 <+53>: lea    -0x4(%ecx),%esp

   0x08048453 <+56>: ret    

End of assembler dump.


참고로 \n을 사용하시게되면 인자를 넣지 않는 한 puts로 바뀔거에요

암튼 한번 printf를 호출한 다음 부분을 break걸고 got 영역을 확인해보면..


(gdb) disas main

Dump of assembler code for function main:

   0x0804841b <+0>: lea    ecx,[esp+0x4]

   0x0804841f <+4>: and    esp,0xfffffff0

   0x08048422 <+7>: push   DWORD PTR [ecx-0x4]

   0x08048425 <+10>: push   ebp

   0x08048426 <+11>: mov    ebp,esp

   0x08048428 <+13>: push   ecx

   0x08048429 <+14>: sub    esp,0x4

   0x0804842c <+17>: sub    esp,0xc

   0x0804842f <+20>: push   0x80484e0

   0x08048434 <+25>: call   0x80482f0 <printf@plt>

   0x08048439 <+30>: add    esp,0x10

   0x0804843c <+33>: sub    esp,0xc

=> 0x0804843f <+36>: push   0x80484e8

   0x08048444 <+41>: call   0x80482f0 <printf@plt>

   0x08048449 <+46>: add    esp,0x10

   0x0804844c <+49>: mov    ecx,DWORD PTR [ebp-0x4]

   0x0804844f <+52>: leave  

   0x08048450 <+53>: lea    esp,[ecx-0x4]

   0x08048453 <+56>: ret    

End of assembler dump.


(gdb) x/i 0x80482f0

   0x80482f0 <printf@plt>: jmp    DWORD PTR ds:0x804a00c


(gdb) x/x 0x804a00c

0x804a00c <printf@got.plt>: 0xb7e50130


(gdb) x/2i 0xb7e50130

   0xb7e50130 <__printf>: push   ebx

   0xb7e50131 <__printf+1>: call   0xb7f28e95 <__x86.get_pc_thunk.bx>


다음과 같이 첫 printf 호출 당시에 dl_runtime_resolve 함수에 의해 got 테이블에 printf 주소가 입력되었다는 사실을 알 수 있었습니다.


각 실행파일마다 이러한 영역은 readelf 명령을 통해 확인해 볼 수 있습니다.


먼가 본의아니게 늦게 언급하게됬는데

plt는 procedure linkable table의 약자이고 got는 global offset table의 약자입니다. 의미를 풀어놓으니 확실히 위에서 plt와 got가 어떻게 쓰였는지 매치가 될 것 같네용 ㅎ


'공부' 카테고리의 다른 글

malloc chunk  (0) 2015.10.28
  (0) 2015.10.26
dynamic linker  (0) 2015.10.18
vdso  (0) 2015.10.17
syscall 목록  (0) 2015.10.16