2

我希望这里有一些经验丰富的汇编/操作系统开发人员,即使我的问题不是很大。我正在尝试组装并创建一个小型操作系统。事实上,我想要的是一个引导加载程序和第二个引导加载程序,它激活 pmode 并在屏幕上显示一个字符,使用视频内存(显然不是中断)。我正在使用 VirtualBox 来模拟​​代码,我将其手动粘贴到 VHD 磁盘中(两个代码扇区)

首先,我的代码:

boot.asm
这是第一个引导加载程序

bits    16
org     0
mov     al, dl

jmp     07c0h:Start

Start:
    cli
    push    ax
    mov     ax, cs
    mov     ds, ax
    mov     es, ax
    pop     ax
    sti
    jmp     ReadDisk

ReadDisk:
    call    ResetDisk
    mov     bx, 0x1000
    mov     es, bx
    mov     bx, 0x0000
    mov     dl, al
    mov     ah, 0x02
    mov     al, 0x01
    mov     ch, 0x00
    mov     cl, 0x02
    mov     dh, 0x00
    int     0x13
    jc      ReadDisk
    jmp     0x1000:0x0000

ResetDisk:
    mov     ah, 0x00
    mov     dl, al
    int     0x13
    jc      ResetDisk
    ret

times       510 - ($ - $$) db 0
dw          0xAA55

boot2.asm
这是第二个引导加载程序,粘贴在第二个扇区(接下来的 512 字节)

bits    16
org     0

jmp     0x1000:Start

InstallGDT:
    cli
    pusha
    lgdt    [GDT]
    sti
    popa
    ret

StartGDT: 
    dd      0
    dd      0 

    dw      0ffffh 
    dw      0
    db      0
    db      10011010b
    db      11001111b
    db      0

    dw      0ffffh
    dw      0
    db      0
    db      10010010b
    db      11001111b
    db      0

StopGDT:

GDT:
    dw StopGDT - StartGDT - 1
    dd StartGDT + 10000h

OpenA20:
    cli
    pusha
    call    WaitInput
    mov     al, 0xad
    out     0x64, al
    call    WaitInput
    mov     al, 0xd0
    out     0x64, al
    call    WaitInput
    in      al, 0x60
    push    eax
    call    WaitInput
    mov     al, 0xd1
    out     0x64, al
    call    WaitInput
    pop     eax
    or      al, 2
    out     0x60, al
    call    WaitInput
    mov     al, 0xae
    out     0x64, al
    call    WaitInput
    popa
    sti
    ret

WaitInput:
    in      al, 0x64
    test    al, 2
    jnz     WaitInput
    ret

WaitOutput:
    in      al, 0x64
    test    al, 1
    jz      WaitOutput
    ret

Start:
    cli
    xor ax,     ax
    mov ds,     ax
    mov     es,     ax
    mov ax,     0x9000
    mov ss,     ax
    mov sp,     0xffff
    sti

    call    InstallGDT
    call    OpenA20

ProtectedMode:
    cli
    mov     eax,    cr0
    or      eax,    1
    mov     cr0,    eax
    jmp 08h:ShowChar

bits    32

ShowChar:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov esp, 90000h

    pusha               ; save registers
    mov edi, 0xB8000
    mov bl, '.'
    mov dl, bl          ; Get character
    mov dh, 63      ; the character attribute
    mov word [edi], dx      ; write to video display
    popa
cli
hlt

所以,我编译这段代码并将二进制文件粘贴到 VHD 中,然后在 Virtual Box 上运行系统。我可以看到它正确地进入了 pmode,启用了 A20 门,并且 LGTR 包含一个内存地址(我不知道它是否正确)。这是日志文件的某些部分,可能会引起您的兴趣:

