装配中的实验操作系统 - 无法在屏幕上显示字符(pmode)

时间:2013-01-17 20:34:49

标签: memory video assembly operating-system bootloader

我希望这里有一些经验丰富的汇编/操作系统开发人员,即使我的问题不是很大。 我正在尝试使用汇编并创建一个小型操作系统。事实上,我想要的是一个启动加载器和第二个启动加载器,它激活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函数,而只是假设。

5 个答案:

答案 0 :(得分: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

这是一个零属性的问号,即黑色背景上的黑色问号。

答案 1 :(得分:2)

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

GDT:
    dw StopGDT - StartGDT - 1
    dd StartGDT

GDT: dw StopGDT - StartGDT - 1 dd StartGDT 这不是一个“绝对”(不是seg:offs)地址吗?由于您已经在1000h加载了它,我希望就在这里。否?

答案 2 :(得分:1)

这是一个可行的极简主义引导加载程序,切换到受保护并打印" X"到VGA,使用Qemu(所以不需要读取磁盘)。

[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

答案 3 :(得分:0)

要在标准VGA中写入视频内存,您​​需要将数据写入内存地址0xb8000或内存中的字节8000。要简单地使用值15 << 8对字符值进行简单的OR黑色和白色字符,以便获得16位无符号短整数。然后,将这16位写入该内存位置以绘制字符。

答案 4 :(得分:0)

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

jmp 08h:ShowChar

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

Org 0

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

要解决此问题,请更改:

Org 0

Org 0x10000

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

无论如何,只需更改上面显示的origin指令就可以解决问题。

顺便说一句,您的代码与http://www.brokenthorn.com/Resources/OSDev8.html

上的代码非常相似

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

有趣..