Linux 内核中的 parse_elf() 中的段在何处复制?

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

我正在寻找一些帮助来理解 arch/x86/boot/compressed/misc.cparse_elf() 中解压缩的 Linux 图像的解析。具体来说,我不明白 ELF 段被复制到哪些内存区域。下面是一些带注释的代码,显示了我的(错误)理解。

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;
    }
}

令人困惑的是,在重定位的情况下,memcpy 的第一个和第二个参数似乎是相同的,调用 parse_elf 毫无意义。也许我误解了LOAD_PHYSICAL_ADDRphdr->p_paddr是什么,或者内核解压到位后采取的步骤。

非重定位情况更有意义,因为我们只需要从解压缩的内核复制到 "hard-coded" 地址。

一些定义:

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

由于除了可加载段之外还有其他段,如果p_offset = p_paddr 内核内存中会有未使用的空洞。 p_paddr 跳过空洞,因此总是等于或小于 p_offset。这样我们就可以从第一个段开始向下复制以将段打包到它们的预期位置。请注意,第一段,甚至几段,可能不会移动。

为了进一步说明这些值的来源,p_offset 是段的文件位置。由于文件是按顺序加载到内存中的,因此也是解压缩图像的偏移量。 p_paddr 是 compile+link 分配给代码或数据段开始的地址。

参考:https://linux.die.net/man/5/elf