X86 在引导加载程序中加载内核之前检测内存

X86 在引导加载程序中加载内核之前检测内存,x86,osdev,X86,Osdev,在我的引导加载程序中加载内核之前15小时,我尝试检测eax=0xe820 int的内存。 所以我做了一些调查,发现问题出在EDX寄存器上。 我想我已经为加载内核准备好了DL和BX,然后通过调用do_e820将它们销毁了 下面是我的引导加载程序文件bootloader.asm: [bits 16] global _start number_sector db 0 _start: cli xor ax, ax mov ds, ax mov es, ax m

在我的引导加载程序中加载内核之前15小时,我尝试检测eax=0xe820 int的内存。 所以我做了一些调查,发现问题出在EDX寄存器上。 我想我已经为加载内核准备好了DL和BX,然后通过调用do_e820将它们销毁了

下面是我的引导加载程序文件bootloader.asm:

[bits 16]

global _start

number_sector db 0
_start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x8000      ; Stack pointer at SS:SP = 0x0000:0x8000
    mov [BOOT_DRIVE], dl; Boot drive passed to us by the BIOS
    mov dh, 17          ; Number of sectors (kernel.bin) to read from disk
                        ; 17*512 allows for a kernel.bin up to 8704 bytes
    mov bx, 0x9000      ; Load Kernel to ES:BX = 0x0000:0x9000

    call do_E820
    call load_kernel
    call enable_A20

;   call graphics_mode  ; Uncomment if you want to switch to graphics mode 0x13
    lgdt [gdtr]
    mov eax, cr0
    or al, 1
    mov cr0, eax
    jmp CODE_SEG:init_pm

graphics_mode:
    mov ax, 0013h
    int 10h
    ret

load_kernel:
                        ; load DH sectors to ES:BX from drive DL
    push dx             ; Store DX on stack so later we can recall
                        ; how many sectors were request to be read ,
                        ; even if it is altered in the meantime
    mov ah , 0x02       ; BIOS read sector function
    mov al , dh         ; Read DH sectors
    mov ch , 0x00       ; Select cylinder 0
    mov dh , 0x00       ; Select head 0
    mov cl , 0x02       ; Start reading from second sector ( i.e.
                        ; after the boot sector )
    int 0x13            ; BIOS interrupt
    jc disk_error       ; Jump if error ( i.e. carry flag set )
    pop dx              ; Restore DX from the stack
    cmp dh , al         ; if AL ( sectors read ) != DH ( sectors expected )
    jne disk_error      ; display error message
    ret
disk_error:
    mov bx , ERROR_MSG
    call print_string
    hlt

; prints a null - terminated string pointed to by EDX
print_string:
    pusha
    push es                   ;Save ES on stack and restore when we finish

    push VIDEO_MEMORY_SEG     ;Video mem segment 0xb800
    pop es
    xor di, di                ;Video mem offset (start at 0)
print_string_loop:
    mov al , [ bx ] ; Store the char at BX in AL
    mov ah , WHITE_ON_BLACK ; Store the attributes in AH
    cmp al , 0 ; if (al == 0) , at end of string , so
    je print_string_done ; jump to done
    mov word [es:di], ax ; Store char and attributes at current
        ; character cell.
    add bx , 1 ; Increment BX to the next char in string.
    add di , 2 ; Move to next character cell in vid mem.
    jmp print_string_loop ; loop around to print the next char.

print_string_done:
    pop es                    ;Restore ES that was saved on entry
    popa
    ret ; Return from the function

%include "BOOT/a20.inc"
%include "BOOT/gdt.inc"
%include "BOOT/detect_mem.inc"

[bits 32]
init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp
    call 0x9000
    cli
loopend:                                ;Infinite loop when finished
    jmp loopend

[bits 16]
; Variables
ERROR            db "A20 Error!" , 0
ERROR_MSG        db "Error!" , 0
BOOT_DRIVE:      db 0

VIDEO_MEMORY_SEG equ 0xb800
WHITE_ON_BLACK   equ 0x0f

times 510-($-$$) db 0
db 0x55
db 0xAA
我的功能是检测内存detect_mem.inc:

entries equ 0
buffer equ 1

