Subtract 0x400000
from st_value
since the address is located in the first LOAD
segment of the binary.
According to the System V ABI (generic), the meaning of the st_value
symbol depends on what type of object file the binary is:
Symbol table entries for different object file types have slightly different interpretations
for the st_value
member.
- In relocatable files,
st_value
holds alignment constraints for a symbol whose section index is SHN_COMMON
.
- In relocatable files,
st_value
holds a section offset for a defined symbol. That is, st_value
is an offset from the beginning of the section that st_shndx
identifies.
- In executable and shared object files,
st_value
holds a virtual address. To make these files’ symbols more useful for the dynamic linker, the section offset (file interpretation) gives way to a virtual address (memory interpretation) for which the section number is irrelevant.
An executable ELF64 object file linked by ld
has a canonical base address of 0x0000000000400000
given by the linker script variable __executable_start
. This means that one can subtract the value of the base address from the st_value
to find its file offset.
We can verify this by looking at a link map of an ELF64 binary. Here a snippet of the output of ld -M /bin/ls
:
Memory Configuration
Name Origin Length Attributes
*default* 0x0000000000000000 0xffffffffffffffff
Linker script and memory map
LOAD /bin/ls
0x0000000000400000 PROVIDE (__executable_start, 0x400000) <--------------
0x0000000000400190 . = (0x400000 + SIZEOF_HEADERS)
.interp 0x0000000000400190 0x1c
*(.interp)
.interp 0x0000000000400190 0x1c /bin/ls
.note.ABI-tag 0x00000000004001ac 0x20
.note.ABI-tag 0x00000000004001ac 0x20 /bin/ls
.note.gnu.build-id
0x00000000004001cc 0x24
*(.note.gnu.build-id)
.note.gnu.build-id
0x00000000004001cc 0x24 /bin/ls
This can be further verified by examining the section-to-segment mapping of the same binary via readelf -l /bin/ls
:
Elf file type is EXEC (Executable file)
Entry point 0x404890
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 <-------------
0x0000000000019d44 0x0000000000019d44 R E 200000
LOAD 0x0000000000019df0 0x0000000000619df0 0x0000000000619df0
0x0000000000000804 0x0000000000001570 RW 200000
DYNAMIC 0x0000000000019e08 0x0000000000619e08 0x0000000000619e08
0x00000000000001f0 0x00000000000001f0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000001701c 0x000000000041701c 0x000000000041701c
0x000000000000072c 0x000000000000072c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000019df0 0x0000000000619df0 0x0000000000619df0
0x0000000000000210 0x0000000000000210 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
The first LOAD
segment has a base address of 0x0000000000400000
.
Compare the values in the Off
column with those in the Address
column:
$ readelf -SW /bin/ls
There are 28 section headers, starting at offset 0x1a700:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 000068 00 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400300 000300 000c18 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400f18 000f18 000593 00 A 0 0 1
[ 7] .gnu.version VERSYM 00000000004014ac 0014ac 000102 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000004015b0 0015b0 000090 00 A 6 2 8
[ 9] .rela.dyn RELA 0000000000401640 001640 0000a8 18 A 5 0 8
[10] .rela.plt RELA 00000000004016e8 0016e8 000a80 18 A 5 12 8
[11] .init PROGBITS 0000000000402168 002168 00001a 00 AX 0 0 4
[12] .plt PROGBITS 0000000000402190 002190 000710 10 AX 0 0 16
[13] .text PROGBITS 00000000004028a0 0028a0 00f65a 00 AX 0 0 16
[14] .fini PROGBITS 0000000000411efc 011efc 000009 00 AX 0 0 4
[15] .rodata PROGBITS 0000000000411f20 011f20 0050fc 00 A 0 0 32
[16] .eh_frame_hdr PROGBITS 000000000041701c 01701c 00072c 00 A 0 0 4
[17] .eh_frame PROGBITS 0000000000417748 017748 0025fc 00 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000619df0 019df0 000008 00 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000619df8 019df8 000008 00 WA 0 0 8
[20] .jcr PROGBITS 0000000000619e00 019e00 000008 00 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000619e08 019e08 0001f0 10 WA 6 0 8
[22] .got PROGBITS 0000000000619ff8 019ff8 000008 08 WA 0 0 8
[23] .got.plt PROGBITS 000000000061a000 01a000 000398 08 WA 0 0 8
[24] .data PROGBITS 000000000061a3a0 01a3a0 000254 00 WA 0 0 32
[25] .bss NOBITS 000000000061a600 01a5f4 000d60 00 WA 0 0 32
[26] .gnu_debuglink PROGBITS 0000000000000000 01a5f4 000008 00 0 0 1
[27] .shstrtab STRTAB 0000000000000000 01a5fc 0000fe 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)