assembly 如何重新Map内核

zf2sa74q  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(130)

这是一个理论问题,因为我似乎找不到任何参考资料,如何做到这一点。
我正在写一个小内核,我已经有虚拟内存工作。
我已经定义了我的内存Map(灵感来自https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt),现在我想设置布局。
我有以下函数将物理页Map到虚拟地址:

void mapMemory(void* virtualMemory, void* physicalMemory)

给定由以下结构定义的任意内存区域(示例中为 somebuffer):

struct region {
    void * base;
    uint64_t size;
};

我可以将物理页重新Map到任意地址(示例中为 OFFSET

uint64_t address = somebuffer.base;
uint64_t size    = somebuffer.size;

uint64_t index = 0;
for (uint64_t i = address; i < (address+size); i += 0x1000) {
    mapMemory((void*)OFFSET+index, (void*)i);
    index += 0x1000;
}

然后手动修复指针,如下所示:

somebuffer.base = (void*)OFFSET;

这样我仍然可以访问 somebuffer
我想要的是重新Map整个运行时(内核段、堆栈、EFI运行时服务等),但是我缺少 osdev 理论的这一部分。
到目前为止,我对如何解决这个问题有一些想法:

  • 对于堆栈:只需使用嵌入式汇编将偏移量添加到rbprsp即可。
  • 对于内核:修改 GDT 中的条目。
  • 对于内核:改变段寄存器并进行 * 远跳转 *。

先谢谢你!
P.S.您可以在下面的答案中选择一个工作解决方案。

2vuwiymt

2vuwiymt1#

堆栈是相对的,因此不需要添加堆栈偏移量或其他任何东西。对堆栈的要求取决于是否使用帧指针。使用gcc,可以指定不使用-fomit-frame-pointer的帧指针。与此选项的区别在于,将使用相对于RSP的正偏移量来访问局部变量。如果使用帧指针,则将使用相对于RBP的负偏移量来访问局部变量。
在内核中改变堆栈位置的问题与堆栈帧的分配有关。如果编译器为主函数分配了一定数量的堆栈,而你用一个太小的堆栈帧改变了主函数中的堆栈位置,那么这将破坏你的内核。问题是代码本身将访问堆栈的一个区域,而该区域不应该在堆栈帧之外访问(可能是没有分页或者包含了其他一些重要的代码/数据)。另外,当调用另一个函数时,新函数可能会覆盖主函数已经初始化并使用的一些数据。
同样,如果你改变了main函数中堆栈的位置,那么你需要确保之前初始化的任何东西都不会在函数中被进一步使用。这是因为你的堆栈现在在其他地方,所以你不能认为在堆栈帧中找到的数据是相同的(除非你手动重建它)。
通常,这是分页的本质。你可以改变你的堆栈在虚拟内存中的位置,并让该区域指向物理内存中的任何位置(包括它的前一个位置)。
内核的位置也不需要改变。通常所做的是调整内核的代码,使其运行在虚拟地址空间的上半部分。内核被加载到物理内存中较低的位置,并调整页表,使内核代码中包含的地址指向该位置。
您提到的其他事情似乎没有意义。如果您修改GDT,它不会对分页产生影响。实际上,GDT可能应该在退出UEFI时进行修改,但它只应该“控制”GDT(知道它的条目号等)。GDT应该仍然代表一个跨越整个地址空间的平面内存模型。无论如何,就我所知道的,GDT中的限制在长模式下被忽略。2你应该只使用分页。
如果你想在UEFI退出时修改GDT,那么你必须修改段寄存器,但是对于CS,你需要一个远返回。长模式不支持远跳转。我不记得细节了,但是你需要新的CS值和远返回指令之前堆栈上的返回地址。
您也无法重新MapUEFI运行时服务,因为一旦退出UEFI环境,这些服务就会消失。UEFI的工作方式类似于小内核。修改执行环境后,小内核会中断(您更改IA32_LSTAR,您更改系统调用编号,您更改GDT,等等)。在任何情况下,如果您希望在自定义内核中使用这些服务,您需要知道UEFI标准中的系统调用编号,并使用内联汇编手动调用它们,这不是很有效。退出UEFI环境后,您最好忘记这些编号,并集中精力从头构建自己的执行环境沿着自己的系统调用和驱动程序。

x3naxklr

x3naxklr2#

我如何解决它:
首先是:谢谢大家的帮助!我的解决方案是基于你们的建议,这是你们的功劳。我发布这篇文章是为了帮助其他人,如果有人最终像我一样。
为了解决这个问题,我首先在我的项目中创建了一个单独的文件夹,并在启用分页之前复制了 Boot 过程中涉及的所有文件。
然后我编辑了我的链接器脚本,如下所示:

OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_start)

SECTIONS
{
    . = 0x8000;
    _BootStart = .;
    .boot : ALIGN(0x1000)
    {
        lib/entry/*.o (.text);
        lib/entry/*.o (.data);
        lib/entry/*.o (.rodata);
        lib/entry/*.o (.bss);
    }
    . = 0x20008000;
    _BootEnd = .;

    . = 0xffffffff80000000;
    .kernel : AT(_BootEnd)
    {
        *(.text)
        *(.data)
        *(.rodata)
        *(COMMON)
        *(.bss)
    }
    . = 0xffffffff9fffffff;

    /DISCARD/ : {
        *(.discard)
        *(.eh_frame)
        *(.eh_frame_hdr)
    }
}

我知道,相当糟糕的链接器脚本...但它确实起了作用。
使用此布局,我的程序将分两部分加载: Boot 和内核。第一个从地址0x8000开始,将跨越接下来的512兆字节。它将在分页之前运行,因此在本例中为VMA=LMA
第二个(内核)将在 Boot 部分结束后立即加载到物理地址,但内存访问将指向虚拟地址0xffffffff80000000及以上。
使用这个链接方案,我只需要在代码的第一部分设置分页,这样MMU就可以将内核的物理地址Map到更高的地址。
几个问题及其解决方法:
启用分页后,内核崩溃:记住标识Map内核的第一部分,否则在设置cr3后会崩溃。我的全局变量仍然Map到更高的地址,即使在 Boot 文件中定义:全局变量通常属于COMMON部分。将它们声明为extern以避免此问题。

相关问题