overwriter
overwriter

Reputation: 61

ELF64 loading & memory alignment

Any experts with a deep understanding of ELF loading, could you please explain to me why the following ELF file throws a Segmentation fault (errno=139)?

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:               0x131a
  Start of program headers:          64 (bytes into file)
  Start of section headers:          232 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         8
  Section header string table index: 7

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] null              NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .init             PROGBITS         000000000000131a  0000031a
       0000000000000001  0000000000000000  AX       0     0     1
  [ 2] .text             PROGBITS         000000000000131b  0000031b
       0000000000000096  0000000000000000  AX       0     0     1
  [ 3] .fini             PROGBITS         00000000000013b1  000003b1
       0000000000000001  0000000000000000  AX       0     0     1
  [ 4] .rodata           PROGBITS         00000000000013b2  000003b2
       0000000000000014  0000000000000000   A       0     0     1
  [ 5] .data             PROGBITS         00000000000013c6  000003c6
       000000000000001e  0000000000000000   A       0     0     1
  [ 6] .bss              NOBITS           00000000000013e4  000003e4
       0000000000000000  0000000000000000  WA       0     0     1
  [ 7] strtab            STRTAB           00000000000012e8  000002e8
       0000000000000032  0000000000000000  AS       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x000000000000031a 0x000000000000131a 0x000000000000131a
                 0x0000000000000098 0x0000000000000098  R E    0x1000
  LOAD           0x00000000000003b2 0x00000000000013b2 0x00000000000013b2
                 0x0000000000000014 0x0000000000000014  R      0x1000
  LOAD           0x00000000000003c6 0x00000000000013c6 0x00000000000013c6
                 0x000000000000001e 0x000000000000101e  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .init .text .fini
   01     .rodata
   02     .data .bss

The exact same executable with the following file alignment changes works fine:

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:               0x10400
  Start of program headers:          64 (bytes into file)
  Start of section headers:          232 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         8
  Section header string table index: 7

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] null              NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .init             PROGBITS         0000000000010400  00000400
       0000000000000001  0000000000000000  AX       0     0     1
  [ 2] .text             PROGBITS         0000000000010800  00000800
       0000000000000096  0000000000000000  AX       0     0     1
  [ 3] .fini             PROGBITS         0000000000010c00  00000c00
       0000000000000001  0000000000000000  AX       0     0     1
  [ 4] .rodata           PROGBITS         0000000000011000  00001000
       0000000000000014  0000000000000000   A       0     0     1
  [ 5] .data             PROGBITS         0000000000011400  00001400
       000000000000001e  0000000000000000   A       0     0     1
  [ 6] .bss              NOBITS           0000000000011800  00001800
       0000000000000000  0000000000000000  WA       0     0     1
  [ 7] strtab            STRTAB           00000000000102e8  000002e8
       0000000000000032  0000000000000000  AS       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000400 0x0000000000010400 0x0000000000010400
                 0x0000000000000801 0x0000000000000801  R E    0x1
  LOAD           0x0000000000001000 0x0000000000011000 0x0000000000011000
                 0x0000000000000014 0x0000000000000014  R      0x1
  LOAD           0x0000000000001400 0x0000000000011400 0x0000000000011400
                 0x000000000000001e 0x000000000000101e  RW     0x1

 Section to Segment mapping:
  Segment Sections...
   00     .init .text .fini
   01     .rodata
   02     .data .bss

In both cases it holds that:

I appreciate your help - thank you very much in advance.

UPDATE: I realized that my LOAD segments were overlapping in virtual memory in the first readelf printout that I posted. I have corrected this now, but for the now non-overlapping LOAD segments I still get a segmentation fault when my start virtual memory address for the first page is at 0x0 (same if it is at 0x1000, i.e. one page size higher):

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:               0x31a
  Start of program headers:          64 (bytes into file)
  Start of section headers:          232 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         8
  Section header string table index: 7

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] null              NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .init             PROGBITS         000000000000031a  0000031a
       0000000000000001  0000000000000000  AX       0     0     0
  [ 2] .text             PROGBITS         000000000000031b  0000031b
       0000000000000076  0000000000000000  AX       0     0     0
  [ 3] .fini             PROGBITS         0000000000000391  00000391
       0000000000000001  0000000000000000  AX       0     0     0
  [ 4] .rodata           PROGBITS         0000000000001392  00000392
       0000000000000014  0000000000000000   A       0     0     0
  [ 5] .data             PROGBITS         00000000000023a6  000003a6
       000000000000001e  0000000000000000   A       0     0     0
  [ 6] .bss              NOBITS           00000000000023c4  000003c4
       0000000000000000  0000000000000000  WA       0     0     0
  [ 7] strtab            STRTAB           00000000000002e8  000002e8
       0000000000000032  0000000000000000  AS       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x000000000000031a 0x000000000000031a 0x000000000000031a
                 0x0000000000000078 0x0000000000000078  R E    0x1000
  LOAD           0x0000000000000392 0x0000000000001392 0x0000000000001392
                 0x0000000000000014 0x0000000000000014  R      0x1000
  LOAD           0x00000000000003a6 0x00000000000023a6 0x00000000000023a6
                 0x000000000000001e 0x0000000000000082  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .init .text .fini
   01     .rodata
   02     .data .bss

