Analysis of the readelf Command and ELF Files in Embedded Linux

ELF (Executable and Linking Format) is a file format that defines how the internal information of object files is composed and organized. The kernel uses this information to load executable files, determining where to fetch code, where to obtain initialization data, and where to load shared libraries, among other details.

Analysis of the readelf Command and ELF Files in Embedded Linux

There are three types of ELF files:

1. Object files

$ gcc -c test.c

The resulting test.o is an object file, which can be linked to generate an executable file.

A static library is also considered an object file, created by packaging object files into a .a file using the ar command.

For example: ar crv libtest.a test.o

2. Executable files

$ gcc -o test test.c

The resulting test file is an executable binary file.

3. Shared libraries

$ gcc test.c -fPIC -shared -o libtest.so

The resulting file libtest.so is a shared library.

The readelf command can be used to distinguish between the three types of ELF files, as the header information for each type is different.

$ readelf -h test.o

Object file

ELF Header:  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   Class:                             ELF64  Data:                              2's complement, little endian  Version:                           1 (current)  OS/ABI:                            UNIX - System V  ABI Version:                       0  Type:                              REL (Relocatable file)  Machine:                           Advanced Micro Devices X86-64  Version:                           0x1  Entry point address:               0x0  Start of program headers:          0 (bytes into file)  Start of section headers:          456 (bytes into file)  Flags:                             0x0  Size of this header:               64 (bytes)  Size of program headers:           0 (bytes)  Number of program headers:         0  Size of section headers:           64 (bytes)  Number of section headers:         13  Section header string table index: 10

$ readelf -h test

Executable file

ELF Header:  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   Class:                             ELF64  Data:                              2's complement, little endian  Version:                           1 (current)  OS/ABI:                            UNIX - System V  ABI Version:                       0  Type:                              EXEC (Executable file)  Machine:                           Advanced Micro Devices X86-64  Version:                           0x1  Entry point address:               0x400420  Start of program headers:          64 (bytes into file)  Start of section headers:          2696 (bytes into file)  Flags:                             0x0  Size of this header:               64 (bytes)  Size of program headers:           56 (bytes)  Number of program headers:         8  Size of section headers:           64 (bytes)  Number of section headers:         30  Section header string table index: 27

$ readelf -h libtest.so

Shared library

ELF Header:  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   Class:                             ELF64  Data:                              2's complement, little endian  Version:                           1 (current)  OS/ABI:                            UNIX - System V  ABI Version:                       0  Type:                              DYN (Shared object file)  Machine:                           Advanced Micro Devices X86-64  Version:                           0x1  Entry point address:               0x570  Start of program headers:          64 (bytes into file)  Start of section headers:          2768 (bytes into file)  Flags:                             0x0  Size of this header:               64 (bytes)  Size of program headers:           56 (bytes)  Number of program headers:         6  Size of section headers:           64 (bytes)  Number of section headers:         29  Section header string table index: 26

Below is the content of the test.c file:

#include <stdio.h>
int global_data = 4;int global_data_2;int main(int argc, char **argv){     int local_data = 3; 
    printf("Hello World\n");     printf("global_data = %d\n", global_data);     printf("global_data_2 = %d\n", global_data_2);     printf("local_data = %d\n", local_data); 
    return (0);}

$ gcc -o test test.c

Generate the executable file test, and then analyze it using readelf.

$ readelf -h test

Below is the output:

ELF Header:  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00   Class:                             ELF64  Data:                              2's complement, little endian  Version:                           1 (current)  OS/ABI:                            UNIX - System V  ABI Version:                       0  Type:                              EXEC (Executable file)  Machine:                           Advanced Micro Devices X86-64  Version:                           0x1  Entry point address:               0x400420  Start of program headers:          64 (bytes into file)  Start of section headers:          2696 (bytes into file)  Flags:                             0x0  Size of this header:               64 (bytes)  Size of program headers:           56 (bytes)  Number of program headers:         8  Size of section headers:           64 (bytes)  Number of section headers:         30  Section header string table index: 27

What information can we derive from the above?

1. Based on Class, Type, and Machine, we can determine that this file is a 64-bit executable file generated for an X86-64 machine.

2. The Entry point address indicates that when this program starts, it runs from the virtual address 0x400420. This address is not the address of the main function, but rather the address of the _start function, which is created by the linker for program initialization. You can see the _start function using this command: objdump -d -j .text test

3. The Number of program headers indicates that this program has 8 segments.

4. The Number of section headers indicates that this program has 30 sections.

The information stored in sections is used for linking and mainly includes: program code, program data (variables), relocation information, etc. For example: the Code section stores the code, while the data section stores initialized or uninitialized data, and so on.

The Linux kernel cannot recognize executable files in terms of sections. The kernel uses Virtual Memory Areas (VMAs) that include contiguous pages to identify processes. Each VMA may map one or more sections. Each VMA represents a segment of an ELF file.

