0

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
user2649681
  • 750
  • 1
  • 6
  • 23
  • Use bochs with its built-in debugger or study qemu output in case it told you what the exception was. – Jester Oct 03 '18 at 22:02
  • 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 Oct 03 '18 at 23:56
  • 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 Oct 04 '18 at 00:57
  • 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 Oct 04 '18 at 01:17
  • Setting the segments to 010h as I suggested should be done after `jmp 08h:idle` .Once you set _SS_ you should also be setting _ESP_ – Michael Petch Oct 04 '18 at 01:29
  • It does not crash, but is there any reason that calling `int 10h` with `ah = 0Eh`once entering protected mode would cause it to reboot? I am trying to confirm that it reached that part of the code, so I set those selectors and added that interrupt call, but it just reboots – user2649681 Oct 04 '18 at 01:36
  • Because once you enter protected mode the real mode BIOS routines can't be used. You will have to consider writing text directly to video memory (There are ports you can write to that changes the position of the cursor). – Michael Petch Oct 04 '18 at 01:48
  • Would you happen to know why, given this code, writing a value to `0xb8000` before the `jmp 08h:idle` prints the character, but doing so after the `jmp` fails to? Or even just any way I can check that the code actually ends up where it should after the `jmp`? – user2649681 Oct 04 '18 at 02:12
  • Well I hope you aren't placing the code between the `jmp 08h:idle` and the `idle:` label because those instructions will never execute because you have jumped over them. Do something like `jmp 08h:setcs` `setcs:` then put the code to initialize the segments and do an idle loop after that. – Michael Petch Oct 04 '18 at 02:15
  • Sorry for the unclear wording. That's what I was doing. `jmp 08h:idle` then after `idle:` initializing the segment selectors, then writing to `0xb8000` after that. – user2649681 Oct 04 '18 at 02:19
  • What is the instruction(s) you are using to write to b8000 – Michael Petch Oct 04 '18 at 02:20
  • `mov ebx,0xb8000 \ mov ax,0x0748 ;value of character \ mov [ebx],ax` These three instructions should, I beleive, write 0x0748 to 0xb8000 in memory, with the GDT I have described in OP – user2649681 Oct 04 '18 at 02:22
  • After your `JMP 08h:idle` you will need to make sure you have the line `bits 32` so the assembler emits 32-bit instructions from that point on. – Michael Petch Oct 04 '18 at 02:32

1 Answers1

2

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.)

prl
  • 11,716
  • 2
  • 13
  • 31
  • Alternatively, you could write lgdt [gdtr-0x7c00] – prl Oct 03 '18 at 22:54
  • Does that mean that the instruction `lgdt XXX` sets loads gdtr with ds:XXX? – user2649681 Oct 03 '18 at 22:55
  • Yes, every memory reference in an instruction uses a segment register, combined with the offset given in the instruction. However, the address of gdtstp loaded from gdtr is used directly as a linear address without a segment register. – prl Oct 03 '18 at 22:59
  • After making this change and putting a `mov ax,08h` after `idle:`, qemu crashes with an error I will add to the question. – user2649681 Oct 03 '18 at 23:06