ali
ali

Reputation: 11045

Experimental OS in assembly - can't show a character on the screen (pmode)

I hope there's some experienced assembly/os developer here, even if my problem is not a huge one. I am trying to play with assembly and create a small operating system. In fact, what I want is a boot-loader and a second boot-loader that activates pmode and displays a single char on the screen, using the video memory (not with interrupts, evidently). I am using VirtualBox to emulate the code, which I paste manually inside a VHD disk (two sectors of code)

In first place, my code:

boot.asm
This is the first boot-loader

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
This is the second boot-loader, pasted on the second sector (next 512 bytes)

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

So, I compile this code and paste the binary in the VHD, then run the system on Virtual Box. I can see that it goes in pmode correctly, the A20 gate is enabled and the LGTR contains a memory address (which I have no idea if is the correct). This is some part of the log file, that may be of interest:

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)

So, this is the status of the processor at the end of the test.

The problem is that, I cannot see the character on the screen. This can be a problem related to memory (I must admit I'm not so good at memory addressing), like wrong content in segment register, or it can be related to the manner in which I am trying to use the video memory in order to show that character, but it may be something else. What do you think is wrong? Thanks so much!

Update The problem is related to memory addressing. The ShowChar instructions are not executed. I verified it in the logs file. What I know is that everything is executed correctly up to this line:

jmp 08h:ShowChar

So, this might be related to wrong segment registers, wrong GDTR or something else related to memory addressing.

Update I changed GDT, to be a linear address instead of a segment:offset one, but still not seeing the character. The problem is that I can't figure out the origin of the problem, because I can't verify if the GDT is correct. I can see the content of all the registers, but how could I know that the GDTR (which at the moment is 0000000000ff53f0:00e9) is correct? I'm just supposing that the ShowChar function is not executed because of a wrong GDT, but just a supposition.

Upvotes: 2

Views: 365

Answers (5)

Joe
Joe

Reputation: 1

The problem is your use of the ORG directive and mixing up real mode and protected mode addressing schemes. You are right about your 32 bit code not being executed. When the CPU executes this code:

jmp 08h:ShowChar

It jumps to somewhere in the currently loaded Interrupt Vector Table, at the beginning of memory instead of your 32 bit code. Why? Because the base of your defined code segment is 0, and you told your assembler to resolve addresses relative to 0:

Org 0

Thus the CPU is actually jumping to an address that is numerically equal to (0 + the offset of the first instruction of your ShowChar code) (i.e. Code Segment Base + Offset)

To rectify this issue, change:

Org 0

Into

Org 0x10000

Then you would need to change your segment registers to match, but in this case the segment registers you originally set were incorrect for the origin directive you originally specified, but are valid when the origin directive is changed as above, so no further changes need to be made. As a side note, the fact that your origin directive was incorrect can explain why your GDT address appeared to be garbage - because it was in fact some part of the Interrupt Vector Table that was loaded by your lgdt instruction. Your pointer to the GDT parameters ('GTD' label) is actually pointing to somewhere in the beginning of the Interrupt Vector Table.

Anyway, simply changing the origin directive as shown above should fix the problem.

By the way, your code looks awfully similar to the code over at http://www.brokenthorn.com/Resources/OSDev8.html

Especially the demo code provided at the bottom of the page of http://www.brokenthorn.com/Resources/OSDev10.html

Interesting..

Upvotes: 0

user3166684
user3166684

Reputation: 199

Here is a workable minimalist bootloader that switch to protected and print a "X" to VGA, using Qemu (so no need to read the disk).

[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

Then, do:

nasm boot.asm
qemu boot

Upvotes: 1

Frank Kotler
Frank Kotler

Reputation: 1452

I'm not very experienced with this, but...

GDT:
    dw StopGDT - StartGDT - 1
    dd StartGDT

Doesn't this need to be an "absolute" (not seg:offs) address? Since you've loaded it at segment 1000h, I would expect dd StartGDT + 10000h to be right here. No?

Upvotes: 2

Anton Kovalenko
Anton Kovalenko

Reputation: 21507

The problem is, despite all your work for making character and attribute available in DX:

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

you end up writing word 63 into the screen buffer:

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

which is a question mark with zero attributes, i.e. black question mark on black background.

Upvotes: 5

Jesus Ramos
Jesus Ramos

Reputation: 23268

To write to video memory in standard VGA you write data to memory address 0xb8000 or byte 8000 in memory. To do a simple black and white character simply OR the character value with the value 15 << 8 so that you get a 16 bit unsigned short. You then write these 16 bits to that memory location to draw the character.

Upvotes: 0

Related Questions