So, how does the kernel know which section belongs to which VMA (segment)? The mapping relationship is stored in the Program Header Table (PHT).

Next, let’s check the contents of the sections:

$ readelf -S test

There are 30 section headers, starting at offset 0xa88:
Section Headers:  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align  [ 0]                   NULL             0000000000000000  00000000       0000000000000000  0000000000000000           0     0     0  [ 1] .interp           PROGBITS         0000000000400200  00000200       000000000000001c  0000000000000000   A       0     0     1  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c       0000000000000020  0000000000000000   A       0     0     4  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c       0000000000000024  0000000000000000   A       0     0     4  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260       000000000000001c  0000000000000000   A       5     0     8  [ 5] .dynsym           DYNSYM           0000000000400280  00000280       0000000000000078  0000000000000018   A       6     1     8  [ 6] .dynstr           STRTAB           00000000004002f8  000002f8       0000000000000044  0000000000000000   A       0     0     1  [ 7] .gnu.version      VERSYM           000000000040033c  0000033c       000000000000000a  0000000000000002   A       5     0     2  [ 8] .gnu.version_r    VERNEED          0000000000400348  00000348       0000000000000020  0000000000000000   A       6     1     8  [ 9] .rela.dyn         RELA             0000000000400368  00000368       0000000000000018  0000000000000018   A       5     0     8  [10] .rela.plt         RELA             0000000000400380  00000380       0000000000000048  0000000000000018   A       5    12     8  [11] .init             PROGBITS         00000000004003c8  000003c8       0000000000000018  0000000000000000  AX       0     0     4  [12] .plt              PROGBITS         00000000004003e0  000003e0       0000000000000040  0000000000000010  AX       0     0     4  [13] .text             PROGBITS         0000000000400420  00000420       0000000000000238  0000000000000000  AX       0     0     16  [14] .fini             PROGBITS         0000000000400658  00000658       000000000000000e  0000000000000000  AX       0     0     4  [15] .rodata           PROGBITS         0000000000400668  00000668       0000000000000053  0000000000000000   A       0     0     8  [16] .eh_frame_hdr     PROGBITS         00000000004006bc  000006bc       0000000000000024  0000000000000000   A       0     0     4  [17] .eh_frame         PROGBITS         00000000004006e0  000000000000007c  0000000000000000   A       0     0     8  [18] .ctors            PROGBITS         0000000000600760  00000760       0000000000000010  0000000000000000  WA       0     0     8  [19] .dtors            PROGBITS         0000000000600770  00000770       0000000000000010  0000000000000000  WA       0     0     8  [20] .jcr              PROGBITS         0000000000600780  00000780       0000000000000008  0000000000000000  WA       0     0     8  [21] .dynamic          DYNAMIC          0000000000600788  00000788       0000000000000190  0000000000000010  WA       6     0     8  [22] .got              PROGBITS         0000000000600918  00000918       0000000000000008  0000000000000008  WA       0     0     8  [23] .got.plt          PROGBITS         0000000000600920  00000920       0000000000000030  0000000000000008  WA       0     0     8  [24] .data             PROGBITS         0000000000600950  00000950       0000000000000008  0000000000000000  WA       0     0     4  [25] .bss              NOBITS           0000000000600958  00000958       0000000000000018  0000000000000000  WA       0     0     8  [26] .comment          PROGBITS         0000000000000000  00000958       000000000000002c  0000000000000001  MS       0     0     1  [27] .shstrtab         STRTAB           0000000000000000  00000984       00000000000000fe  0000000000000000           0     0     1  [28] .symtab           SYMTAB           0000000000000000  00001208       0000000000000648  0000000000000018          29    46     8  [29] .strtab           STRTAB           0000000000000000  00001850       000000000000021e  0000000000000000           0     0     1Key to Flags:  W (write), A (alloc), X (execute), M (merge), S (strings)  I (info), L (link order), G (group), x (unknown)  O (extra OS processing required) o (OS specific), p (processor specific)

The .text section stores the program’s code (binary instructions), and the flag for this section is X, indicating it is executable.

Next, let’s use objdump to disassemble and view the contents of the .text section:

$ objdump -d -j .text test

The -d option tells objdump to disassemble the machine code, and the -j option specifies that we are only interested in the .text section.

test:     file format elf64-x86-64

