0
votes

I am attempting to write a simple bootloader for x86 architecture that should just output the character 'A', enter protected mode, then halt. My code, with comments, is as follows:

BITS 16
ORG 0x7c00

jmp 0:start ;set cs to 0

start:
mov ax,0x7c0
add ax,288
mov ss,ax
mov sp,4096

mov ax,0x7c0
mov ds,ax ;Sets segment descriptors for now

mov ah,0eh
mov al,65
int 10h ;Print A for test

jmp pretect ;Jump to a ~1 second delay before entering protected mode so we can see the 'A' if anything goes wrong

nod:
jmp nod ;Not used for now

gdtstp: ;global descriptor table
dq 0 ;Null
dw 0xffff ;Entry 08h, full 4gb
dw 0
db 0
db 0x9a
db 11001111b
db 0
dw 0xffff ;Entry 16h, full 4gb
dw 0
db 0
db 0x92
db 11001111b
db 0

gdtr: ;descriptor for gdt
dw 24
dd gdtstp

pretect: ;Wait for about 1 second before jumping to protect
mov esi,0x20000000
.loop:
dec esi
test esi, esi
jz protect
jmp .loop

protect: ;attempt to enter protected mode
cli
lgdt [gdtr] ;set gdt register
mov eax,cr0
or al,1
mov cr0,eax ;set bit 1 of cr0

jmp 08h:idle ;sets cs to 08h and jumps to idle

idle:
jmp idle ;Should stop here

times 510-($-$$) db 0
dw 0xaa55 ;magic number

This is in NASM and is being run on qemu. I have a slipshod means of adding a delay of about 1 second between outputting 'A' and attempting to enter protected mode. Currently, when I try to run this code, it prints the 'A', lingers for about a second, then reboots. I cannot tell why this is, but I assume it is likely because the global descriptor table is invalid or improperly loaded, or because the far jump to set the code segment selector isn't correct.

What my code should be doing is: print the 'A', have a GDT of 3 entries: a null descriptor, a code segment of all 4GB, and a data segment of all 4 GB, have the GDTR that specifies 24 bytes and the address of the GDT, wait for 1 second, disable interrupts, load the GDT, enable protected mode, set the code segment selector with a far jump to idle, then stay there indefinitely.

If it is possible to determine what in my assembly code is not right for entering protected mode, please point it out. I realize the initial bootloader generally does not fulfill this task, but I am just trying to get an understanding of how it works through a minimal working program.

EDIT: After changing

mov ax,0x7c0
mov ds,ax

to

mov ax,0
mov ds,ax

and putting mov ax,08h between idle: and jmp idle, qemu crashes and gives the following:

qemu: fatal: Trying to execute code outside RAM or ROM at 0x000a0000
EAX=feeb0010 EBX=00000000 ECX=00000000 EDX=ffffffff
ESI=00000000 EDI=8000007c EBP=00000000 ESP=00000ffc
EIP=0009fc6d EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =08e0 00008e00 0000ffff 00009300 DPL=0 DS16 [-WA]
DS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
FS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
GS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c1f 00000018
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=feeb0010 CCD=feeb0010 CCO=ADDB
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
1
Use bochs with its built-in debugger or study qemu output in case it told you what the exception was.Jester
My best guess is that the far jump after enter protected mode isn’t right. Can you disassemble it or get an assembly listing to see what is actually being generated? I think it should be something like ea 48 7c 08 00. (7c48 is the address of idle, which I guessed at.)prl
The opcodes for this jump appear to be ea 5f 7c 08 00. The jmp idle instruction has the opcodes eb fe and is at position 0x5f in the file, which I would think means it's loaded into position 0x7c5f (where the far jump directs to) in main memory.user2649681
I can't reproduce the bug you suggest by changing 0x7c0 to 0x0 (when loading DS) and putting mov ax, 08h before the jmp idle. But something yous should be doing is loading all the segment registers (ES, DS, SS) with the selector 010h (you have only set CS to 08h). You can also set GS and FS to 010h as well if you wish. EIP=0009fc6d in the dump suggests you have wandered way out in memory somewhere.Michael Petch
Setting the segments to 010h as I suggested should be done after jmp 08h:idle .Once you set SS you should also be setting ESPMichael Petch

1 Answers

2
votes

You need to load ds with 0, not 7c0h.

In lgdt, the offset in the instruction is 0-based. (That’s the only instruction that uses ds.)