do_E820:
    pushf
    pusha
    mov edi , buffer ;destination buffer
    mov byte [edi+20] , 1  ;Force a vaalid ACPI

.begin:
    xor ebx , ebx
    mov edx , 0x534D4150
    mov eax , 0xE820
    mov ecx , 24
    int 0x15

    jc .failed
    mov edx ,  0x534D4150
    cmp eax,edx ;voir si eax est different de  0x534D4150
    jne .failed
    test ebx , ebx  ;EBX will be set to some non-zero value
    je .failed
    jmp .verify

.do_E820_loop:
    mov eax, 0xe820     ; eax, ecx get trashed on every int 0x15 call
    mov [es:di+20], dword 1 ; force a valid ACPI 3.X entry
    mov ecx, 24     ; ask for 24 bytes again
    int 0x15
    jc .e820_failed

.verify:
    jcxz .skip_entries  ;Length of "region" (if this value is 0, ignore the entry) 
    cmp cl , 20
    jbe .extension
    cmp byte [es:di+20] , 0
    je .skip_entries

.extension:    ;go to the next buffer if cl equal 24
    push eax
        mov eax , dword [entries]
        inc eax
        mov dword [entries] , eax
    pop eax
    mov ecx , [es:di+8]
    jcxz .skip_entries
    add di ,24

.skip_entries:
    test ebx , ebx  ;if ebx resets to 0, list is complete
    jne .do_E820_loop

.e820_failed:
    clc
    popa
    popf
    ret

.failed:
    stc
    popa
    popf
    ret
请,你能给我一些建议吗?如果我犯了别人的错误,也请给我一些建议

我想我已经为加载内核准备好了DL和BX,然后通过调用do_e820将它们销毁了

不,您没有-do_e820中的pusha和popa将保存并恢复dh和dl组合的dx

还有许多其他问题。例如:

1在cli启动后,让中断处于禁用/延迟状态没有任何意义;而且不仅仅是因为BIOS功能只会启用IRQ,特别是处理磁盘,这可能需要IRQ来指示传输已完成

2 do_e820代码使用进位标志返回成功/失败;但它设置或清除进位标志clc或stc,并从堆栈popf中弹出调用者的标志,覆盖进位标志以返回成功/失败

3在do_e820循环中没有缓冲区完全保护,因此至少在理论上,如果内存映射中有大量条目,那么di可能会卷起并丢弃所有内容

4 do_e820中的jcxz.skip_项是错误的,它只检查该项64位大小字段的32位,因此如果BIOS想说某个地方有4 GiB的可用RAM的倍数,您会错误地忽略它,并且在增加条目的数量后会发生错位,因此,您可以增加条目的数量,然后忽略/跳过该条目,最终在[entries]中得到错误的值。注意,您可以使用inc dword[entries]使其更干净,而无需保存/恢复eax

5 hlt仅在中断发生之前停止CPU。这包括暂停,直到发生非屏蔽中断/NMI,其中使用cli禁用/延迟IRQ,这是错误的/愚蠢的,不会阻止NMI;允许CPU在hlt之后继续执行代码。出于这个原因,您应该始终使用类似.die:hlt然后是jmp.die的循环;但在这种情况下,您没有理由禁用IRQ。请注意,对于真正的计算机来说,在引导加载程序决定永久停止后按control+alt+delete非常方便,但在禁用IRQ时将无法工作

6对于软盘,你不应该在第一次出错后就放弃,因为软盘是相对不可靠的。标准做法是至少重试3次,并在重试之间重置软盘

还要注意的是,只要稍微重新安排一下,代码就更容易阅读,也不容易出错。具体来说,您可以先调用do_e820,然后加载调用load_内核所需的参数,例如mov dh,17,mov bx,0x9000。在这种情况下,您还可以使用mov dl、[BOOT_DRIVE]重新加载dl,并从do_e820中删除pusha和popa以及pushf和popf

我想我已经为加载内核准备好了DL和BX,然后通过调用do_e820将它们销毁了

不,您没有-do_e820中的pusha和popa将保存并恢复dh和dl组合的dx

还有许多其他问题。例如:

