Page 1 of 2
SOLVED Crash when entering protected mode BUT in Bochs/QEMU
Posted: Tue Apr 30, 2013 6:50 pm
by cnlohr
My problem was that I did not put a $ sign in front of the constant I was oring with eax when entering protected mode. I was not properly setting the CR0 register. Much thanks to everyone for helping me learn everything else, and bringing myself down to copying and pasting the Intel code.
I have need to make a very small bootable x86 program (needs to fit in ~12kB). I faired very well in the emulators, both QEMU and BOCHS had no problem with anything I was doing. However, when I went and put it on a pen drive, the code chirps along happily until it enters protected mode on CR0. I can verify that by the characters it prints to the screen.
It does this on two different systems I've checked. All real systems don't work, QEMU and BOCHS don't even give me warnings.
About the only thing I figure I might be doing wrong is I only set up a GDT, and it's flat.
This doesn't seem to be a problem I can find anyone else having :-/
Code: Select all
/*
Copyright 2009 Charles Lohr under the MIT/X11 License.
This code is a bootloader and switches to flat 4G code, 4G data protected mode.
It then calls C code.
*/
//Useful article: http://thiscouldbebetter.wordpress.com/2011/03/17/entering-protected-mode-from-assembly
.code16
.global loader
//Code runs from 0x7c00
.align 4
.org 0x00
bootstart:
//Configure the stack
mov $0xc000, %sp
//Configure the segments
mov $0x0000, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
//First, preserve drive letter.
push %dx
//Print out the letter 1, so we know it's working.
mov $0x0e31, %ax
mov $0x00, %bx
int $0x10
**EDIT (but no change)**
//Enable a20
mov $0x2401, %ax
int $0x15
//Verify a20 was set.
mov %ah, %al
mov $0x0e, %ah
add $0x30, %al
mov $0x00, %ebx
int $0x10
**END EDIT**
//Just in case we have a dumb bios that set our code segment.
ljmp $0x00, $continuestart
continuestart:
//Copy over first few sectors of hard disk into ram.
mov %al, %dl
mov $0x0208, %ax //# of sectors to read.
mov $0x0002, %cx //Track = 0, sector = 2
pop %dx
mov $0x00, %dh //Head = 0, Drive = (whatever drive we started with)
mov $0x0000, %bx
mov %bx, %es //Segment of output
mov $0x7e00, %bx //Pointer of output
int $0x13
mov %ah, %al
mov $0x0e, %ah
add $0x30, %al
mov $0x00, %ebx
int $0x10
//Setup our real big GDT
cli
pusha
lgdt (GDT_ptr)
popa
//Enter protected mode
mov %cr0, %eax
or 1, %eax [b]NEEDS TO BE %1[/B]
mov %eax, %cr0
//Jump to correct data selector, and enter 32-bit mode
*edit* If I put a hlt here, it halts.
jmpl $0x08, $later2
later2:
*edit* If I put a hlt here, it does not halt.
Here's my GDT:
Code: Select all
//GDT, Flat 4GiB
//http://wiki.osdev.org/GDT_Tutorial
GDT:
.long 0x00000000
.long 0x00000000
//Descriptor (0x9a, RW, Execute) (Code32)
.long 0x0000ffff
.long 0x00CF9A00
//Descriptor (0x92, RW) (Data32)
.long 0x0000ffff
.long 0x00cf9200
//Code16
.long 0x0000ffff
.long 0x000f9a00
//Data16
.long 0x0000ffff
.long 0x000f9200
GDT_end:
GDT_ptr:
.word GDT_end - GDT // For limit storage
.long GDT // For base storage
Any ideas? Any way I can make bochs do super verbose output? Do I /need/ an IDT? Why would the behavior be so different on a real system?
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Tue Apr 30, 2013 10:57 pm
by xenos
This does not really make sense:
Code: Select all
//Code runs from 0x7c00
.align 4
.org 0x00
Indeed your code runs from 0x7c00 - but you set your .org to 0x00... It should be 0x7c00. Otherwise the jump into protected mode will get you to some bogus address, and very likely cause a crash / triple fault / reboot.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 3:54 am
by osdog
XenOS wrote:Otherwise the jump into protected mode will get you to some bogus address, and very likely cause a crash / triple fault / reboot.
In fact even the first jump to reload CS should go bad ... unless of course the .org is overridden by some magic linker option my crystal ball has not detected.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 9:15 am
by cnlohr
It is overridden. The linker script gives the absolute base address. It is 0x7c00. If I alter the .org here, it will alter it absolutely in the binary. Jumps, pointers, etc. definitely work.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 11:40 am
by osdog
cnlohr wrote:
Code: Select all
[...]
GDT_end:
GDT_ptr:
.word GDT_end - GDT - 1 // For limit storage
.long GDT // For base storage
That should do it. The size of the GDTD is the size of the GDT in octets minus 1.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 12:17 pm
by cnlohr
Already tried, no change in behavior. It now reads:
Code: Select all
.align 8
//GDT, Flat 4GiB
//http://wiki.osdev.org/GDT_Tutorial
GDT:
.long 0x00000000
.long 0x00000000
//Descriptor (0x9a, RW, Execute) (Code32)
.long 0x0000ffff
.long 0x00CF9800
//Descriptor (0x92, RW) (Data32)
.long 0x0000ffff
.long 0x00cf9200
//Code16
.long 0x0000ffff
.long 0x000f9a00
//Data16
.long 0x0000ffff
.long 0x000f9200
GDT_end:
.align 8
GDT_ptr:
.word GDT_end - GDT - 1// For limit storage
.long GDT // For base storage
Could it be that the BIOS expects some other information to be on the disk? That I am using the area at the beginning for something other than descriptors?
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 1:23 pm
by cnlohr
Here's the updated code, same behavior, though.
Code: Select all
/*
Copyright 2009-2013 Charles Lohr under the MIT/X11 License.
This code is a bootloader and switches to flat 4G code, 4G data protected mode.
It then calls C code.
*/
//Useful article: http://thiscouldbebetter.wordpress.com/2011/03/17/entering-protected-mode-from-assembly
.code16
.global loader
//Code runs from 0x7c00
.align 4
.org 0x00
bootstart:
//Configure the stack
mov $0xc000, %sp
//Configure the segments
mov $0x0000, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
//First, preserve drive letter.
push %dx
//Print out the letter 1, so we know it's working.
mov $0x0e31, %ax
mov $0x00, %bx
int $0x10
//Just in case we have a dumb bios that set our code segment.
ljmp $0x00, $continuestart
continuestart:
//Copy over first few sectors of hard disk into ram.
mov %al, %dl
mov $0x0208, %ax //# of sectors to read.
mov $0x0002, %cx //Track = 0, sector = 2
pop %dx
mov $0x00, %dh //Head = 0, Drive = (whatever drive we started with)
mov $0x0000, %bx
mov %bx, %es //Segment of output
mov $0x7e00, %bx //Pointer of output
int $0x13
push %ax
mov %ah, %al
mov $0x0e, %ah //Should read 0 to say everything was fine with the read call.
add $0x30, %al
mov $0x00, %ebx
int $0x10
pop %ax
mov $0x0e, %ah
add $0x30, %al
mov $0x00, %ebx //should be the number of bytes read
int $0x10
mov $0x0e, %ah
mov (0x7e02), %al //Should be lower case x
mov $0x00, %ebx
int $0x10
//Setup our real big GDT
lgdt (GDT_ptr)
//I am certain this gets called. I can see the on-screen text.
mov $0xb800, %ax
mov %ax, %ds
movb $0x91, (32)
movb $0xf1, (33) //prints a small 0x91 a couple of columns in.
mov $0x0000, %ax
mov %ax, %ds
cli
//CODE HERE WILL EXECUTE
//Enter protected mode
mov %cr0, %eax
or 0x00000001, %eax
mov %eax, %cr0
//Jump to correct data selector, and enter 32-bit mode
jmpl $0x08, $later2
later2:
.code32
//CODE HERE WILL NOT EXECUTE ON REAL SYSTEMS.
loopback:
jmpl $0x08, $loopback
//Setup data selectors
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
//This does not get called in real life, but does in the emulator.
mov $0x3131, %ax
mov $0xb8004, %edx
movw %ax, (%edx)
cli
//Enable the FPU
mov %cr0, %eax
and $0xfffffff3,%eax
mov %eax, %cr0
fninit
jmpl $0x08, $main //$0x7e00 //Start of main program
.align 8
//GDT, Flat 4GiB
//http://wiki.osdev.org/GDT_Tutorial
GDT:
.long 0x00000000
.long 0x00000000
//Descriptor (0x9a, RW, Execute) (Code32)
.long 0x0000ffff
.long 0x00CF9800
//Descriptor (0x92, RW) (Data32)
.long 0x0000ffff
.long 0x00cf9200
//Code16
.long 0x0000ffff
.long 0x000f9a00
//Data16
.long 0x0000ffff
.long 0x000f9200
GDT_end:
.align 8
GDT_ptr:
.word GDT_end - GDT - 1// For limit storage
.long GDT // For base storage
//Disk Signature
.org 0x1b8
.long 0xefcdab89
.short 0x0000
//Disk entry 1.
.byte 0x80 //Active partition 1 (and bootable)
.byte 0x00
.byte 0x02 //CHS address
.byte 0x00
.byte 0x56 //Partition type
.byte 0x03
.byte 0x0b //CHS adderss of last sector
.byte 0x00
.long 0x01
.long 0xc7 //Number of sectors in partition.
//Area for setting variables to call the interrupt
//We are putting this here since the values must be all zeroes anyway.
//In fact, we can put anything that's all zeroes on boot here.
.global CI32Params, ci32ax, ci32bx, ci32cx, ci32dx, ci32ss
.global ci32sp, ci32bp, ci32di, ci32si, ci32gs, ci32fs, ci32es, ci32ds
CI32Params:
ci32ax: .word 0x0000
ci32bx: .word 0x0000
ci32cx: .word 0x0000
ci32dx: .word 0x0000
ci32bp: .word 0x0000
ci32di: .word 0x0000
ci32si: .word 0x0000
ci32gs: .word 0x0000
ci32fs: .word 0x0000
ci32es: .word 0x0000
ci32ds: .word 0x0000
idt_prot:
.word 0x0000
.long 0x00000000
savecr0:
.long 0x00000000
//still have about 70 bytes left between zero-init values and 0x55aa signature
//.long 0xCCCCCCCC
//MBR Signature
.org 0x1fe
.byte 0x55
.byte 0xaa
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Wed May 01, 2013 1:38 pm
by osdog
We'll here is the procedure for switching to protected mode as per the Intel specification - your's looks a little different, so maybe try to follow the Intel Manual by the letter:
Code: Select all
[...]
/***********************
* enter protected mode
***********************/
debug_enter_protected_mode:
/* 9.9.1 Switching to Protected Mode [3] */
/* 1. Disable interrupts. A CLI instruction disables maskable
* hardware interrupts. NMI interrupts can be disabled with external
* circuitry. (Software must guarantee that no exceptions or
* interrupts are generated during the mode switching operation.)
*/
cli
inb $0x70, %al
or $0x80, %al
outb %al, $0x70
/* 2. Execute the LGDT instruction to load the GDTR register
* with the base address of the GDT.
*/
lgdtl (gdtd)
/* 3. Execute a MOV CR0 instruction that sets the PE flag (and
* optionally the PG flag) in control register CR0.
*/
mov %cr0, %eax
or $0x1, %eax
mov %eax, %cr0
/* 4. Immediately following the MOV CR0 instruction, execute a far
* JMP or far CALL instruction. (This operation is typically a far
* jump or call to the next instruction in the instruction stream.)
*/
ljmpl $0x08, $(1f)
1:
.code32
/* 5. The JMP or CALL instruction immediately after the MOV CR0
* instruction changes the flow of execution and serializes the
* processor.
*/
// NOTE: pray change of flow of execution and serialization worked?
/* 6. If paging is enabled, the code for the MOV CR0 instruction
* and the JMP or CALL instruction must come from a page that is
* identity mappe (that is, the linear address before the jump
* is the same as the physical address after paging and protected
* mode is enabled). The target instruction for the JMP or CALL
* instruction does not need to be identity mapped.
*/
// NOTE: we don't use paging
/* 7. If a local descriptor table is going to be used, execute the
* LLDT instruction to load the segment selector for the LDT in the
* LDTR register.
*/
// NOTE: we don't use a ldt
/* 8. Execute the LTR instruction to load the task register with a
* segment selector to the initial protected-mode task or to a
* writable area of memory that can be used to store TSS information
* on a task switch.
*/
// NOTE: we don't use tss
/* 9. After entering protected mode, the segment registers continue
* to hold the contents they had in real-address mode. The JMP or
* CALL instruction in step 4 resets the CS register. Perform one
* of the following operations to update the contents of the remaining
* segment registers.
* - Reload segment registers DS, SS, ES, FS, and GS. If the ES,
* FS, and/or GS registers are not going to be used, load them
* with a null selector.
* - Perform a JMP or CALL instruction to a new task, which
* automatically resets the values of the segment registers and
* branches to a new code segment.
*/
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
/* 10. Execute the LIDT instruction to load the IDTR register with
* the address and limit of the protected-mode IDT.
*/
// NOTE: don't set an idt ... we are just a boot loader!
/* 11. Execute the STI instruction to enable maskable hardware
* interrupts and perform the necessary hardware operation to
* enable NMI interrupts.
*/
// NOTE: don't enable interrupts ... kernel must do so after
// setting up an IDT
// sti
// inb $0x70, %al
// and $~0x80, %al
// outb %al, $0x70
[...]
/******
* GTD
******/
. = 408
gdt:
/* Figure 3-8. Segment Descriptor [3] */
/* .byte limit[0:7],limit[8:15],base[0:7],base[8:15],
* base[16:32], present[0]<<7|DPL[0:1]<<5|iscodedataseg[0]<<4|
* type[0:3],
* granularity[0]<<7|32bit[0]<<6|64bit[0]<<5|avail[0]<<4|
* limit[16:19], base[24:31]
*/
/* null */
.byte 0, 0, 0, 0, 0, 0, 0, 0
/* protected mode */
/* code 0x08 */
.byte 0xff, 0xff, 0, 0, 0, 1<<7|1<<4|0xa, 1<<7|1<<6|0xf, 0x00
/* data 0x10 */
.byte 0xff, 0xff, 0, 0, 0, 1<<7|1<<4|0x2, 1<<7|1<<6|0xf, 0x00
gdt_end:
wasted_space:
.word 0xdead
gdtd:
.word gdt_end - gdt - 1
.long gdt
/* References:
* [3] Intel(R) 64 and IA-32 Architectures Software Developer’s Manual:
* Volume 3 (3A, 3B & 3C): System Programming Guide,
* Order Number: 325384-044US, August 2012
*/
Anyway I would also try to reduce your code to the bare minimum (no copying of sectors) to narrow down the problem.
Good luck.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Thu May 02, 2013 7:20 am
by JAAman
one thing looked funny to me (though not likely the problem) -- you appear to be setting SP before setting SS (quite a bit before, actually) -- since you cannot be certain where BIOS had your stack, you shouldn't mess with SP without also changing SS at the same time
and since interrupts don't appear to be disabled at this point (disabling interrupts is not necessary if you load stack correctly), you are actually likely to get an interrupt during this time, which would cause unpredictable behavior (meaning it may work on some computers, and crash unpredictably on others, and cause various random conditions which may cause untraceable errors and crashes at a later point in your code, or, more likely, during BIOS calls)
simply moving your set SP down immediately below where you set SS should fix this
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Thu May 02, 2013 1:24 pm
by cnlohr
Ok, I've stripped it down - same behavior as always. On VM's it works as expected, on my PC it reboots when entering protected mode. @JAAman, I've tried swapping them. @osdog, I am now using the Intel table, and to no avail.
Here's my code:
Code: Select all
.code16
.global loader
//Code runs from 0x7c00
.align 4
.org 0x00
bootstart:
//Configure the segments
mov $0x0000, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
//Configure the stack
mov $0xc000, %sp
mov $0x0e, %ah
mov (0x7e02), %al //Should be lower case x
mov $0x00, %ebx
int $0x10
cli
//Disable NMI
inb $0x70, %al
or $0x80, %al
outb %al, $0x70
//Setup our real big GDT
lgdt (GDT_ptr)
/*
//I am certain this gets called. I can see the on-screen text. Both real and emulated.
mov $0xb800, %ax
mov %ax, %ds
movb $0x91, (32)
movb $0xf1, (33) //prints a small 0x91 a couple of columns in.
mov $0x0000, %ax
mov %ax, %ds
*/
//Enter protected mode
mov %cr0, %eax
or 0x00000001, %eax
mov %eax, %cr0
//Jump to correct data selector, and enter 32-bit mode
jmpl $0x08, $(1f)
.code32
1:
//On the VM this pauses the system. On real machines, this causes a reboot.
xor %eax, %eax
test %eax, %eax
test:
jz test
.org 408
.align 8
//GDT, Flat 4GiB
//http://wiki.osdev.org/GDT_Tutorial
GDT:
//Copied from the Intel one.
.byte 0, 0, 0, 0, 0, 0, 0, 0
/* protected mode */
/* code 0x08 */
.byte 0xff, 0xff, 0, 0, 0, 1<<7|1<<4|0xa, 1<<7|1<<6|0xf, 0x00
/* data 0x10 */
.byte 0xff, 0xff, 0, 0, 0, 1<<7|1<<4|0x2, 1<<7|1<<6|0xf, 0x00
GDT_end:
.align 8
GDT_ptr:
.word GDT_end - GDT - 1// For limit storage
.long GDT // For base storage
//Disk Signature
.org 0x1b8
.long 0xefcdab89
.short 0x0000
//Disk entry 1.
.byte 0x80 //Active partition 1 (and bootable)
.byte 0x00
.byte 0x02 //CHS address
.byte 0x00
.byte 0x56 //Partition type
.byte 0x03
.byte 0x0b //CHS adderss of last sector
.byte 0x00
.long 0x01
.long 0xc7 //Number of sectors in partition.
//MBR Signature
.org 0x1fe
.byte 0x55
.byte 0xaa
Here's my hex:
Code: Select all
00000000 b8 00 00 8e d8 8e c0 8e e0 8e e8 8e d0 bc 00 c0 |................|
00000010 b4 0e a0 02 7e 66 bb 00 00 00 00 cd 10 fa e4 70 |....~f.........p|
00000020 0c 80 e6 70 0f 01 16 b0 7d 0f 20 c0 66 0b 06 01 |...p....}. .f...|
00000030 00 0f 22 c0 66 ea 3c 7c 00 00 08 00 31 c0 85 c0 |..".f.<|....1...|
00000040 74 fe 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |t...............|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001a0 ff ff 00 00 00 9a cf 00 ff ff 00 00 00 92 cf 00 |................|
000001b0 17 00 98 7d 00 00 00 00 89 ab cd ef 00 00 80 00 |...}............|
000001c0 02 00 56 03 0b 00 01 00 00 00 c7 00 00 00 00 00 |..V.............|
000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
My small concern is with the lgdt command, is this correct? The opcode generated for it looks bizarre.
Code: Select all
38 //Setup our real big GDT
39 0024 0F0116B0 lgdt (GDT_ptr)
39 01
I can't seem to find a good description of what the opcodes for this command should look like.
Regardless... it works in an emulator and not on real PCs.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Thu May 02, 2013 8:41 pm
by JAAman
My small concern is with the lgdt command, is this correct? The opcode generated for it looks bizarre.
I can't seem to find a good description of what the opcodes for this command should look like.
should be: LGDT = 0F 01 mod 010 r/m
so yours appears to be encoded as 0F 01 16 B0 7D:
0F 01 sub-code=010 = LGDT opcode
mod = 00, r/m = 110 -- this combination means immediate 16-bit address follows the instruction
B0 7D == 7DB0 == (beginning of sector)+1B0 (0x40 short of end-of-sector)
the best place for information on encoding would be intel manuals in volume 2B: appendix B (specifically B2, for a table of all non-64bit encodings) and volume 2A: chapter2 (specifically 2.1.5) for mod and r/m encodings (note according to the table in 2B, mod=11 is reserved)
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Fri May 03, 2013 5:01 am
by osdog
cnlohr wrote:@osdog, I am now using the Intel table, and to no avail.
Careful the code I posted isn't straight from Intel. They haven't published any code afaik. I only posted code (I found) that I use for my bootup process and the comments seem to be taken straight from the Intel manual and the code seems to follow them by the letter.
cnlohr wrote:
My small concern is with the lgdt command, is this correct? The opcode generated for it looks bizarre.
Good question. That really depends on your assembler and possible also disassembler you are investigating it with ... most tools can't handle 16bit code let alone 16bit code mixed with 32bit code well.
Anyway the code I posted uses lgdt
l so maybe try that. lgdt will use default op size (in this case 16bit, which should be fine regardless) while lgdtl explicitly overrides the op size to be 32bit. Maybe that's the hardwares problem.
Also the jmp. Try making it a
ljmpl. I've has problems with using simply jmpl before.
But other than that I have no idea.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Fri May 03, 2013 6:35 am
by JAAman
as for your particular problem, usually these sort of problems (working on emulators but not certain real machines) come from making assumptions about the start-up state, however i don't see that here... your code looks right to me, but i didn't spend much time with it and I'm not used to AT&T syntax, so i could be missing something
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Fri May 03, 2013 9:28 am
by Combuster
Well, then this would be a good moment to post all the code, scripts, and build instructions you used to perform the test on a real machine. It'd at least give us the opportunity to test it ourselves.
Re: Crash when entering protected mode (But NOT in Bochs/QEM
Posted: Fri May 03, 2013 10:22 am
by cnlohr
OH MY GOSH I am dumb. This is what was wrong:
Code: Select all
mov %cr0, %eax
or 0x00000001, %eax
mov %eax, %cr0
It needed to be $0x00000001, not 0x00000001
I learned a lot, though. Thanks, everyone! Everything is cooking along!