9

I see three different outputs for where libc should be loaded.

ldd shows the loading address of libc at 0xf7e9e000

$ ldd ~/my_tool
    linux-gate.so.1 =>  (0xf7ffe000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e9e000)
    /lib/ld-linux.so.2 (0x56555000)

While gdb tells me it's loaded at 0xf7e96c60

(gdb) info sharedlibrary
From        To          Syms Read   Shared Object Library
0xf7fe1820  0xf7ff805f  Yes (*)     /lib/ld-linux.so.2
0xf7e96c60  0xf7f9643c  Yes (*)     /lib/i386-linux-gnu/libc.so.6

And when I manually calculate the base address based on a symbol:

(gdb) print system
$1 = {<text variable, no debug info>} 0xf7ebb6b0 <system>

system offset

I get this address:

0xf7ebb6b0-0x003b6b0 = 0xf7e80000

Why is that so?

calculated: 0xf7e80000
gdb:        0xf7e96c60
ldd:        0xf7e9e000

2 Answers 2

9

ldd

The program ldd is wrong for a few reasons.

First, ldd is not meant to be accurate for determining load addresses. Use the environment variable LD_TRACE_LOADED_OBJECTS.

Second, ldd will never be correct with ASLR enabled as Guntram showed. You can disable this pretty trivially if you have sudo access.

$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f44dae1b000)
$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9b35341000)
$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fef18efd000)
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
0
$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff75e7000)
$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff75e7000)
$ LD_TRACE_LOADED_OBJECTS=1 /bin/bash | grep libc
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff75e7000)

You can verify this by starting an instance of bash in the background and inspecting its maps file.

$ bash &
[1] 30398
[1]  + 30398 suspended (tty input)  bash                                                                             
$ grep libc /proc/30398/maps | head -n1
7ffff75e7000-7ffff77a2000 r-xp 00000000 08:01 525269                     /lib/x86_64-linux-gnu/libc-2.19.so

info sharedlibrary

The address reported by info sharedlibrary is the address of the .text area.

Note that in the particular libc that I have, the .text is at 0x1f4a0.

$ readelf --wide --section-headers /lib/x86_64-linux-gnu/libc-2.19.so | grep text
  [12] .text             PROGBITS        000000000001f4a0 01f4a0 145c23 00  AX  0   0 16

In GDB, if we look at the load addresses of libc, we see that it is loaded at 0x7ffff7a14000. This will change each time the program is run, if ASLR is enabled on the system. If you run it under GDB, it will also disable ASLR. Run the command set disable-randomization off before running the target and you will observe it change each run.

gdb-peda$ info proc mapping
  ...
  0x7ffff7a14000     0x7ffff7bcf000   0x1bb000        0x0 /lib/x86_64-linux-gnu/libc-2.19.so
  0x7ffff7bcf000     0x7ffff7dcf000   0x200000   0x1bb000 /lib/x86_64-linux-gnu/libc-2.19.so
  0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x1bb000 /lib/x86_64-linux-gnu/libc-2.19.so
  0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x1bf000 /lib/x86_64-linux-gnu/libc-2.19.so
  ...

You can verify this by checking to see if it starts with the ELF header.

gdb-peda$ hexdump 0x00007ffff7a14000 4
0x0000 0x00007ffff7a14000 │ 7f 45 4c 46                                      │ .ELF
0x0004 0x00007ffff7a14004

If we add the 0x1f4a0 offset from the .text area, we get 0x7ffff7a334a0.

gdb-peda$ printf "%p\n",(0x00007ffff7a14000+0x1f4a0)
0x7ffff7a334a0

Now if we look at info sharedlibrary, we see exactly this address.

gdb-peda$ info sharedlibrary libc
From                To                  Syms Read   Shared Object Library
0x00007ffff7a334a0  0x00007ffff7b790c3  Yes         /lib/x86_64-linux-gnu/libc.so.6

Extra Tips

In GDB, you should use the info proc mapping command to get the base address of a loaded module.

However, this is not available on FreeBSD, since GDB does not support the FreeBSD-specific APIs necessary to get module base addresses. Instead, you must use the info proc mapping command and perform the translation yourself (by subtracting the address of the .text section).

5
  • 1
    There is no ASLR in my case. The example I posted in the original question are constant each time.
    – samuirai
    Dec 4, 2014 at 11:42
  • 1
    My answer addresses the issue. Info sharedlibrary prints the address of the .text area, not the moduel base. Dec 17, 2014 at 21:20
  • Ping. This is answered. The very tl;dr is at the end. You want "info proc mapping". Apr 15, 2015 at 16:42
  • @ZachRiggle ping, could you add an explanation for why ldd "is not meant to be accurate"? Also, using LD_TRACE_LOADED_OBJECTS=1 does not improve the situation: indeed reading from man ldd, invoking ldd is equivalent to explicitly call the loader with LD_TRACE_LOADED_OBJECTS variable set -- so, ldd is not using it. pastebin.com/raw/06bQjui5 Jul 29, 2016 at 15:58
  • @guiscri: Looking back, I'm not sure why I made that statement. I expect there was a reason, but it's been two years and I've forgotten. ldd foo and LD_TRACE_LOADED_OBJECTS foo should be functionally equivalent. In either case, you shouldn't rely on it for module addresses. Aug 11, 2016 at 21:03
4

First, ASLR will load the library at a (slightly) different address with each invocation, to help protect against malware. This is why the addresses between ldd and gdb are different, and why they may be even different each time you run gdb.

If i just grep for the libc executable segment on my system (64 bit, as i didn't have a 32 bit system handy):

$ grep 'r-xp.*libc-2.19' /proc/*/maps|head

/proc/10987/maps:7fe455b72000-7fe455d2d000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/11880/maps:7f029dc1f000-7f029ddda000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/11884/maps:7f25a9c25000-7f25a9de0000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/15715/maps:7ffc713da000-7ffc71595000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/17705/maps:7fe9db80c000-7fe9db9c7000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/18558/maps:7fc248544000-7fc2486ff000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/20156/maps:7f0f8eb13000-7f0f8ecce000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/2139/maps:7fba4c097000-7fba4c252000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/2215/maps:7f934ed76000-7f934ef31000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
/proc/2224/maps:7f064d98f000-7f064db4a000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so

you see every process maps the same library a bit differently.

Also, when the loader loads a library, it creates several memory segments for it. The segments from the ELF file get read into these shared memory segments, including some headers. This is why the code start address isn't identical to the segment start address.

For example, on my system :

(gdb) info sharedLibrary
From                To                  Syms Read   Shared Object Library
0x00007ffff7a334a0  0x00007ffff7b790c3  Yes         /lib/x86_64-linux-gnu/libc.so.6

and

cat /proc/26271/maps 
7ffff7a14000-7ffff7bcf000 r-xp 00000000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
7ffff7bcf000-7ffff7dcf000 ---p 001bb000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
7ffff7dcf000-7ffff7dd3000 r--p 001bb000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so
7ffff7dd3000-7ffff7dd5000 rw-p 001bf000 08:16 23461990                   /lib/x86_64-linux-gnu/libc-2.19.so

so you see the memory map for the code segment (the one that is executable, i.e. has the x bit set) ranges from 7ffff7a14000-7ffff7bcf000, but the region the actual code is loaded to is just the subset 7ffff7a334a0-7ffff7b790c3.

You can find a more thorough explanation here.

1
  • There is no ASLR in my case. The example I posted in the original question are constant each time.
    – samuirai
    Dec 4, 2014 at 11:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.