Here's a very simple C++ program:
// main.cpp
int main() {}
My Makefile
generates the following command to compile the program.
❯ make
g++ -O0 -fverbose-asm -o main main.cpp
I check with the command file
to see it's an ELF executable:
❯ file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=921d352e49a0e4262aece7e72418290189520782, for GNU/Linux 3.2.0, not stripped
Everything seems fine until I try to inspect the ELF header:
❯ readelf -e main
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
...
Type: DYN (Shared object file)
From wikipedia here, it appears that there are different types of files such as EXEC
. Why does it say my simple main program is a shared object and not an executable on the ELF header?
From the limited scope of knowledge I have on .so
's, I thought they were libraries that is linked but not loaded until runtime. How does that make sense in this context?
❯ g++ --version
g++ (Arch Linux 9.3.0-1) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
❯ readelf --version
GNU readelf (GNU Binutils) 2.34
Copyright (C) 2020 Free Software Foundation, Inc.
❯ lsb_release -a
LSB Version: 1.4
Distributor ID: Arch
Description: Arch Linux
Release: rolling
Codename: n/a
A dynamic shared object (DSO) is an object file that is meant to be used simultaneously (or shared) by multiple applications (a. out files) while they are executing.
you can use readelf and objdump to read parts of an elf file. You can also use 'hexdump filename' to get a hexdump of the contents of a binary file (this is likely only useful if you like reading machine code or you are writing an assembler).
The ELF header defines whether to use 32-bit or 64-bit addresses. The header contains three fields that are affected by this setting and offset other fields that follow them. The ELF header is 52 or 64 bytes long for 32-bit and 64-bit binaries respectively.
An ELF file consists of zero or more segments, and describe how to create a process/memory image for runtime execution. When the kernel sees these segments, it uses them to map them into virtual address space, using the mmap(2) system call. In other words, it converts predefined instructions into a memory image.
So depending upon the type of object file, the ELF header gives detailed information about the file. Mostly in case of executable files, an ELF header is followed by program header table. A program header table helps in creating process image.
If your ELF file is a normal binary, it requires these program headers. Otherwise, it simply won’t run. It uses these headers, with the underlying data structure, to form a process. This process is similar for shared libraries.
On a Linux terminal, the command man elf gives you a handy summary about the structure of an ELF file: files. Amongst these files are normal executable files, relocatable object files, core files and shared libraries. followed by a program header table or a section header table, or both. The ELF header is always at offset zero of the file.
This ELF header magic provides information about the file. The first 4 hexadecimal parts define that this is an ELF file (45= E ,4c= L ,46= F ), prefixed with the 7f value.
Executables that are as compiled as "position independent executables" (with -pie
/-fPIE
) should be relocated to a random address at runtime. To achieve this, they use the DYN type.
Your version of g++ was configured with --enable-default-pie
, so it sets -pie
and -fPIE
by default. You can disable this, and generate a normal executable, by linking with -no-pie
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With