Question

I'm currently building a toy OS using a custom linker script to create the binary :

ENTRY(entry_point)

/* base virtual address of the kernel */

VIRT_BASE = 0xFFFFFFFF80000000;

SECTIONS
{
    . = 0x100000;

    /*
     * Place multiboot header at 0x10000 as it is where Grub will be looking
     * for it.
     * Immediately followed by the boot code
     */

    .boot :
    {
        *(.mbhdr)
        _load_start = .;
        *(.boot)

        . = ALIGN(4096);

        /* reserve space for paging data structures */

        pml4 = .;
        . += 0x1000;
        pdpt = .;
        . += 0x1000;
        pagedir = .;
        . += 0x1000;
        . += 0x8000;

        /* stack segment for loader */

        stack = .;
    }

    /*
     * Kernel code section is placed at his virtual address
     */

    . += VIRT_BASE;

    .text ALIGN(0x1000) : AT(ADDR(.text) - VIRT_BASE)
    {
        *(.text)
        *(.gnu.linkonce.t*)
    }

    .data ALIGN(0x1000) : AT(ADDR(.data) - VIRT_BASE)
    {
        *(.data)
        *(.gnu.linkonce.d*)
    }

    .rodata ALIGN(0x1000) : AT(ADDR(.rodata) - VIRT_BASE)
    {
        *(.rodata*)
        *(.gnu.linkonce.r*)
    }

    _load_end = . - VIRT_BASE;

    .bss ALIGN(0x1000) : AT(ADDR(.bss) - VIRT_BASE)
    {
        *(COMMON)
        *(.bss)
        *(.gnu.linkonce.b*)
    }

    _bss_end = . - VIRT_BASE;

    /DISCARD/ :
    {
        *(.comment)
        *(.eh_frame)
    }
}

Since I use virtual memory, I use the AT() directive to make a distinction between the relocation address (the value of the symbol) and the actual physical load address, because virtual memory is not enabled when the binary is loaded. The address I give to AT correspond to the physical address mapped to the virtual relocation address. this works well.

But in some cases, I notice there is a strange shift after my code is loaded in memory. the .text section (which does not include the assembly boot code, only C++ code) is located 8 bytes higher than where it should be. An objdump shows the virtual relocation address is correct in the binary, as well as the load address. The only visible change is in readelf -l :

Elf file type is EXEC (Executable file)
Entry point 0x10003c
There are 3 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x0000e8 0x0000000000100000 0x0000000000100000 0x00c000 0x00c000 R   0x8
  LOAD           0x00c0f0 0xffffffff8010c000 0x000000000010c000 0x004032 0x005002 RWE 0x10
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x8

 Section to Segment mapping:
  Segment Sections...
   00     .boot
   01     .text .text._ZN2io3outEth .data .rodata .bss
   02

The ALIGN of my text section is 0x10 here, whereas it is 0x8 when everything works properly.

Why is this align only present in some cases ? e.g. when I use this kind of C++ static initialization :

struct interrupt_desc
{
    uint16_t clbk_low  = 0x0;
    uint16_t selector  = 0x08;
    uint8_t  zero      = 0x0;
    uint8_t  flags     = 0xE;
    uint16_t clbk_mid  = 0x0;
    uint32_t clbk_high = 0x0;
    uint32_t zero2     = 0x0;
} __attribute__((packed));

interrupt_desc bar[32];

or when I implement these assembly structures for IDT handling, if I put them in the .data section, everything if fine, but if they go to the .text, the align is there again :

[SECTION .data]
[GLOBAL _interrupt_table_register]
[GLOBAL _interrupt_vector_table]
[BITS 64]

align 8

_interrupt_table_register:
    DW  0xABCD
    DQ  _interrupt_vector_table

_interrupt_vector_table:
    %rep    256
        DW      0x0000
        DW      0x0008
        DB      0x00
        DB      0x0E
        DW      0x0000
        DD      0x00000000
        DD      0x00000000
    %endrep

And finally, I don't really understand what this align value means and how I can adjust it. Moreover the load address is already 0x10 bytes-aligned, so why does this align changes the effective load address ?

Maybe I fail to understand something important here so any explanation about the internals of ELF is very welcome.

In case anyone wonders, the binary is an ELF64 linked with GNU ld, booted by Grub2 using Multiboot2.

PS: there is nothing wrong with my virtual memory mappings as they work perfectly when the align is 8. also, the shift is present event when I dump physical memory.

also, previously, I used to use Rust for the main code instead of C++. With Rust, this align bug was always present since the implementation of LTO in the compiler, but fine previously.

Thanks for your answers.

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top