【问题标题】:How to read sector into memory and jump to it for OS?如何将扇区读入内存并跳转到操作系统?
【发布时间】:2022-11-02 04:20:23
【问题描述】:

我知道这个问题被问了很多,但我找到的每个答案都对我不起作用。我正在尝试加载我的操作系统的stage 2,位于我的图像文件的第二个扇区(0x200

这是我尝试使用的代码:


bits 16                             ; Starting at 16 bits
org 0x0                               ; And starting at 0

jmp main                            ; Hop to main!


; TODO: copy comment from prev. loader
; args: SI
print:
    lodsb                           ; Load the next/first character to AL
    or al, al                       ; Is it 0?
    jz donePrint                    ; Yes - Done.
    mov ah, 0eh                     ; No - keep going.
    int 10h                         ; Print character.
    jmp print                       ; Repeat
donePrint:
    ret                             ; Return


; todo: args
readSector:
    mov ah, 02h
    mov al, 1
    mov dl, 0x80
    mov ch, 0
    mov dh, 0
    mov cl, 2

    mov bx, 0x500


    int 13h
    jnc good
    jmp fail

main:
    ; First, setup some registers.
    cli                             ; Clear interrupts
    mov ax, 0x07C0                  ; Point all registers to segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Create the stack(0x0000-0xFFFF).
    mov ax, 0x0000
    mov ss, ax                      ; Point SS to 0x0000
    mov sp, 0xFFFF                  ; Stack pointer at 0xFFFF
    sti                             ; Restore interrupts

    mov si, LOADING
    call print

    call readSector

    

    
fail:
    mov si, FAILURE_MSG
    call print
    
good:
    mov si, LOADOK 
    call print
    jmp 0x500

LOADING        db 0x0D, 0x0A, "Booting loader...", 0x0D, 0x0A, 0x00
FAILURE_MSG    db 0x0D, 0x0A, "ERROR: Press any key to reboot.", 0x0A, 0x00
LOADOK    db 0x0D, 0x0A, "load ok", 0x0A, 0x00



TIMES 510 - ($-$$) DB 0
DW 0xAA55

但它只是引导循环。我尝试了其他解决方案无济于事。我究竟做错了什么? 如果我需要更新问题,请告诉我。

谢谢!

编辑#1:根据 Sep Roland 的回答,我更新了我的代码,但它仍然无法正常工作。如果有任何帮助,我会将更新的代码放在这里。此外,如果要求,我可以发布我的第二阶段代码。它应该使用 0x500 作为 org.新代码:

bits 16                             ; Starting at 16 bits
org 0x0                             ; And starting at 0

jmp main                            ; Hop to main!


; TODO: copy comment from prev. loader
; args: SI
print:
    lodsb                           ; Load the next/first character to AL
    or al, al                       ; Is it 0?
    jz donePrint                    ; Yes - Done.
    mov ah, 0eh                     ; No - keep going.
    int 10h                         ; Print character.
    jmp print                       ; Repeat
donePrint:
    ret                             ; Return


; todo: args
readSector:
    mov ah, 02h
    mov al, 1
    mov ch, 0
    mov dh, 0
    mov cl, 2

    mov bx, 0x500


    int 13h
    jnc good
    jmp fail

main:
    ; First, setup some registers.
    cli                             ; Clear interrupts
    mov ax, 0x07C0                  ; Point all registers to segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Create the stack(0x0000-0xFFFF).
    mov ax, 0x0000
    mov ss, ax                      ; Point SS to 0x0000
    mov sp, 0xFFFE                  ; Stack pointer at 0xFFFE
    sti                             ; Restore interrupts

    mov si, LOADING
    call print

    call readSector

    

    
fail:
    mov si, FAILURE_MSG
    call print
end:
    cli
    hlt
    jmp end
    
good:
    mov si, LOADOK 
    call print
    jmp 0x07C0:0x0500

LOADING        db 0x0D, 0x0A, "Booting loader...", 0x0D, 0x0A, 0x00
FAILURE_MSG    db 0x0D, 0x0A, "ERROR: Press any key to reboot.", 0x0A, 0x00
LOADOK    db 0x0D, 0x0A, "load ok", 0x0A, 0x00



TIMES 510 - ($-$$) DB 0
DW 0xAA55

编辑#2:发布包括gdt.inc 的第二阶段代码,因为有人提到LGDT 可能导致了问题:

主要代码(某些部分已被删除,但它们不是必需的,如字符串)

bits 16                                 ; We start at 16 bits

org 0x500                               ; We are loaded in at 0x500

jmp main                                ; Jump to main code.


; ----------------------------------------
; Includes
; ----------------------------------------
%include "include/stdio.inc"
%include "include/gdt.inc"
%include "include/a20.inc"


; ---------------------------------------
; Data and strings
; ---------------------------------------

stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00

; ---------------------------------------------------------------------
; main - 16-bit entry point
; Installing GDT, storing BIOS info, and enabling protected mode
; ---------------------------------------------------------------------

main:
    ; Our goal is jump to main32 to become 32-bit

    ; Setup segments and stack
    cli                                 ; Clear interrupts
    xor ax, ax                          ; Null segments AX, DS, and ES
    mov ds, ax
    mov es, ax
    mov ax, 0x9000                      ; Stack begins at 0x9000-0xFFFF
    mov ss, ax
    mov sp, 0xFFFF                      ; Stack pointer is 0xFFFF
    sti                                 ; Enable interrupts

    

    ; Install the GDT
    call installGDT                     ; Install the GDT

    ; Enable A20
    call enableA20_KKbrd_Out            ; Enable A20 through output port

    ; Print loading messages
    mov si, msg1
    call print16                        ; Print the message

    mov si, msg2                        ; A message
    call print16                        ; Print the message

    ; Enter protected mode
    cli                                 ; Clear interrupts
    mov eax, cr0                        ; Set bit 0 in CR0--ENTER protected mode
    or eax, 1
    mov cr0, eax

    jmp CODE_DESC:main32                ; Far jump to fix CS
    
    ; We can't re-enable interrupts because that would triple-fault. This will be fixed in main32.


bits 32                                 ; We are now 32 bit!

%include "include/stdio32.inc"

main32:
    ; Set registers up
    mov ax, 0x10                        ; Setup data segments to 0x10(data selector)
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov esp, 90000h                     ; Stack begins from 90000h
    
    call clear32                        ; Clear screen
    mov ebx, MSGHIDDEN                   ; Setup params for our message
    call puts32                         ; Call puts32 to print

    cli                                 ; Clear interrupts
    hlt                                 ; Halt the processor

LGDT 代码:

%ifndef __GDT_INC_67343546FDCC56AAB872_INCLUDED__
%define __GDT_INC_67343546FDCC56AAB872_INCLUDED__

bits 16                     ; We are in 16-bit mode


; -----------------------------------------
; installGDT - install the GDT
; -----------------------------------------
installGDT:
    cli                     ; Clear interrupts
    pusha                   ; Save the registers
    lgdt [toc]              ; Load GDT into GDTR
    sti                     ; Re-enable interrupts
    popa                    ; Restore registers
    ret                     ; Return!


; ----------------------------------------
; Global Descriptor Table data
; ----------------------------------------

gdt_data:
    dd 0                    ; Null descriptor
    dd 0
    
    ; GDT code starts here
    dw 0FFFFh               ; Limit low
    dw 0                    ; Base low
    db 0                    ; Base middle
    db 10011010b            ; Access
    db 11001111b            ; Granularity
    db 0                    ; Base high

    ; GDT data starts here(mostly same as code, only difference is access)
    dw 0FFFFh               ; Limit low, again.
    dw 0                    ; Base low
    db 0                    ; Base middle
    db 10010010b            ; Access - different
    db 11001111b            ; Granularity
    db 0

gdt_end:
toc:
    dw gdt_end - gdt_data - 1
    dd gdt_data             ; Base of GDT

; Descriptor offsets names

%define NULL_DESC 0
%define CODE_DESC 0x8
%define DATA_DESC 0x10

; End of GDT code.
%endif ;__GDT_INC_67343546FDCC56AAB872_INCLUDED__

【问题讨论】:

  • 你能提供你的第二阶段吗?第二阶段的任何机会都是 lgdt 。如果是这样,我可以想到为什么会失败。如果您使用lgdt 作为进入保护模式的一部分(我猜),您可能会遇到由 DS != 0 引起的问题,并且 GDTR 需要线性地址,这需要修复将 0x7c00 添加到基数GDTR 中 GDT 的地址。通常使用 0x500 的人打算将第二阶段放在 0x0000:0x0500。看到 0x07c0:0x0500 是物理地址 0x8100 有点不寻常。
  • 如果 LGDT 有问题,则可能还必须调整阶段 2 中的 far jmp 以进入保护模式。如果您可以向我们展示第 2 阶段,我们可能会解决您的问题。我不相信您看到的问题与引导扇区有关。
  • 我将在当天晚些时候更新第 2 阶段的代码。是的,这可能是问题所在,正在调用 LGDT 和其他方法。
  • 我个人会在引导加载程序中设置org 0x7c00,在第二阶段设置org 0x500,然后将所有段(ES、DS 设置为0 而不是0x07c0)。这也会将您的第二阶段加载到 0x0000:0x0500 而不是 0x07c0:0x0500。使用非零段带有可以通过使用 0 来避免的陷阱(包括您在 GDT 中遇到的问题等)。
  • @MichaelPetch 它奏效了!谢谢!您可以将此作为答案发布,以便我接受吗?

标签: assembly nasm x86-16 bootloader osdev


【解决方案1】:

您自己加载的扇区在 0x7C0 的额外段中的偏移量 0x500 处加载。
jmp 0x500 指令跳转到代码段中的偏移量 0x500。
不能保证 CS==0x7C0。改用远跳:

jmp 0x07C0:0x0500

mov dl, 0x80

你确定这个驱动器号吗?当引导加载程序获得控制权时,最好使用 BIOS 在 DL 寄存器中提供的值。


mov sp, 0xFFFF

字对齐的堆栈指针会更好!

使用mov sp, 0xFFFE 甚至xor sp, sp(信任环绕)。


fail:
  mov si, FAILURE_MSG
  call print

good:
  mov si, LOADOK 
  call print
  jmp 0x500

如果加载扇区失败,您跳转到失败,但是在显示消息后,您会愉快地继续(失败)使用代码好的.你需要停止:

fail:
    mov  si, FAILURE_MSG
    call print
theEnd:
    cli
    hlt
    jmp  theEnd
good:
    mov  si, LOADOK
    call print
    jmp  0x07C0:0x0500

[编辑]

xor ax, ax   ; Null segments AX, DS, and ES
mov ds, ax
mov es, ax

您已对第一阶段引导加载程序进行了更正。控制已成功传递到 0x07C0:0x0500。因为第二阶段使用org 0x500,所以段寄存器(至少 DS)保持在 0x07C0 是至关重要的。但我看到的第一件事是你用 0 重新加载 DS 和 ES。这是行不通的,因为它会在汇编程序生成的偏移量(根据 org)和偏移量(相对 DS)之间造成不匹配实际数据所在的位置。

这种不匹配的第一个表现是lgdt [toc] 指令。
第二阶段的所有数据都驻留在内存中 0x8100 标记(0x7C00 + 0x0500)上方。
目录另一方面,标签将由汇编器转换为略大于 0x0500 的偏移地址。当 DS=0 时,这个地址内存远低于 0x8100 标记。根本没有有效的数据可以采取行动,因此崩溃(或类似情况)!

问题不仅在于lgdt [toc] 指令。 mov si, msg1mov si, msg2 将以同样的方式失败,mov ebx, MSGHIDDENdd gdt_data 将失败线性的地址。 dw gdt_end - gdt_data - 1 不会受到损害,因为当两个幅度都错误时(以相同的方式),差异不会改变。

您应该做的是根据org 0x0500 将段寄存器保持在 0x07C0,或者更好地将第一阶段引导加载程序中的段寄存器归零,并在第一阶段使用org 0x7C00,在第一阶段使用org 0x8100第二阶段。这些设置会将所有内容留在内存中的相同位置,第一阶段和第二阶段之间有 768 字节的间隙,这是我们每天都看不到的。
更受欢迎的方式但是,将第一阶段引导加载程序中已经存在的段寄存器归零,第一阶段使用org 0x7C00,第二阶段使用org 0x0600。这将第二阶段置于低内存 BIOS 变量之后。 MS-DOS 是我们很好的例子。

进一步审查

installGDT:
    cli                     ; Clear interrupts
    pusha                   ; Save the registers
    lgdt [toc]              ; Load GDT into GDTR
    sti                     ; Re-enable interrupts
    popa                    ; Restore registers
    ret                     ; Return!

在此代码中保留通用寄存器是多余的。 lgdt 指令不会改变它们中的任何一个。


; Create the stack(0x0000-0xFFFF).
mov ax, 0x0000
mov ss, ax        ; Point SS to 0x0000
mov sp, 0xFFFE    ; Stack pointer at 0xFFFE
mov ax, 0x9000    ; Stack begins at 0x9000-0xFFFF
mov ss, ax
mov sp, 0xFFFF    ; Stack pointer is 0xFFFF
mov ax, 0x10      ; Setup data segments to 0x10(data selector)
mov ds, ax
mov ss, ax
mov es, ax
mov esp, 90000h   ; Stack begins from 90000h

(1) 不要对 SP 使用奇数值。
(2) 不要在加载 SS 和 ESP 之间放置任何东西。
(3) 不要在堆栈的低端和高端都使用“开始”这个词。

您正在设置堆栈 3 次,每次都在不同的位置和大小!第一次从0x00000000运行到0x0000FFFD,第二次从0x00090000运行到0x0009FFFE,第三次从0x00000000运行到0x0008FFFF。
我建议设置堆栈,使实模式地址对应于保护模式地址。至少对于前 64KB。
在第一阶段使用:

mov  ax, 0x8000
mov  ss, ax
xor  sp, sp           ; 0x00080000 - 0x0008FFFF (64KB)

在第二阶段使用:

mov  ax, DATA_DESC
mov  ss, ax
mov  esp, 0x00090000  ; 0x00000000 - 0x0008FFFF (576KB)

第一阶段

bits 16
org  0x7C00

jmp  main

; args: SI
print:
    lodsb
    or   al, al
    jz   donePrint
    mov  bx, 0007h
    mov  ah, 0Eh
    int  10h
    jmp  print
donePrint:
    ret

main:
    cli
    xor  ax, ax
    mov  ds, ax
    mov  es, ax
    mov  fs, ax
    mov  gs, ax
    mov  ax, 0x8000    ; Stack between 0x8000:0x0000
    mov  ss, ax        ;           and 0x8000:0xFFFF (64KB)
    xor  sp, sp
    sti

    mov  si, LOADING
    call print

readSector:
    mov  dh, 0
    mov  cx, 0002h
    mov  bx, 0x0600     ; Sector buffer at 0x0000:0x0600
    mov  ax, 0201h
    int  13h
    jnc  good
    
fail:
    mov  si, FAILURE_MSG
    call print
end:
    cli
    hlt
    jmp  end
    
good:
    mov  si, LOADOK 
    call print
    jmp  0x0000:0x0600  ; Start second stage

LOADING     db 13, 10, "Booting loader...", 13, 10, 0
FAILURE_MSG db 13, 10, "ERROR: Press any key to reboot.", 10, 0
LOADOK      db 13, 10, "load ok", 10, 0

TIMES 510 - ($-$$) DB 0
DW 0xAA55

第二阶段

bits 16
org  0x0600

jmp  main
; ---------------------------------------
; Includes
; ---------------------------------------
%include "include/stdio.inc"
%include "include/gdt.inc"
%include "include/a20.inc"
; ---------------------------------------
; Data and strings
; ---------------------------------------
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
; ---------------------------------------
; main - 16-bit entry point
; Installing GDT, storing BIOS info, and enabling protected mode
; ---------------------------------------

main:
    call installGDT
    call enableA20_KKbrd_Out
    mov  si, msg1
    call print16
    mov  si, msg2
    call print16

    ; Enter protected mode
    cli
    mov  eax, cr0
    or   eax, 1
    mov  cr0, eax

    jmp  CODE_DESC:main32 ; Far jump to fix CS
    
    ; We can't re-enable interrupts because that would triple-fault. This will be fixed in main32.


bits 32                   ; We are now 32 bit!

%include "include/stdio32.inc"

main32:
    ; Set registers up
    mov  ax, DATA_DESC
    mov  ds, ax
    mov  es, ax
    mov  fs, ax
    mov  gs, ax
    mov  ss, ax           ; Stack between 0x00000000
    mov  esp, 0x00090000  ;           and 0x0008FFFF (576KB)

    call clear32          ; Clear screen
    mov  ebx, MSGHIDDEN
    call puts32           ; Call puts32 to print

    cli
    hlt

    ...

【讨论】:

  • 作为实践,我发现最好通过跳转到07c0:xxxx0000:7cxx 来启动引导块,并相应地设置所有其他段寄存器。不同的 BIOS 总是做不同的奇怪的事情,你永远不能假设你得到了什么。 (就此而言,过去有一些 BIOS 甚至没有正确设置 dl
  • @sj95126:不设置 CS 只会影响一小部分使用间接近 CALL/JMP 到绝对内存偏移的指令。这不是您在引导加载程序中经常看到的东西。如果您知道您没有使用任何此类指令,则不设置 CS 是安全的,尤其是当您的引导加载程序中的空间不足时。如果空间不是问题,那么显式设置 CS 根本就不是问题。我从其他网站上收集了各种问题,并将它们合并为一个自我问答:stackoverflow.com/questions/34548325/…
  • 这是一个令人难以置信的答案,我现在正在尝试这些更改
  • 你好!感谢您的回答,但不幸的是它不起作用。我更新了代码(检查原始问题),但它仍在引导。我应该怎么办?第二阶段代码位于 0x200 并使用 org 0x500 作为指令,如果这有助于或有所作为。
  • 九月你可能想看看他最近添加的 Stage2 代码。他的 Stage2 预计在 0x0000:0x0500 加载。我在问题下的评论中建议他改用org 0x7c00,对DS和ES段使用0并将他的JMP 0x07c0:0x0500修改为0x0000:0x0500。他说这些改变奏效了。您可能会考虑更新您的答案。
猜你喜欢
  • 2012-08-16
  • 2013-04-28
  • 1970-01-01
  • 2016-07-12
  • 2014-04-21
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多