Warm tip: This article is reproduced from serverfault.com, please click

x86 16-我似乎无法使此16位内存检测程序集代码正常工作

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

发布于 2020-12-16 22:36:34

不久前,我正在研究一个简单的引导加载程序项目,因此我决定再次开始该项目。无论如何,我正在尝试使用来检测内存BIOS INT=15H EAX=E820h我在循环中使用了中断,并为内存映射分配了空间来容纳所有条目。现在,我尝试解析从最后一个条目开始的条目。我的目标是找到我可以用来保存从磁盘读取的文件的最大1MB区域。

到目前为止,这是我的代码。它已在Bochs 2.6.11上进行了测试,具有32MB的RAM,其他所有设置均为默认设置。当然,它是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时引用了该网站

Questioner
Arush Agarampur
Viewed
11
Brendan 2020-12-17 14:01:06

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

此代码在堆栈上为20字节结构分配空间(没有多余的标志字段):

    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(最大缓冲器大小)与“实际返回数据的大小”所取代。

sub sp, cx循环结束后的after释放了int 0x15返回的许多字节数据,这可能与实际分配的原始最大缓冲区大小完全不同;有可能会损毁堆栈第二时间(特别是如果你想通过更换来解决以前的问题mov ecx, 20mov ecx, 24)。

另请注意,BIOS可以通过2种不同的方式处理“到达列表结尾”。返回ebx = 0列表中的最后一个条目只是一种可能。另一种可能性是BIOSebx为最后一个条目返回一个非零值,然后在你尝试使用该值在最后一个条目之后获取该条目时返回错误。由于这个原因,你不能只是做jc BlInt10Failure

用于可靠地检测到“循环结束”;我建议做一个首字母缩写int 0x15来获取第一个做到的条目jc BlInt10Failure,然后执行循环以获取其余所有要做的条目jc BlpFindEiArea(换句话说,将第一个条目视为“列表末尾”而不是“失败”)视为失败)。

请注意,如果你确实使用首字母int 0x15来获取第一个条目,那么这也可以确定你使用的BIOS是返回20字节结构还是24字节(或更大)结构的BIOS。这意味着你可以有2个单独的循环,其中一个不必理会预先设置Extra Flags字段(因为它知道未使用),而另一个则不必理会预设置Extra Flags字段(因为它知道将要使用)设置)。如果你的代码“非常防御”并检查返回的数据是否合理(例如,“ type”字段不是不可能的值),它也很有用;和/或如果你想跟踪内存映射的来源(例如,使用enum诸如“较新的0xE820”,“较旧的0xE820”之类的名称,

一旦有了条目列表,你可能就不会盲目地信任它。一个好主意是在对列表进行排序(因此它不是随机的)时,检查“类型”字段中的未知值(并用一个“未知/保留”值替换它们),同时检测是否报告了任何区域重叠(并通过找到每种可能性使用的最小危险替代“类型”来使代码具有处理“重叠但报告为不同类型”的情况),同时丢弃任何具有“ size = 0字节”的条目(可能会发生-例如BIOS使用静态定义的条目号和..)。请注意,不同的计算机具有不同的错误,并且在某些情况下int 0x15会被其他东西“钩住”(例如int 0x15

我也不相信会int 0x15, eax=0xE820保留各种值。例如,我不会假定它不会修改ebpor中的值edx(即使不应这样做),或者(如果缓冲区大小较大)它不会在处覆盖该值,[es:di+20]但仍然只返回20字节(即使不应该),也不会返回carry = clear, ah = error code because the function failed(即使不应该)。

最后; 当你搜索生成的(经过良好排序和完整性检查)的内存映射时;“区域地址”和“区域大小”字段是64位的,因此你不能只比较最低的32位(并且你不应该使用jgewhen是带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

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