Examining dynamic linking with GDB. You can examine the workings of PLT and GOT when you start a dynamically linked executable under gdb. Here is a transcript of my session. Lines starting with SB: are my comments, inserted later: sergey@toy32:~$ gcc -g -o exec exec.c sergey@toy32:~$ gdb ./exec GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08 Copyright (C) 2011 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". For bug reporting instructions, please see: ... Reading symbols from /home/sergey/exec...done. SB: thanks to -g compilation option, we have debug symbols for individual lines of code: (gdb) list 1 #include 2 #include 3 4 int main() 5 { 6 char * args[] = {"/bin/ls", NULL}; 7 8 printf("pid: %d\n", getpid()); 9 10 //sleep(60); SB: We want to examine GOT before any dynamically linked functions are actually called and linked, so breakpoint at main: (gdb) b main Breakpoint 1 at 0x804844d: file exec.c, line 6. SB: Run till that breakpoint. (gdb) r Starting program: /home/sergey/exec Breakpoint 1, main () at exec.c:6 6 char * args[] = {"/bin/ls", NULL}; SB: Disassemble. Note below the actual location of the current instruction (i.e., where we hit the breakpoint just now) shown in the disasm listing with => . This is the instruction at which we stopped. NOTE: We could have put that breakpoint in at the given address instead of using the symbol "main", like so: "b *0x0804844d". Note the * and read "help breakpoints" for other useful breakpoint-related commands! (gdb) disas main Dump of assembler code for function main: 0x08048444 <+0>: push %ebp 0x08048445 <+1>: mov %esp,%ebp 0x08048447 <+3>: and $0xfffffff0,%esp 0x0804844a <+6>: sub $0x20,%esp => 0x0804844d <+9>: movl $0x8048560,0x18(%esp) 0x08048455 <+17>: movl $0x0,0x1c(%esp) 0x0804845d <+25>: call 0x8048350 0x08048462 <+30>: mov $0x8048568,%edx 0x08048467 <+35>: mov %eax,0x4(%esp) 0x0804846b <+39>: mov %edx,(%esp) 0x0804846e <+42>: call 0x8048340 0x08048473 <+47>: lea 0x18(%esp),%eax 0x08048477 <+51>: mov %eax,0x4(%esp) 0x0804847b <+55>: movl $0x8048560,(%esp) 0x08048482 <+62>: call 0x8048380 0x08048487 <+67>: leave 0x08048488 <+68>: ret End of assembler dump. SB: Just so, let's examine that address 0x8048560 -- what's there? A string. What happens if you compile with optimization? (gdb) x/10c 0x8048560 0x8048560: 47 '/' 98 'b' 105 'i' 110 'n' 47 '/' 108 'l' 115 's' 0 '\000' 0x8048568: 112 'p' 105 'i' SB: The fist call to "getpid" (the PLT stub for it, actually) is to 0x8048350. Let's see that it really leads to the stub, as the disassembler hints (it knows all the symbol tables and tries to use them to decorate disassembly). (gdb) disas 0x8048350 Dump of assembler code for function getpid@plt: 0x08048350 <+0>: jmp *0x804a004 0x08048356 <+6>: push $0x8 0x0804835b <+11>: jmp 0x8048330 End of assembler dump. SB: The first instruction is a jump through a GOT entry. You can see the GOT with "objdump -s" too, of course. The address is stored in the annoying little-endian format, but it's 0x08048356 from above, just right after the first instruction of the stub. Convince yourself that all other GOT entries point into their respective stubs as well. (gdb) x/4x 0x804a004 0x804a004 : 0x56 0x83 0x04 0x08 SB: Let's single-step instructions. Recall that GDB interprets 'Enter' as "repeat previous command": (gdb) si 0x08048455 6 char * args[] = {"/bin/ls", NULL}; (gdb) 8 printf("pid: %d\n", getpid()); (gdb) SB: Now we are hitting the stub, and from there also the indirect jump to the dynamic linker at 0x8048330. Disassemble that code and understand how the dynamic linker is able to continue the function call (hint: the original call's arguments are on the stack, as per Linux 32bits userland calling convention; the PLT stub only adds another argument on top of them). 0x08048350 in getpid@plt () (gdb) 0x08048356 in getpid@plt () (gdb) 0x0804835b in getpid@plt () (gdb) 0x08048330 in ?? () (gdb) 0x08048336 in ?? () SB: And we hit the dynamic linker. See that the current instruction address (EIP) is pointing into it, e.g., by looking at /proc//maps (my PID is 9844, see below) (gdb) 0x00123c20 in ?? () from /lib/ld-linux.so.2 SB: Another way to disassemble code. "disas" would balk at disassembling from an address not in a known function symbol, but x/i still works: (gdb) disas 0x8048330 No function contains specified address. (gdb) x/i 0x8048330 0x8048330: pushl 0x8049ff8 (gdb) x/10i 0x8048330 0x8048330: pushl 0x8049ff8 0x8048336: jmp *0x8049ffc 0x804833c: add %al,(%eax) 0x804833e: add %al,(%eax) 0x8048340 : jmp *0x804a000 0x8048346 : push $0x0 0x804834b : jmp 0x8048330 0x8048350 : jmp *0x804a004 0x8048356 : push $0x8 0x804835b : jmp 0x8048330 (gdb) si 0x00123c21 in ?? () from /lib/ld-linux.so.2 (gdb) 0x00123c22 in ?? () from /lib/ld-linux.so.2 (gdb) 0x00123c23 in ?? () from /lib/ld-linux.so.2 (gdb) 0x00123c27 in ?? () from /lib/ld-linux.so.2 (gdb) 0x00123c2b in ?? () from /lib/ld-linux.so.2 SB: Single-stepping ld.so would take too long. Let the process run free with "continue" ("c"), but first set another breakpoint to catch it: (gdb) list 3 4 int main() 5 { 6 char * args[] = {"/bin/ls", NULL}; 7 8 printf("pid: %d\n", getpid()); 9 10 //sleep(60); 11 execv("/bin/ls", args); 12 } (gdb) b 10 Breakpoint 2 at 0x8048473: file exec.c, line 10. (gdb) c Continuing. pid: 9844 Breakpoint 2, main () at exec.c:11 11 execv("/bin/ls", args); SB: After we hit the second breakpoint, look at the GOT slot. Now it has an address in libc.so loaded into the process, 0x1cd380. Look at the map and see it. Also, find the address of printf, it has been resolved too by now (but not the address of execv, though). (gdb) x/4x 0x804a004 0x804a004 : 0x80 0xd3 0x1c 0x00 SB: And to convince ourselves that we are indeed looking at libc.so code for getpid: ("nm /path/to/libc.so | grep getpid" or "objdump -d /path/to/libc.so" will help -- note that the offsets within page -- lower 3 hex digits -- are the same, but the page address may vary.) (gdb) disas 0x1cd380 Dump of assembler code for function getpid: 0x001cd380 <+0>: mov %gs:0x6c,%edx 0x001cd387 <+7>: cmp $0x0,%edx 0x001cd38a <+10>: jle 0x1cd390 0x001cd38c <+12>: mov %edx,%eax 0x001cd38e <+14>: repz ret 0x001cd390 <+16>: jne 0x1cd3a2 0x001cd392 <+18>: mov %gs:0x68,%eax 0x001cd398 <+24>: test %eax,%eax 0x001cd39a <+26>: lea 0x0(%esi),%esi 0x001cd3a0 <+32>: jne 0x1cd38e 0x001cd3a2 <+34>: mov $0x14,%eax 0x001cd3a7 <+39>: call *%gs:0x10 0x001cd3ae <+46>: test %edx,%edx 0x001cd3b0 <+48>: mov %eax,%ecx 0x001cd3b2 <+50>: jne 0x1cd38e 0x001cd3b4 <+52>: mov %ecx,%gs:0x68 0x001cd3bb <+59>: nop 0x001cd3bc <+60>: lea 0x0(%esi,%eiz,1),%esi 0x001cd3c0 <+64>: ret End of assembler dump. SB: Another look at GOT slots: (gdb) x/4x 0x804a004 0x804a004 : 0x80 0xd3 0x1c 0x00 (gdb) x/16x 0x804a004 0x804a004 : 0x80 0xd3 0x1c 0x00 0x66 0x83 0x04 0x08 0x804a00c <__libc_start_main@got.plt>: 0x20 0xa0 0x14 0x00 0x86 0x83 0x04 0x08 (gdb) disas 0x08048366 Dump of assembler code for function __gmon_start__@plt: 0x08048360 <+0>: jmp *0x804a008 0x08048366 <+6>: push $0x10 0x0804836b <+11>: jmp 0x8048330 End of assembler dump. (gdb)