Disassembly of section .text:
0000000000400420 <_start>:  400420:       31 ed                   xor    %ebp,%ebp  400422:       49 89 d1                mov    %rdx,%r9  400425:       5e                      pop    %rsi  400426:       48 89 e2                mov    %rsp,%rdx  400429:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp  40042d:       50                      push   %rax  40042e:       54                      push   %rsp  40042f:       49 c7 c0 80 05 40 00    mov    $0x400580,%r8  400436:       48 c7 c1 90 05 40 00    mov    $0x400590,%rcx  40043d:       48 c7 c7 04 05 40 00    mov    $0x400504,%rdi  400444:       e8 c7 ff ff ff          callq  400410 <__libc_start_main@plt>  400449:       f4                      hlt      40044a:       90                      nop  40044b:       90                      nop
000000000040044c <call_gmon_start>:  40044c:       48 83 ec 08             sub    $0x8,%rsp  400450:       48 8b 05 c1 04 20 00    mov    0x2004c1(%rip),%rax        # 600918 <_DYNAMIC+0x190>  400457:       48 85 c0                test   %rax,%rax  40045a:       74 02                   je     40045e <call_gmon_start+0x12>  40045c:       ff d0                   callq  *%rax  40045e:       48 83 c4 08             add    $0x8,%rsp  400462:       c3                      retq     400463:       90                      nop  400464:       90                      nop  400465:       90                      nop  400466:       90                      nop  400467:       90                      nop  400468:       90                      nop  400469:       90                      nop  40046a:       90                      nop  40046b:       90                      nop  40046c:       90                      nop  40046d:       90                      nop  40046e:       90                      nop  40046f:       90                      nop
0000000000400470 <__do_global_dtors_aux>:  400470:       55                      push   %rbp  400471:       48 89 e5                mov    %rsp,%rbp  400474:       53                      push   %rbx  400475:       48 83 ec 08             sub    $0x8,%rsp  400479:       80 3d d8 04 20 00 00    cmpb   $0x0,0x2004d8(%rip)        # 600958 <__bss_start>  400480:       75 4b                   jne    4004cd <__do_global_dtors_aux+0x5d>  400482:       bb 78 07 60 00          mov    $0x600778,%ebx  400487:       48 8b 05 d2 04 20 00    mov    0x2004d2(%rip),%rax        # 600960 <dtor_idx.6349>  40048e:       48 81 eb 70 07 60 00    sub    $0x600770,%rbx  400495:       48 c1 fb 03             sar    $0x3,%rbx  400499:       48 83 eb 01             sub    $0x1,%rbx  40049d:       48 39 d8                cmp    %rbx,%rax  4004a0:       73 24                   jae    4004c6 <__do_global_dtors_aux+0x56>  4004a2:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)  4004a8:       48 83 c0 01             add    $0x1,%rax  4004ac:       48 89 05 ad 04 20 00    mov    %rax,0x2004ad(%rip)        # 600960 <dtor_idx.6349>  4004b3:       ff 14 c5 70 07 60 00    callq  *0x600770(,%rax,8)  4004ba:       48 8b 05 9f 04 20 00    mov    0x20049f(%rip),%rax        # 600960 <dtor_idx.6349>  4004c1:       48 39 d8                cmp    %rbx,%rax  4004c4:       72 e2                   jb     4004a8 <__do_global_dtors_aux+0x38>  4004c6:       c6 05 8b 04 20 00 01    movb   $0x1,0x20048b(%rip)        # 600958 <__bss_start>  4004cd:       48 83 c4 08             add    $0x8,%rsp  4004d1:       5b                      pop    %rbx  4004d2:       c9                      leaveq   4004d3:       c3                      retq     4004d4:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)  4004db:       00 00 00 00 00 
00000000004004e0 <frame_dummy>:  4004e0:       48 83 3d 98 02 20 00    cmpq   $0x0,0x200298(%rip)        # 600780 <__JCR_END__>  4004e7:       00   4004e8:       55                      push   %rbp  4004e9:       48 89 e5                mov    %rsp,%rbp  4004ec:       74 12                   je     400500 <frame_dummy+0x20>  4004ee:       b8 00 00 00 00          mov    $0x0,%eax  4004f3:       48 85 c0                test   %rax,%rax  4004f6:       74 08                   je     400500 <frame_dummy+0x20>  4004f8:       bf 80 07 60 00          mov    $0x600780,%edi  4004fd:       c9                      leaveq   4004fe:       ff e0                   jmpq   *%rax  400500:       c9                      leaveq   400501:       c3                      retq     400502:       90                      nop  400503:       90                      nop
0000000000400504 <main>:  400504:       55                      push   %rbp  400505:       48 89 e5                mov    %rsp,%rbp  400508:       48 83 ec 20             sub    $0x20,%rsp  40050c:       89 7d ec                mov    %edi,-0x14(%rbp)  40050f:       48 89 75 e0             mov    %rsi,-0x20(%rbp)  400513:       c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)  40051a:       bf 78 06 40 00          mov    $0x400678,%edi  40051f:       e8 dc fe ff ff          callq  400400 <puts@plt>  400524:       8b 15 2a 04 20 00       mov    0x20042a(%rip),%edx        # 600954 <global_data>  40052a:       b8 84 06 40 00          mov    $0x400684,%eax  40052f:       89 d6                   mov    %edx,%esi  400531:       48 89 c7                mov    %rax,%rdi  400534:       b8 00 00 00 00          mov    $0x0,%eax  400539:       e8 b2 fe ff ff          callq  4003f0 <printf@plt>  40053e:       8b 15 24 04 20 00       mov    0x200424(%rip),%edx        # 600968 <global_data_2>  400544:       b8 96 06 40 00          mov    $0x400696,%eax  400549:       89 d6                   mov    %edx,%esi  40054b:       48 89 c7                mov    %rax,%rdi  40054e:       b8 00 00 00 00          mov    $0x0,%eax  400553:       e8 98 fe ff ff          callq  4003f0 <printf@plt>  400558:       b8 aa 06 40 00          mov    $0x4006aa,%eax  40055d:       8b 55 fc                mov    -0x4(%rbp),%edx  400560:       89 d6                   mov    %edx,%esi  400562:       48 89 c7                mov    %rax,%rdi  400565:       b8 00 00 00 00          mov    $0x0,%eax  40056a:       e8 81 fe ff ff          callq  4003f0 <printf@plt>  40056f:       b8 00 00 00 00          mov    $0x0,%eax  400574:       c9                      leaveq   400575:       c3                      retq     400576:       90                      nop  400577:       90                      nop  400578:       90                      nop  400579:       90                      nop  40057a:       90                      nop  40057b:       90                      nop  40057c:       90                      nop  40057d:       90                      nop  40057e:       90                      nop  40057f:       90                      nop
0000000000400580 <__libc_csu_fini>:  400580:       f3 c3                   repz retq   400582:       66 66 66 66 66 2e 0f    data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)  400589:       1f 84 00 00 00 00 00 
0000000000400590 <__libc_csu_init>:  400590:       48 89 6c 24 d8          mov    %rbp,-0x28(%rsp)  400595:       4c 89 64 24 e0          mov    %r12,-0x20(%rsp)  40059a:       48 8d 2d bb 01 20 00    lea    0x2001bb(%rip),%rbp        # 60075c <__init_array_end>  4005a1:       4c 8d 25 b4 01 20 00    lea    0x2001b4(%rip),%r12        # 60075c <__init_array_end>  4005a8:       4c 89 6c 24 e8          mov    %r13,-0x18(%rsp)  4005ad:       4c 89 74 24 f0          mov    %r14,-0x10(%rsp)  4005b2:       4c 89 7c 24 f8          mov    %r15,-0x8(%rsp)  4005b7:       48 89 5c 24 d0          mov    %rbx,-0x30(%rsp)  4005bc:       48 83 ec 38             sub    $0x38,%rsp  4005c0:       4c 29 e5                sub    %r12,%rbp  4005c3:       41 89 fd                mov    %edi,%r13d  4005c6:       49 89 f6                mov    %rsi,%r14  4005c9:       48 c1 fd 03             sar    $0x3,%rbp  4005cd:       49 89 d7                mov    %rdx,%r15  4005d0:       e8 f3 fd ff ff          callq  4003c8 <_init>  4005d5:       48 85 ed                test   %rbp,%rbp  4005d8:       74 1c                   je     4005f6 <__libc_csu_init+0x66>  4005da:       31 db                   xor    %ebx,%ebx  4005dc:       0f 1f 40 00             nopl   0x0(%rax)  4005e0:       4c 89 fa                mov    %r15,%rdx  4005e3:       4c 89 f6                mov    %r14,%rsi  4005e6:       44 89 ef                mov    %r13d,%edi  4005e9:       41 ff 14 dc             callq  *(%r12,%rbx,8)  4005ed:       48 83 c3 01             add    $0x1,%rbx  4005f1:       48 39 eb                cmp    %rbp,%rbx  4005f4:       72 ea                   jb     4005e0 <__libc_csu_init+0x50>  4005f6:       48 8b 5c 24 08          mov    0x8(%rsp),%rbx  4005fb:       48 8b 6c 24 10          mov    0x10(%rsp),%rbp  400600:       4c 8b 64 24 18          mov    0x18(%rsp),%r12  400605:       4c 8b 6c 24 20          mov    0x20(%rsp),%r13  40060a:       4c 8b 74 24 28          mov    0x28(%rsp),%r14  40060f:       4c 8b 7c 24 30          mov    0x30(%rsp),%r15  400614:       48 83 c4 38             add    $0x38,%rsp  400618:       c3                      retq     400619:       90                      nop  40061a:       90                      nop  40061b:       90                      nop  40061c:       90                      nop  40061d:       90                      nop  40061e:       90                      nop  40061f:       90                      nop

Next, let’s use objdump to disassemble and view the contents of the .data section:

$ objdump -d -j .data test

The .data section stores initialized global variables.

Leave a Comment