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