Where are segments getting copied to and from in parse_elf() in the Linux kernel?

Viewed 123

I am looking for some help understanding the parsing of the decompressed Linux image in parse_elf() in arch/x86/boot/compressed/misc.c. Specifically, I don't understand what memory regions the ELF segments are getting copied to and from. Below is some annotated code showing my (mis)understanding.

for (i = 0; i < ehdr.e_phnum; i++) {  // For each segment... 
    phdr = &phdrs[i];

    switch (phdr->p_type) {  // Ignore all segments that
    case PT_LOAD:            // aren't labeled as loadable
#ifdef CONFIG_RELOCATABLE
        dest = output;  // Set `dest` to be equal to the base of the kernel
                        // after decompression and KASLR considerations

        // Next, add to `dest` the difference between the physical address
        // of the segment and where the kernel was told to be loaded by the
        // kernel configuration file. It seems to me that this difference
        // is equal to `phdr->p_offset`.
        dest += (phdr->p_paddr - LOAD_PHYSICAL_ADDR);
#else
        // If we aren't considering relocations then just use the physical
        // address of the segment as the destination.
        dest = (void *)(phdr->p_paddr);
#endif
        // Copy, to the destination determined above, from the beginning
        // of the decompressed kernel plus the offset to the segment until
        // the end of the segment is reached.
        memcpy(dest,
               output + phdr->p_offset,
               phdr->p_filesz);
        break;
    default: /* Ignore other PT_* */ break;
    }
}

What is confusing is that in the case of relocation, memcpy's first and second argument seem like they will be the same and there is no point in calling parse_elf. Perhaps I am misunderstanding what LOAD_PHYSICAL_ADDR or phdr->p_paddr is, or the steps taken after the kernel is decompressed in place.

The non-relocation case makes more sense as we just need to copy from the decompressed kernel to a "hard-coded" address.

1 Answers

Some definitions:

LOAD_PHYSICAL_ADDR - compiled base of kernel
p_offset - start of segment in decompressed kernel image
p_paddr - place where we want to put this segment

Since there are other segments than loadable segments, if p_offset = p_paddr there would be unused holes in kernel memory. The p_paddr skips the holes so is always equal or less than the p_offset. That way we can start at the first segment and copy downwards to pack the segments to their intended positions. Note that the first segment, or even several segments, might not move.

To further explain where these values come from, p_offset is the file position of the segment. Since the file is loaded sequentially into memory, is also the offset into the decompressed image. p_paddr is the address assigned by the compile+link to the start of a code or data segment.

Ref: https://linux.die.net/man/5/elf

Related