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 <http://gnu.org/licenses/gpl.html>
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:
<http://bugs.launchpad.net/gdb-linaro/>...
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 <unistd.h>
2     #include <stdio.h>
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 <getpid@plt>
   0x08048462 <+30>:   mov    $0x8048568,%edx
   0x08048467 <+35>:   mov    %eax,0x4(%esp)
   0x0804846b <+39>:   mov    %edx,(%esp)
   0x0804846e <+42>:   call   0x8048340 <printf@plt>
   0x08048473 <+47>:   lea    0x18(%esp),%eax
   0x08048477 <+51>:   mov    %eax,0x4(%esp)
   0x0804847b <+55>:   movl   $0x8048560,(%esp)
   0x08048482 <+62>:   call   0x8048380 <execv@plt>
   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 <getpid@got.plt>:	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/<PID-of-process>/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 <printf@plt>:	jmp    *0x804a000
   0x8048346 <printf@plt+6>:	push   $0x0
   0x804834b <printf@plt+11>:	jmp    0x8048330
   0x8048350 <getpid@plt>:	jmp    *0x804a004
   0x8048356 <getpid@plt+6>:	push   $0x8
   0x804835b <getpid@plt+11>:	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 <getpid@got.plt>:	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 <getpid+16>
   0x001cd38c <+12>:   mov    %edx,%eax
   0x001cd38e <+14>:   repz ret 
   0x001cd390 <+16>:   jne    0x1cd3a2 <getpid+34>
   0x001cd392 <+18>:   mov    %gs:0x68,%eax
   0x001cd398 <+24>:   test   %eax,%eax
   0x001cd39a <+26>:   lea    0x0(%esi),%esi
   0x001cd3a0 <+32>:   jne    0x1cd38e <getpid+14>
   0x001cd3a2 <+34>:   mov    $0x14,%eax
   0x001cd3a7 <+39>:   call   *%gs:0x10
   0x001cd3ae <+46>:   test   %edx,%edx
   0x001cd3b0 <+48>:   mov    %eax,%ecx
   0x001cd3b2 <+50>:   jne    0x1cd38e <getpid+14>
   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 <getpid@got.plt>:	0x80	0xd3	0x1c	0x00
(gdb) x/16x 0x804a004
0x804a004 <getpid@got.plt>:	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) 

