我似乎无法让这个 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
会不修改各种值。例如,我不会假设它不会修改 ebp
或 edx
中的值(即使它不应该),或者(如果缓冲区大小更大)它不会' 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(并怀疑这是另一个错误)时。
前一段时间我在做一个简单的引导加载程序项目,我决定重新开始做它。无论如何,我正在尝试使用 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
会不修改各种值。例如,我不会假设它不会修改 ebp
或 edx
中的值(即使它不应该),或者(如果缓冲区大小更大)它不会' 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(并怀疑这是另一个错误)时。