不久前,我正在研究一个简单的引导加载程序项目,因此我决定再次开始该项目。无论如何,我正在尝试使用来检测内存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时引用了该网站
最初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, 20
了mov 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
保留各种值。例如,我不会假定它不会修改ebp
or中的值edx
(即使不应这样做),或者(如果缓冲区大小较大)它不会在处覆盖该值,[es:di+20]
但仍然只返回20字节(即使不应该),也不会返回carry = clear, ah = error code because the function failed
(即使不应该)。
最后; 当你搜索生成的(经过良好排序和完整性检查)的内存映射时;“区域地址”和“区域大小”字段是64位的,因此你不能只比较最低的32位(并且你不应该使用jge
when是带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”(并且怀疑这是另一个错误)。
我是一个白痴,因为没有意识到
mov [di+20], DWORD 1
堆栈的作用。很棒的详细说明,这对我很有帮助。谢谢!