Kernel text mapping issue
Posted: Sat Aug 04, 2018 11:46 am
I am (still) trying to adhere to the linux kernel standards regarding mapping addresses. With your help I managed to get a better understanding about the linux kernel, but there's still a big issue which I cannot seem to solve and completely understand: The kernel and its text section mapping.
Up until now, I am following the guide for creating a 64-bit kernel using a separate loader. However, in a rather important section in this guide the author used code which is not really helpful: Section Loader, code: The code and the structs members aren't documented, so I have no idea what seg.address or seg.foffset are (did he or she mean the p_offset member from the elf program header? Which address in the program header, p_vaddr or p_paddr?).
Currently the situation is as follows:
I am using the linker script for the kernel, which is similar to linux in structure:As you can see, the addresses used are the same as in linux. However, when I am retrieving the kernel entry point address from the elf header (elf64_ehdr->e_entry), the addresses leads to a completely different instruction when jumping to it via long jump. So obviously I am missing something, but I really can't seem to find the problem.
The values are as follows:
The values above are calculated with the code below:
The kernel entry point is at 0xffffffff80101c68, according to the elf header of the kernel module. When jumping there, the instruction at the address is a completely different one and upon further examination, the address is not even in the text section of the kernel. After a while, I found out that the real entry point address is at 0xffffffff80294c68, so basically it is the address from the entry point stated by the elf header + the offset of the kernel text section in physical memory (0xffffffff80101c68 + 0x193000 = 0xffffffff80294c68). However, simply adding the offset to the entry point does not solve the problem, because all symbol addresses inside the kernel are around 0xffffffff80100000, meaning the physical offset is not in the address.
So I have quite the dilemma, since I cannot simply change the entry point address by adding an offset, because every function is inside the text section starting at 0xffffffff80100000, just as my linker script above intended to. What do I do?
Should I maybe try to create a minimal example and post it on GitHub?
EDIT: Added minimal crash example on GitHub: click here!
In this case, I removed uneccessary code to reproduce the issue, when I compiled the code, the entry point for the kernel was located at 0xffffffff80100af8 according to the elf header, but the actual entry point is at 0xffffffff80293af8, so it is located 0x193000 bytes further away, just as described above.
I am mapping the addresses:
Identity mapping 0x0 (virtual) to 0x0 (physical) with 4MiB length
Mapping of complete memory 0xffff880000000000 (virtual) to 0x0 (physical) with 1GiB length
Kernel text mapping 0xffffffff80000000 (virtual) to 0x0 (physical) with 512MiB length, all according to the Linux documentation.
Up until now, I am following the guide for creating a 64-bit kernel using a separate loader. However, in a rather important section in this guide the author used code which is not really helpful: Section Loader, code:
Code: Select all
#include "elf64.h" // Also requires elf64.c
char* kernel_elf_space[sizeof(elf_file_data_t)];
elf_file_data_t* kernel_elf = (elf_file_data_t*) kernel_elf_space; /* Pointer to elf file structure (remember there is no memory management yet) */
/* This function parses the ELF file and returns the entry point */
void* load_elf_module(multiboot_uint32_t mod_start, multiboot_uint32_t mod_end){
unsigned long err = parse_elf_executable((void*)mod_start, sizeof(elf_file_data_t), kernel_elf); /* Parses ELF file and returns an error code */
if(err == 0){ /* No errors occurred while parsing the file */
for(int i = 0; i < kernel_elf->numSegments; i++){
elf_file_segment_t seg = kernel_elf->segments[i]; /* Load all the program segments into memory */
/* if you want to do relocation you should do so here, */
const void* src = (const void*) (mod_start + seg.foffset); /* though that would require some changes to parse_elf_executable */
memcpy((void*) seg.address, src, seg.flength);
}
return (void*) kernel_elf->entryAddr; /* Finally we can return the entry address */
}
return NULL;
}
Currently the situation is as follows:
I am using the linker script for the kernel, which is similar to linux in structure:
Code: Select all
ENTRY(_entry)
KERNEL_VMA = 0xffffffff80100000;
KERNEL_OFF = 0xffffffff80000000;
SECTIONS
{
. = KERNEL_VMA;
_kernel = .;
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_OFF)
{
_text = .;
*(.text)
_etext = .;
}
...
The values are as follows:
Code: Select all
memmap: 0x0 -> 0x0, flags: 0x3, size: 0x400000
memmap: 0xffff880000000000 -> 0x0, flags: 0x3, size: 0x40000000
memmap: 0xffffffff80000000 -> 0x0, flags: 0x3, size: 0x20000000
Kernel module located at 0x193000 - 0xAA8AF0
Segment [0]: 0xffffffff80000000 -> 0x193000, size: 0x9130b0
Section [1]: 0xffffffff80100000 -> 0x293000: .text, size: 0x5711
Section [2]: 0xffffffff80106000 -> 0x299000: .rodata, size: 0x9ee
Section [3]: 0xffffffff801069f0 -> 0x2999f0: .eh_frame, size: 0x1990
Section [4]: 0xffffffff80109000 -> 0x29c000: .data, size: 0x88
Section [5]: 0xffffffff8010A000 -> 0x29d000: .bss, size: 0x8830
Section [6]: 0xffffffff80113000 -> 0x2a6000: .mm, size: 0x8000b0
Code: Select all
/* Debugging */
void dbg_print_segments(elf64_ehdr_t *elf64_ehdr)
{
elf64_phdr_t *elf64_phdr = get_elf64_phdr(elf64_ehdr);
for(uint32_t i = 0; i < elf64_ehdr->e_phnum; i++) {
elf64_phdr_t *elf64_segment = &elf64_phdr[i];
uint64_t vaddr = elf64_segment->p_vaddr;
uint64_t paddr = void_ptrtu32(elf64_ehdr) + elf64_segment->p_offset;
uint64_t memsz = elf64_segment->p_memsz;
info("Segment [%d]: 0x%016llx -> 0x%llx, Size: %llx",
i, vaddr, paddr, memsz);
}
}
void dbg_print_sections(elf64_ehdr_t *elf64_ehdr)
{
elf64_shdr_t *elf64_shdr = get_elf64_shdr(elf64_ehdr);
/* Index 0 is reserved */
for(uint32_t i = 1; i < elf64_ehdr->e_shnum; i++) {
elf64_shdr_t *elf64_section = &elf64_shdr[i];
char *sname = get_elf64_shdr_name(elf64_ehdr, elf64_section);
uint64_t vaddr = elf64_section->sh_addr;
uint64_t paddr = void_ptrtu32(elf64_ehdr) + elf64_section->sh_offset;
uint64_t memsz = elf64_section->sh_size;
if(sname != NULL) {
info("Section [%d]: 0x%016llx -> 0x%llx: %s, Size: %llx",
i, vaddr, paddr, sname, memsz);
}
}
}
So I have quite the dilemma, since I cannot simply change the entry point address by adding an offset, because every function is inside the text section starting at 0xffffffff80100000, just as my linker script above intended to. What do I do?
Should I maybe try to create a minimal example and post it on GitHub?
EDIT: Added minimal crash example on GitHub: click here!
In this case, I removed uneccessary code to reproduce the issue, when I compiled the code, the entry point for the kernel was located at 0xffffffff80100af8 according to the elf header, but the actual entry point is at 0xffffffff80293af8, so it is located 0x193000 bytes further away, just as described above.
I am mapping the addresses:
Identity mapping 0x0 (virtual) to 0x0 (physical) with 4MiB length
Mapping of complete memory 0xffff880000000000 (virtual) to 0x0 (physical) with 1GiB length
Kernel text mapping 0xffffffff80000000 (virtual) to 0x0 (physical) with 512MiB length, all according to the Linux documentation.