我似乎无法让这个 16 位内存检测汇编代码工作

I can't seem to get this 16-bit memory detection assembly code to work

前一段时间我在做一个简单的引导加载程序项目,我决定重新开始做它。无论如何,我正在尝试使用 BIOS INT=15H EAX=E820h 检测内存。我在循环中使用了中断,并为内存映射分配了 space 来保存所有条目。现在我试图解析从最后一个条目开始的条目。我的目标是找到我可以用来保存我正在从磁盘读取的文件的最高 1MB 区域。

到目前为止,这是我的代码。它正在 Bochs 2.6.11 上进行测试,内存为 32MB,其他一切都设置为默认设置。当然是16位实模式代码了。

[bits 16]

BlEntry:
    
    [...] ; above here all I did was set all segments to 0, except DS which is set to 7C0h
    
    ;
    ; Build the system's memory map
    ;

    xor ebx, ebx ; HANDLE for next call
    mov edx, 534D4150h ; magic
    mov ecx, 20 ; buffer size
    sub sp, cx ; allocate 20 bytes
    mov di, sp ; di points to buffer
BlpBuildMmBegin:
    mov eax, 0E820h ; magic
    mov [di+20], DWORD 1 ; Request ACPI compat-entry
    int 15h
    jc BlInt10Failure
    cmp eax, edx ; magics should match
    jne BlInt10Failure
    test ebx, ebx ; are we finished?
    je BlpFindEiArea ; jump out
    sub sp, cx ; allocate 20 bytes again
    mov di, sp ; setup di again
    jmp BlpBuildMmBegin ; do it all again
BlpBuildMmEnd:
     
    ;
    ; Load the EI into memory
    ;
    
    ; try to find the highest address we can map the EI to
BlpFindEiAreaStart:
    add di, 20 ; move onto the next entry
    cmp di, bp
    je BlNoMemoryFailure
BlpFindEiArea:
    cmp [di+16], DWORD 1 ; check if we can use this memory region
    jne BlpFindEiAreaStart ; of not, try again
    cmp [di+8], DWORD 100000h ; 1MB EI file size
    jge BlpFindEiAreaEnd
    jmp BlpFindEiAreaStart
BlpFindEiAreaEnd:
    mov eax, DWORD [di]
    cli
    hlt ;hang the system for now, I still need to add more functionality here

如代码所示,当我遍历所有内容时,执行跳转到 BlNoMemoryFailure,它仅使用电传打字机输出显示 No Memory! 输出,然后挂起系统。这就是问题所在 - 我无法让这段代码停止说出消息。我的结构错了吗?我在编写代码时引用了这个网站 http://www.uruk.org/orig-grub/mem64mb.html

最初int 0x15, eax=0xE820 return编辑了一个 20 字节的结构。这被 ACPI 版本扩展到 24 个字节(我认为它是 ACPI 3.0,但没有检查并且可能是错误的)向结构引入了一个新的“标志”字段。

此代码在堆栈上为 20 字节结构分配 space(没有额外的标志字段):

    mov ecx, 20 ; buffer size
    sub sp, cx ; allocate 20 bytes

mov [di+20], DWORD 1 ; Request ACPI compat-entry 预先设置了不在原始 20 字节中的额外标志字段,并破坏了堆栈,因为只分配了 20 字节。

int 15h 导致 cx 中的值(最大缓冲区大小)被替换为“实际数据大小 returned”。

循环结束后的 sub sp, cx 释放了 int 0x15 return 编辑的数据字​​节数,这可能与原来的最大缓冲区大小完全不同实际分配;可能会再次破坏堆栈(特别是如果您尝试通过将 mov ecx, 20 替换为 mov ecx, 24 来解决以前的问题)。

另请注意,BIOS 可以通过两种不同的方式处理“到达列表末尾”;并且 returning ebx = 0 用于列表中的最后一个条目只是一种可能性。另一种可能性是 BIOS return 在 ebx 中的最后一个条目的非零值,然后 return 当您尝试使用该值获取条目后出现错误最后一个条目。出于这个原因,你不能只做 jc BlInt10Failure.

用于可靠地检测“到达循环结束”;我建议做一个初始 int 0x15 以获得第一个执行 jc BlInt10Failure 的条目,然后循环获取执行 jc BlpFindEiArea 的所有剩余条目(换句话说,“失败" 在第一个条目被视为“列表结尾”而不是失败之后)。

请注意,如果您确实使用初始 int 0x15 来获取第一个条目,那么这也可以确定您使用的 BIOS 是 return 的 20 字节结构还是24 字节(或更大)结构;这意味着您可以有 2 个单独的循环,其中一个循环不打扰预先设置额外的标志字段(因为它知道它没有被使用),另一个不打扰预先设置额外的标志字段(因为它知道它会被设置)。如果您的代码“非常防御”并检查数据 returned 是否正常(例如,“类型”字段不是不可能的值),它也很有用; and/or 如果您想跟踪内存映射的来源(例如,使用某种 enum 表示“较新的 0xE820”、“较旧的 0xE820”或其他大约 8 个较旧的替代方案中的任何一个用于 BIOS 或 UEFI)。

一旦你有了一个条目列表,你可能不应该盲目地相信它。最好在排序列表时检查“类型”字段中的未知值(并用单个“unknown/reserved”值替换它们)(因此它不是随机顺序),同时检测是否有任何报告重叠的区域(并有代码通过找到用于每种可能性的最不危险的替代“类型”来处理“重叠但报告为不同类型”的情况),同时丢弃任何具有“大小= 0字节”的条目(这可能发生- 例如 BIOS 使用静态定义的条目号和..)。请注意,不同的计算机有不同的错误,在某些情况下 int 0x15 会被其他东西“钩住”(例如 PXE/network 引导 ROM 通常将 int 0x15 重定向到它自己的代码以隐藏内存ROM本身正在使用)。

我也不相信 int 0x15, eax=0xE820 会不修改各种值。例如,我不会假设它不会修改 ebpedx 中的值(即使它不应该),或者(如果缓冲区大小更大)它不会' t 覆盖 [es:di+20] 处的值,但仍然只有 return 20 个字节(即使它不应该),或者它不会 return carry = clear, ah = error code because the function failed(即使它应该't).

最后;当您搜索生成的(经过良好排序和完整性检查的)内存映射时; “区域地址”和“区域大小”字段是 64 位的,所以你不能只比较最低的 32 位(并且你不应该使用 jge 当 是有符号数时 - 使用 jae代替无符号数)。换句话说,这个:

 cmp [di+8], DWORD 100000h 
 jge BlpFindEiAreaEnd

..应该是:

 cmp dword [di+8+4], 0          ;Is size >= 1 MiB?
 ja BlpFindEiAreaEnd            ; yes
 cmp dword [di+8], 0x00100000   ;Is size >= 1 MiB?
 jae BlpFindEiAreaEnd           ; yes
                                ; no

..但我不知道你为什么要检查“大小 >= 1 MiB”,当你说你正在寻找最高可用地址的 RAM(并怀疑这是另一个错误)时。