1

This is the code for a program that, when booted by a bootloader, disables interrupts, loads a GDT descriptor, enables the A20 Line, enables protected mode, and jumps into 32-bit code.

.code16
.section .text
A20:
    call waitA20_1
    mov $0xAD,%al
    out %al,$0x64
    
    call waitA20_1
    mov $0xD0,%al
    out %al,$0x64

    call waitA20_2
    in $0x60,%al
    push %eax

    call waitA20_1
    mov $0xD1,%al
    out %al,$0x64

    call waitA20_1
    pop %eax
    or $2,%al
    out %al,$0x60

    call waitA20_1
    mov $0xAE,%al
    out %al,$0x64

    call waitA20_1
    ret
    
waitA20_1:    
    in $0x64,%al  # store input from keyboard controller (0x64) in AL
    test $2,%al   # AND operation with 2 (binary: 00000010) and keyboard input
    jnz waitA20_1 # if ZFLAG is 0, try again
    ret

waitA20_2:    
    in $0x64,%al
    test $1,%al
    jz waitA20_2  # if ZFLAG is 1, try again
    ret
.globl _start
_start: 
    cli
    lgdt gdtd
    call A20
    mov %cr0,%eax
    or $1,%al
    mov %eax,%cr0
    ljmp $0x08, $PMode

.code32
PMode:  
    mov 0xb8000,%edi
    movb $'!',(%edi)
    inc %edi
    movb 0x07,(%edi)
    inc %edi
hang:   
    hlt
    jmp hang
.section .data
.align 8
gdt:    
    .quad 0x0000000000000000 # Null descriptor
    
    # kernel code segment
    .word 0xFFFF # lower 16 bits of segment size
    .word 0x0000 # lower 16 bits of base address
    .byte 0x00   # middle 8 bits of base address
    .byte 0x9A   # access byte: ring 0, executable only
    .byte 0xCF   # granularity (0xC)
    .byte 0x00   # higher 8 bits of base address
gdt_end:    

gdtd: 
    .word gdt_end - gdt - 1
    .long gdt

But nothing shows up at all. What's the issue here?

I tried adding a $ before 0xb8000. I expected it to be about loading an address at a particular location into a register versus loading a constant.

1
  • 4
    You forgot to reload the segment registers after jumping to protected mode; they're still set up as if in real mode. Commented May 9 at 20:02

1 Answer 1

0

Besides the CODE descriptor, you also need a DATA descriptor from which you can load the segment registers. You might want to zero the upper 16 bits of the ESP register as well. I'm assuming 16-bit SS got zeroed before, and that a valid SP exists.

To save some space, you can put the GDT pointer over the NULL descriptor.

_start: 
    cli
    call   A20
    lgdt   (gdt)
    mov    %cr0, %eax
    or     $1, %al
    mov    %eax, %cr0
    ljmp   $0x08, $PMode

.code32
PMode:
    mov    $0x10, %ax
    mov    %ax, %ds
    mov    %ax, %es
    mov    %ax, %fs
    mov    %ax, %gs
    mov    %ax, %ss
    movzwl %sp, %esp

    mov    $0x000B8000, %edi
    movb   $'!', (%edi)
    inc    %edi
    movb   $0x07, (%edi)
    inc    %edi
hang:   
    hlt
    jmp hang

.section .data
.align 8
gdt:
    # NULL descriptor re-purposed
    .word gdt_end - gdt - 1
    .long gdt
    .word 0
    
    # CODE descriptor
    .word 0xFFFF # lower 16 bits of segment size
    .word 0x0000 # lower 16 bits of base address
    .byte 0x00   # middle 8 bits of base address
    .byte 0x9A
    .byte 0xCF   # higher 4 bits of segment size
    .byte 0x00   # higher 8 bits of base address
    
    # DATA descriptor
    .word 0xFFFF # lower 16 bits of segment size
    .word 0x0000 # lower 16 bits of base address
    .byte 0x00   # middle 8 bits of base address
    .byte 0x92
    .byte 0xCF   # higher 4 bits of segment size
    .byte 0x00   # higher 8 bits of base address
gdt_end:    
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.