When I change the start address to 0x10000 (PAGESIZE * 16), then the segmentation fault disappears. Any ideas why that is?

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:               0x1031a
  Start of program headers:          64 (bytes into file)
  Start of section headers:          232 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         8
  Section header string table index: 7

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] null              NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .init             PROGBITS         000000000001031a  0000031a
       0000000000000001  0000000000000000  AX       0     0     0
  [ 2] .text             PROGBITS         000000000001031b  0000031b
       0000000000000076  0000000000000000  AX       0     0     0
  [ 3] .fini             PROGBITS         0000000000010391  00000391
       0000000000000001  0000000000000000  AX       0     0     0
  [ 4] .rodata           PROGBITS         0000000000011392  00000392
       0000000000000014  0000000000000000   A       0     0     0
  [ 5] .data             PROGBITS         00000000000123a6  000003a6
       000000000000001e  0000000000000000   A       0     0     0
  [ 6] .bss              NOBITS           00000000000123c4  000003c4
       0000000000000000  0000000000000000  WA       0     0     0
  [ 7] strtab            STRTAB           00000000000002e8  000002e8
       0000000000000032  0000000000000000  AS       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x000000000000031a 0x000000000001031a 0x000000000001031a
                 0x0000000000000078 0x0000000000000078  R E    0x1000
  LOAD           0x0000000000000392 0x0000000000011392 0x0000000000011392
                 0x0000000000000014 0x0000000000000014  R      0x1000
  LOAD           0x00000000000003a6 0x00000000000123a6 0x00000000000123a6
                 0x000000000000001e 0x0000000000000082  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .init .text .fini
   01     .rodata
   02     .data .bss

UPDATE 2: Thank you Employed Russian for your answer and ideas. I wanted to share the following update on my own research:

After digging a bit more, I ran across the following line in an Oracle document about program loading:

By default, 64–bit SPARC programs are linked with a starting address of 0x100000000. The whole program is located above 4 gigabytes, including its text, data, heap, stack, and shared object dependencies. This helps ensure that 64–bit programs are correct because the program will fault in the least significant 4 gigabytes of its address space if the program truncates any of its pointers. While 64–bit programs are linked above 4 gigabytes, you can still link programs below 4 gigabytes by using a mapfile and the -M option to the link-editor. See /usr/lib/ld/sparcv9/map.below4G.

(Source: https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter6-34713/index.html)

Now I am aware the information from that link is awfully specific, but I was nonetheless wondering if there could be some more universal truth to this on other platforms, or at least point me in the right direction.

So I wrote a tiny test program in C and compiled it in two different ways:

  1. gcc test.c - ELF type is ET_DYN / shared object file and no default virtual address offset is used for the LOAD segments:
Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0005c8 0x0005c8 R   0x1000
  LOAD           0x001000 0x0000000000001000 0x0000000000001000 0x0001c5 0x0001c5 R E 0x1000
  LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x000130 0x000130 R   0x1000
  LOAD           0x002df0 0x0000000000003df0 0x0000000000003df0 0x000220 0x000228 RW  0x1000
  1. gcc -static test.c - ELF type is ET_EXEC / executable and default virtual address offset of 0x400000 is used:
Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x000518 0x000518 R   0x1000
  LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x0936dd 0x0936dd R E 0x1000
  LOAD           0x095000 0x0000000000495000 0x0000000000495000 0x02664d 0x02664d R   0x1000
  LOAD           0x0bc0c0 0x00000000004bd0c0 0x00000000004bd0c0 0x005170 0x0068c0 RW  0x1000

Any ideas why that is? I know it possibly has to do with position-independent code, but I do not understand the necessity for an offset if absolute code is used (as in 2. above). Thanks.

Upvotes: 3

Views: 473

Answers (1)

Employed Russian
Employed Russian

Reputation: 213955

When I change the start address to 0x10000 (PAGESIZE * 16), then the segmentation fault disappears. Any ideas why that is?

This was mentioned in the comments to this answer:

Why does loading at 0x10000 work but at 0x1000 doesn't? Does this depend on the kernel or the hardware? How do I pick the right number here?

Some code in the kernel doesn't like to use addresses below 0x10000, but I have not found that code.

I've tried to load a binary with first PT_LOAD.p_vaddr == 0x1000 into UML kernel (which is easy to debug), but that actually worked, so specific kernel code which prohibits this may be architecture-dependent.

Upvotes: 2

Related Questions