1在cli启动后,让中断处于禁用/延迟状态没有任何意义;而且不仅仅是因为BIOS功能只会启用IRQ,特别是处理磁盘,这可能需要IRQ来指示传输已完成

2 do_e820代码使用进位标志返回成功/失败;但它设置或清除进位标志clc或stc,并从堆栈popf中弹出调用者的标志,覆盖进位标志以返回成功/失败

3在do_e820循环中没有缓冲区完全保护,因此至少在理论上,如果内存映射中有大量条目,那么di可能会卷起并丢弃所有内容

4 do_e820中的jcxz.skip_项是错误的,它只检查该项64位大小字段的32位,因此如果BIOS想说某个地方有4 GiB的可用RAM的倍数,您会错误地忽略它,并且在增加条目的数量后会发生错位,因此,您可以增加条目的数量,然后忽略/跳过该条目,最终在[entries]中得到错误的值。注意,您可以使用inc dword[entries]使其更干净,而无需保存/恢复eax

5 hlt仅在中断发生之前停止CPU。这包括暂停,直到发生非屏蔽中断/NMI,其中使用cli禁用/延迟IRQ,这是错误的/愚蠢的,不会阻止NMI;允许CPU在hlt之后继续执行代码。出于这个原因,您应该始终使用类似.die:hlt然后是jmp.die的循环;但是那样的话你 没有理由禁用IRQ。请注意,对于真正的计算机来说,在引导加载程序决定永久停止后按control+alt+delete非常方便,但在禁用IRQ时将无法工作

6对于软盘,你不应该在第一次出错后就放弃,因为软盘是相对不可靠的。标准做法是至少重试3次,并在重试之间重置软盘


还要注意的是,只要稍微重新安排一下,代码就更容易阅读,也不容易出错。具体来说,您可以先调用do_e820,然后加载调用load_内核所需的参数,例如mov dh,17,mov bx,0x9000。在这种情况下,您还可以使用mov dl、[BOOT_DRIVE]重新加载dl,并从do_e820中删除pusha和popa以及pushf和popf。

汇编中edx寄存器的特殊性是什么???@ledoux:我不太明白。edx就像任何其他通用寄存器eax、ebx。。除了一些使用暗示mul、div、cwd的说明之外。所有32位通用寄存器的最低16位ax、bx、…、最低8位al、bl、。。。和他们中间的8位啊,bh。。。;主要是因为历史原因,比如说,在英特尔将其扩展到32位之前,你就已经有了这个功能。好的,我已经准备好了。但是我收到了这个错误BOOT/bootloader.asm:109:error:TIMES值-38是负数,当我包含另一个时,我想我的BOOT没有第109行超过512字节,是真的吗??我如何修复这个错误?@ledoux:是的,这个错误意味着引导加载程序太大了38字节,无法安装。要修复它,您必须减小代码的大小;或者找到更节省空间的方法来做同样的事情,或者将代码从引导扇区转移到其他地方。我做的一个技巧是有多个扇区,其中第一个扇区包含要打印到屏幕的代码,第二个扇区包含要加载的代码,然后这两个扇区中的代码加载引导加载程序的其余扇区。汇编中edx寄存器的特殊性是什么???@ledoux:我不确定是否理解。edx就像任何其他通用寄存器eax、ebx。。除了一些使用暗示mul、div、cwd的说明之外。所有32位通用寄存器的最低16位ax、bx、…、最低8位al、bl、。。。和他们中间的8位啊,bh。。。;主要是因为历史原因,比如说,在英特尔将其扩展到32位之前,你就已经有了这个功能。好的,我已经准备好了。但是我收到了这个错误BOOT/bootloader.asm:109:error:TIMES值-38是负数,当我包含另一个时,我想我的BOOT没有第109行超过512字节,是真的吗??我如何修复这个错误?@ledoux:是的,这个错误意味着引导加载程序太大了38字节,无法安装。要修复它,您必须减小代码的大小;或者找到更节省空间的方法来做同样的事情,或者将代码从引导扇区转移到其他地方。我做的一个技巧是有多个扇区,其中第一个扇区包含要打印到屏幕的代码和要加载第二个扇区的代码,然后这两个扇区中的代码加载引导加载程序的其余扇区。