00:00:07.852082 ****************** Guest state at power off ******************
00:00:07.852088 Guest CPUM (VCPU 0) state: 
00:00:07.852096 eax=00000011 ebx=00000000 ecx=00010002 edx=00000080 esi=0000f4a0     edi=0000fff0
00:00:07.852102 eip=0000016d esp=0000ffff ebp=00000000 iopl=0         nv up di pl zr na po nc
00:00:07.852108 cs={1000 base=0000000000010000 limit=0000ffff flags=0000009b} dr0=00000000 dr1=00000000
00:00:07.852118 ds={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr2=00000000 dr3=00000000
00:00:07.852124 es={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr4=00000000 dr5=00000000
00:00:07.852129 fs={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr6=ffff0ff0 dr7=00000400
00:00:07.852136 gs={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr0=00000011 cr2=00000000
00:00:07.852141 ss={9000 base=0000000000090000 limit=0000ffff flags=00000093} cr3=00000000 cr4=00000000
00:00:07.852148 gdtr=0000000000539fc0:003d  idtr=0000000000000000:ffff  eflags=00000006
00:00:07.852155 ldtr={0000 base=00000000 limit=0000ffff flags=00000082}
00:00:07.852158 tr  ={0000 base=00000000 limit=0000ffff flags=0000008b}
00:00:07.852162 SysEnter={cs=0000 eip=00000000 esp=00000000}
00:00:07.852166 FCW=037f FSW=0000 FTW=0000 FOP=0000 MXCSR=00001f80 MXCSR_MASK=0000ffff
00:00:07.852172 FPUIP=00000000 CS=0000 Rsrvd1=0000  FPUDP=00000000 DS=0000 Rsvrd2=0000
00:00:07.852177 ST(0)=FPR0={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852185 ST(1)=FPR1={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852193 ST(2)=FPR2={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852201 ST(3)=FPR3={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852209 ST(4)=FPR4={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852222 ST(5)=FPR5={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852229 ST(6)=FPR6={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852236 ST(7)=FPR7={0000'00000000'00000000} t0 +0.0000000000000000000000 ^ 0
00:00:07.852244 XMM0 =00000000'00000000'00000000'00000000  XMM1 =00000000'00000000'00000000'00000000
00:00:07.852253 XMM2 =00000000'00000000'00000000'00000000  XMM3 =00000000'00000000'00000000'00000000
00:00:07.852262 XMM4 =00000000'00000000'00000000'00000000  XMM5 =00000000'00000000'00000000'00000000
00:00:07.852270 XMM6 =00000000'00000000'00000000'00000000  XMM7 =00000000'00000000'00000000'00000000
00:00:07.852280 XMM8 =00000000'00000000'00000000'00000000  XMM9 =00000000'00000000'00000000'00000000
00:00:07.852287 XMM10=00000000'00000000'00000000'00000000  XMM11=00000000'00000000'00000000'00000000
00:00:07.852295 XMM12=00000000'00000000'00000000'00000000  XMM13=00000000'00000000'00000000'00000000
00:00:07.852302 XMM14=00000000'00000000'00000000'00000000  XMM15=00000000'00000000'00000000'00000000
00:00:07.852310 EFER         =0000000000000000
00:00:07.852312 PAT          =0007040600070406
00:00:07.852316 STAR         =0000000000000000
00:00:07.852318 CSTAR        =0000000000000000
00:00:07.852320 LSTAR        =0000000000000000
00:00:07.852322 SFMASK       =0000000000000000
00:00:07.852324 KERNELGSBASE =0000000000000000
00:00:07.852327 ***
00:00:07.852334 Guest paging mode:  Protected (changed 5 times), A20 enabled (changed 2 times)

因此,这是测试结束时处理器的状态。

问题是,我看不到屏幕上的字符。这可能是与内存有关的问题(我必须承认我不太擅长内存寻址),例如段寄存器中的错误内容,或者可能与我尝试按顺序使用视频内存的方式有关显示那个字符,但它可能是别的东西。你觉得哪里不对?非常感谢!

更新 问题与内存寻址有关。不执行 ShowChar 指令。我在日志文件中验证了它。我所知道的是,直到这一行,一切都正确执行:

jmp 08h:ShowChar

因此,这可能与错误的段寄存器、错误的 GDTR 或与内存寻址有关的其他事情有关。

更新 我将 GDT 更改为线性地址而不是段:偏移量,但仍然看不到字符。问题是我无法弄清楚问题的根源,因为我无法验证 GDT 是否正确。我可以看到所有寄存器的内容,但我怎么知道 GDTR(目前是0000000000ff53f0:00e9)是正确的?我只是假设由于错误的 GDT 而没有执行 ShowChar 函数,而只是一个假设。

4

5 回答 5

5

问题是,尽管您为在 DX 中提供角色和属性所做的所有工作:

mov bl, '.'
mov dl, bl          ; Get character
mov dh, CHAR_ATTRIB     ; the character attribute

您最终将字 63 写入屏幕缓冲区:

mov word [edi], 63      ; write to video display

这是一个零属性的问号,即黑底问号。

于 2013-01-17T20:42:32.577 回答
2

我对此不是很有经验,但是...

GDT:
    dw StopGDT - StartGDT - 1
    dd StartGDT

这不需要是“绝对”(不是 seg:offs)地址吗?由于您已在 1000h 段加载它,因此我希望dd StartGDT + 10000h就在此处。不?

于 2013-01-17T22:32:08.977 回答
1

这是一个可行的极简引导加载程序,它使用 Qemu 切换到受保护并将“X”打印到 VGA(因此无需读取磁盘)。

[org 0x7C00]

cli
lgdt [gdt_descriptor] 

; Enter PM
mov eax, cr0
or  eax, 0x1
mov cr0, eax

; 1 GDT entry is 8B, code segment is 2nd entry (after null entry), so
; jump to code segment at 0x08 and load init_pm from there
jmp 0x8:init_pm   


[bits 32]
init_pm :
    ; Data segment is 3rd entry in GDT, so pass to ds the value 3*8B = 0x10
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ;Print a X of cyan color
    ;Note that this is printed over the previously printed Qemu screen
    mov al, 'L'
    mov ah, 3 ; cyan
    mov edx, 0xb8004
    mov [edx], ax      

    jmp $


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]

GDT:
;null : 
    dd 0x0 
    dd 0x0

;code : 
    dw 0xffff       ;Limit
    dw 0x0          ;Base
    db 0x0          ;Base
    db 0b10011010   ;1st flag, Type flag
    db 0b11001111   ;2nd flag, Limit
    db 0x0          ;Base

;data : 
    dw 0xffff       
    dw 0x0          
    db 0x0
    db 0b10010010 
    db 0b11001111 
    db 0x0

gdt_descriptor :
    dw $ - GDT - 1      ;16-bit size
    dd GDT              ;32-bit start address


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Bootsector padding
times 510-($-$$) db 0
dw 0xaa55

然后做:

nasm boot.asm
qemu boot
于 2014-11-21T00:09:39.560 回答
0

要以标准 VGA 写入视频内存,您​​需要将数据写入内存地址0xb8000或内存中的 8000 字节。要做一个简单的黑白字符,只需将字符值与该值进行 OR 运算,即可15 << 8得到 16 位无符号短整型。然后将这 16 位写入该内存位置以绘制字符。

于 2013-01-17T20:40:20.457 回答
0

问题是您使用 ORG 指令并将实模式和保护模式寻址方案混合在一起。你是对的,你的 32 位代码没有被执行。当 CPU 执行此代码时:

jmp 08h:ShowChar

它跳转到当前加载的中断向量表中的某个位置,位于内存的开头,而不是您的 32 位代码。为什么?因为您定义的代码段的基数是 0,并且您告诉汇编器解析相对于 0 的地址:

Org 0

因此,CPU 实际上是跳转到一个数值等于(0 + ShowChar 代码的第一条指令的偏移量)的地址(即代码段基址 + 偏移量)

要纠正此问题,请更改:

Org 0

进入

Org 0x10000

然后您需要更改您的段寄存器以匹配,但在这种情况下,您最初设置的段寄存器对于您最初指定的原始指令不正确,但是当原始指令如上所述更改时有效,因此无需进一步更改做出来。作为旁注,您的 origin 指令不正确这一事实可以解释为什么您的 GDT 地址似乎是垃圾 - 因为它实际上是您的 lgdt 指令加载的中断向量表的一部分。您指向 GDT 参数(“GTD”标签)的指针实际上指向中断向量表开头的某个位置。

无论如何,只需如上所示更改 origin 指令即可解决问题。

顺便说一句,您的代码看起来与http://www.brokenthorn.com/Resources/OSDev8.html上的代码非常相似

特别是http://www.brokenthorn.com/Resources/OSDev10.html页面底部提供的demo代码

有趣的..

于 2015-01-03T12:01:04.840 回答