C 内核开发人员:在实模式下设置ES:DI

C 内核开发人员:在实模式下设置ES:DI,c,assembly,x86,real-mode,C,Assembly,X86,Real Mode,我正在开发一个玩具内核,以供娱乐和教育(不是课堂项目)。我正在开始我的内存管理器的工作,所以我尝试在仍然处于实模式时使用int0x15,EAX=E820调用从BIOS获取内存映射。我正在从osdev wiki(在“获取E820内存映射”一节中)调整我的函数。然而,我希望这是一个可以从我的C代码调用的函数,所以我尝试对它进行一些修改。我希望它包含两个参数:一个指针指向存储映射项的位置,另一个指针指向一个整数,该整数将按表中的项数递增 根据wiki,ES:DI需要指向数据应该存储的位置,因此我将第一

我正在开发一个玩具内核,以供娱乐和教育(不是课堂项目)。我正在开始我的内存管理器的工作,所以我尝试在仍然处于实模式时使用
int0x15,EAX=E820
调用从BIOS获取内存映射。我正在从osdev wiki(在“获取E820内存映射”一节中)调整我的函数。然而,我希望这是一个可以从我的C代码调用的函数,所以我尝试对它进行一些修改。我希望它包含两个参数:一个指针指向存储映射项的位置,另一个指针指向一个整数,该整数将按表中的项数递增

根据wiki,
ES:DI
需要指向数据应该存储的位置,因此我将第一个参数分为两个(段选择器,
指针到映射/16
,偏移量,
指针到映射%16
)。下面是C代码的一部分:

typedef struct SMAP_entry {
    unsigned int baseL; // Base address, a QWORD
    unsigned int baseH;
    unsigned int lengthL; // Length, a QWORD
    unsigned int lengthH;
    unsigned int type; // entry type
    unsigned int ACPI; // extra data from ACPI 3.0
} SMAP_entry_t;

SMAP_entry_t data[100];
kprint("Pointer: ");
kprint_int((int) data, 16);
kprint_newline();

int res = 0;
read_mem_map(((int) data) / 16, ((int) data) % 16, &res);
kprint("res: ");
kprint_int(res, 16);
kprint_newline();
以下是我的ASM代码的一部分:

; performs a INT 0x15, eax=0xE820 call to find the memory map
; inputs: the pointer to the data table / 16, the pointer % 16, a pointer to an dword (int) which will be
;       incremented by the number of entries after this function returns.
; preserves: no registers except esi
read_mem_map:
    mov es, [esp + 4]           ; set es to the value of the first argument
    mov di, [esp + 8]           ; set di to the value of the second argument
这就是我粘贴的全部内容,因为程序会出现三重故障并关闭那里的虚拟机。通过移动
ret
命令,我发现函数在第一行就崩溃了。如果我用C注释掉这个调用,那么一切都如您所期望的那样工作


我在谷歌上读到,几乎没有理由直接设置ES:DI,在我找到的代码中,他们将其设置为一个文本。如何设置ES:DI?如果不直接设置,如何使C和ASM以正确的方式交互?

每个段寄存器(在80x86上)都有一个可见部分和几个隐藏字段(段基、段限制和段的属性-读/写、权限级别等)

在保护模式下;加载段寄存器时,CPU使用可见部分作为GDT或LDT的索引,并从该描述符(在GDT或LDT中)加载段的隐藏字段

在实模式下;CPU做了一些完全不同的事情——它只将段基设置为“visible part*16”,不使用任何(GDT,LDT)表

考虑到您正在使用指向数据表的32位指针和32位堆栈指针(例如,
mov-es,[esp+4]
);我假设您的C代码处于32位保护模式。这与实模式完全不兼容,部分原因是段加载的工作方式完全不同,部分原因是默认操作数/地址大小为32位而不是16位

所有BIOS功能均为实模式设计。它们不能在保护模式下使用

基本上;我建议:

  • 将指向数据表的指针作为32位整数/指针(而不是两个单独的16位整数)传递给程序集
  • 调用“go to real mode”(转到实模式)函数(这将有点棘手,因为您还需要从32位堆栈切换到16位堆栈,并且需要16位代码中的“32位返回指令”)
  • 将指向数据表的指针拆分为其段和汇编中的偏移量,并加载段(现在处于实模式时,该段应能正常工作)
  • 调用BIOS功能(当您现在处于实模式时,该功能应能正常工作)
  • 调用“go to protected mode”(转到受保护模式)函数(这也会有点棘手,包括32位代码中的“16位返回指令”)
  • 返回(32位保护模式)调用者

“英特尔系统程序员指南”中包含了从实模式切换到保护模式以及从保护模式切换到实模式的说明。:)

嗯??ES:DI是多条汇编指令的目标地址。为什么在设置ES或DI时会出现问题?我不确定,但是VM在那些MOV命令上崩溃了,所以我认为问题就在那里。我对汇编代码有点陌生,所以我也可能犯了一个愚蠢的错误。我的猜测是,这与我试图设置的数据大小有关,但将强制转换更改为短转换除了生成一些编译器警告之外,没有任何作用。有可能把它们放在一起吗?很难说你被卡在哪里了。最好的资源之一是。请特别看第4章。对于使用
段:[register]
寻址的特定示例,请看一下如果您在64位框上,函数参数不会在堆栈上传递,它们在
RDI、RSI、RDX、RCX、R8和R9中,谢谢!我是为x86编写的,我可能应该提到这一点。在您给出的链接中,许多函数假定ES:DI已经指向某个字符串。你是如何设置的?谢谢,我明白你使用32位指针的意思。然而,我甚至还没有编写转换到保护模式的代码。既然指向数据表的指针是16位,我是否应该将di设置为该指针,而不使用es?我还有其他asm函数使用esp获取参数,它们加载idt进行键盘中断处理(这很有效),所以我认为这不是问题所在。@Zrneey:我很困惑。或者你在保护模式下,有一个IDT和一个32位堆栈指针(ESP)是有意义的;或者你在实模式下,有BIOS自己的IVT(和BIOS键盘IRQ处理程序),没有IDT,16位堆栈指针没有意义,你发现了一个晦涩难懂的编译器,实际上仍然能够生成实模式代码。我猜您正在使用GRUB/multiboot之类的东西(并且处于保护模式,即使您自己没有切换到保护模式,即使您没有意识到)。