trying to get into pm and back

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

trying to get into pm and back

Post by sancho1980 »

hi
i was trying to write some code in nasm that boots up and then enters protected mode, but it doesnt work...would someone please have a look at it..the first file (myboot.asm) is the 512 byte boot code read from the disk..the second file (mykernel1.asm) is the "kernel" and it needs to be placed on the floppy right after the 512 byte boot sector...the boot code reads the kernel from the floppy and jumps there..this works, i know for sure...you can try by commenting out all the switch-to-pm related stuff as described in the comments
the 512 byte boot sector asks the user to press a key and it will display the ascii and scan codes of the key hit; only when the user presses enter will the boot code load the kernel...i compile with:

nasm -f bin myboot.asm -o myboot.bin
nasm -f bin mykernel1.asm -o mykernel1.bin
dd if=mykernel1.bin of=myboot.bin count=1 seek=1

myboot.bin then is the floppy which i load in qemu

thanks for any help!

martin
Attachments
myboot.asm
(2.51 KiB) Downloaded 28 times
mykernel1.asm
(3.52 KiB) Downloaded 24 times
1234
Posts: 24
Joined: Sat May 26, 2007 7:58 pm

Post by 1234 »

[post deleted]
Last edited by 1234 on Wed Nov 21, 2007 5:24 pm, edited 1 time in total.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

ive seen examples of how its done before but the problem here is i am manipulationg the gdt data and code descriptors while still in real mode to reflect the exact same addresses as the current code in real mode and im not sure i i have done that correctly...also, i am not *jumping* into pm but calling it for clearing the pipe and i also dont know if theres any problem with that...in short, im stuck, i dont see the problem why my code doesnt work :-(
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Post by Brendan »

Hi,

When you use "BITS 32" or "BITS 16" you're only telling the assembler what type of code to generate, but the CPU itself doesn't know anything about it.

You need to make sure that code assembled after a "BITS 32" is executed when the CPU is using a 32-bit code segment, and you also need to make sure that code assembled after a "BITS 16" is executed when the CPU is using a 16-bit code segment.

Also, it's impossible to use a 32-bit code segment without enabling protected mode. This means you'd need a sequence like this:

Code: Select all

    BITS 16

     **Switch to protected mode**

     jmp far 0x10:.foo       ;Load a 32-bit code segment

    BITS 32

.foo:
     **Do 32-bit stuff**

    jmp 0x18:.bar        ;Load a 16-bit code segment

.bar:
    **Do 16-bit protected mode stuff

    **Disable protected mode**

    **Do 16-bit real mode stuff**

Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

you're probably referring to this part of the code, i'll put the rest of my reply as comment into the code ok:

Code: Select all

start:
    pusha
    pushf
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov si, txt0	;tell user that
    call printstring	;we're entering protected mode

;if you leave out everything between here and (1), it works well

    mov ax, cs

;we are still in real mode; i am using the "BITS 32" directive because i want to calculate the linear address of my real mode code segment and place it into gdt_code in the descriptor table so that my pm code segment has the same base address as my real mode code segment, so i need to make sure that something like "eax" really be assembled into an instruction that refers to the 32-bit register (linear base address of code segment *could* require up to 20 bits)

BITS 32

    movzx eax, ax
    shl eax, 4		;eax now contains linear address of real mode cs
    mov ebx, eax	;here we
    shr ebx, 16		;write linear
    mov [cbase1+1], bl	;address of real mode
    mov ebx, eax	;code segment
    mov [cbase1+2], bh	;into code segment descriptor
    mov [cbase2], bl	;in gdt

;here i am going back to "BITS 16" because i want to make sure that "ax" means the 16-bit register

BITS 16

    mov ax, ds

;here i am doing the same as above, but for the data segment, instead of the code segment

BITS 32

    movzx eax, ax
    shl eax, 4		;eax now contains linear address of real mode ds
    mov ebx, eax	;here we
    shr ebx, 16		;write linear
    mov [dbase1+1], bl	;address of real mode
    mov ebx, eax	;data segment
    mov [dbase1+2], bh	;into data segment descriptor
    mov [dbase2], bl	;in gdt

    mov ebx, 0ffffffffh		;calculate highest possible value
    sub ebx, eax		;for pm stack pointer
    mov [stack_pointer], ebx	;and store in [stack_pointer]

BITS 16
    
    ;now let's prepare for entry into protected mode
    push ds				;save these
    push es				;registers
    mov bx, ss ;bx=old stack segment	;for future
    mov cx, sp ;cx=old stack pointer	;restoration

    mov ax, 0ch
    mov ds, ax
    mov es, ax
    mov ss, ax

;here i am using the "BITS 32" directive because i need to make sure that "esp" refers to the 32-bit register and not the 16 bit register

BITS 32
    mov esp, [stack_pointer]
    cli
    lgdt [gdt_desc]
    mov eax, cr0		;enable
    or eax, 1			;protected
    mov cr0, eax		;mode
    
    call 08h:enter_pm

    mov eax, cr0		;disable
    or eax, 0			;protected
    mov cr0, eax		;mode
    sti

BITS 16

    mov ss, bx				;fortunately
    mov sp, cx				;we saved these registers
    pop es				;before entering
    pop ds				;protected mode

    mov si, txt1	;tell user that
    call printstring	;we're back from protected mode

;(1)
    popf
    popa
    retf
any hints?

ps: do i HAVE to jmp into protected mode or is there a way to call and return (as attempted in this example)???
User avatar
os64dev
Member
Member
Posts: 553
Joined: Sat Jan 27, 2007 3:21 pm
Location: Best, Netherlands

Post by os64dev »

brendan gave the correct hints and your code does not use them. Try again ;-). You can call protected mode but then you have to take into account the fact that your push/pop call/ret behaviour changes, so it is better to do it with a jump.

You cannot use BITS 32 before you have done the jump, because the compiler might generate opcode the PC does not understand at that time.

Again use Brendan's framework.
Author of COBOS
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

okay, but when i am in real mode, an instruction like

Code: Select all

mov eax, 12
refers to the 16-bit register ax rather than eax, right?

how can i use the eax register for calculations in real mode?
urxae
Member
Member
Posts: 149
Joined: Sun Jul 30, 2006 8:16 am
Location: The Netherlands

Post by urxae »

sancho1980 wrote:okay, but when i am in real mode, an instruction like

Code: Select all

mov eax, 12
refers to the 16-bit register ax rather than eax, right?
Wrong
how can i use the eax register for calculations in real mode?
Just use the instruction you quoted.
While the opcode that means "mov eax, 12" in 32-bit mode will reference ax in 16-bit mode, there is an encoding to use eax instead while in 16-bit mode (using a prefix byte). If "[BITS 16]" is in effect, the assembler will put that prefix before your instruction automatically if you use eax instead of ax.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

ah cheers, i didnt know that...will try out when i finish work ;)
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

hi
i've tried it out and followed your steps..it still doesnt work for me, please can you have a look at it, mykernel.asm now looks like this (remarks in code):

[edit]id better upload it again, this looks terrible[/edit]
Attachments
mykernel2.asm
(4.37 KiB) Downloaded 50 times
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Post by Brendan »

Hi,

This code:

Code: Select all

    mov [cbase1], bx		;of the real mode cs
    shr ebx, 16			;into the base fields of
    mov [cbase2], bx		;the 32-bit code segment in the gdt

    mov ebx, eax		;move the linear address
    mov [cbase16_1], bx		;of the real mode cs
    shr ebx, 16			;into the base fields of
    mov [cbase16_2], bx		;the 16-bit code segment in the gdt
Will trash the "type" field of each descriptor. Change to:

Code: Select all

    mov [cbase1], bx		;of the real mode cs
    shr ebx, 16			;into the base fields of
    mov [cbase2], bl		;the 32-bit code segment in the gdt

    mov ebx, eax		;move the linear address
    mov [cbase16_1], bx		;of the real mode cs
    shr ebx, 16			;into the base fields of
    mov [cbase16_2], bl		;the 16-bit code segment in the gdt

For this code:

Code: Select all

gdt_desc:
	dw gdt_end - gdt - 1	;limit of the gdt
	dd gdt			;base address of the gdt
I have a feeling the offset is wrong - the "dd gdt" may need to be "dd gdt + 0x200" because the boot loader loads the code at 0x0020:0x0000.

If you are loading the kernel code at 0x0020:0x0000 (or 0x00000200) then you're trashing the BIOS's data area while you're using the BIOS. I'd choose somewhere else to load the kernel (then disable IRQs and relocate it when you're finished with the BIOS if it needs to be at 0x00000200).

This code doesn't do anything:

Code: Select all

    mov eax, cr0	;disable
    or eax, 0		;protected
    mov cr0, eax	;mode
Instead you'd want:

Code: Select all

    mov eax, cr0	;disable
    and al, 0xFE	;protected
    mov cr0, eax	;mode
Once you disable protected mode CS will still equal 0x18 (left over from 16-bit protected mode). If an interrrupt occurs the CPU will push CS:IP on the stack (or 0x0018:0x????) and then do the interrupt and try to return to 0x0018:0x????). When it does try to return the 0x18 won't be correct because you're not in protected mode anymore. To fix this you should do something like this:

Code: Select all

    mov eax, cr0	;disable
    and al, 0xFE	;protected
    mov cr0, eax	;mode
    jmp 0x0020:.here
.here:
   sti

Also, in your boot loader you should probably change:

Code: Select all

    mov [drive], dl
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0ffffh
To:

Code: Select all

    mov ax, cs
    mov ds, ax
    mov [drive], dl
    mov es, ax
    cli
    mov ss, ax
    mov esp, 0fffch
    sti
I'm not sure if there's other problems, but that should give you something to start with...


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

thank you so much! it works now, i only had to make very few corrections to what you suggested...seems like this whole mode-switching is pretty subtle! i only have one question: why do i have to disable the interrupts in the boot loader? it works like that! do you have an explanation for that? i mean, your argument sounds reasonable, but still im surprised that it works without the disabling...

thanks

martin

ps: and by the way, the bootloader doesnt really load the kernel at 0x0020:0x0000! the bootloader itself is loaded at 07C0h:0 and it loads the kernel exactly 512 bytes after that ;)

pps: i only now realised that you actually suggested to replace sp with esp and change 0ffffh for 0fffch in

Code: Select all

mov esp, 0fffch
Whats the reason for that?

And also one more question please: when going back into real mode from protected mode, why is it necessary to first load a 16-bit protected mode segment? why can i not jump directly to the 16-bit real mode segment straight after turning off pm??
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

sancho1980 wrote:pps: i only now realised that you actually suggested to replace sp with esp and change 0ffffh for 0fffch in

Code: Select all

mov esp, 0fffch
Whats the reason for that?
The stack must be aligned to a (I think) DWORD (4 byte) boundary.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Post by Brendan »

Hi,
sancho1980 wrote:thank you so much! it works now, i only had to make very few corrections to what you suggested...seems like this whole mode-switching is pretty subtle! i only have one question: why do i have to disable the interrupts in the boot loader? it works like that! do you have an explanation for that? i mean, your argument sounds reasonable, but still im surprised that it works without the disabling...
In the boot loader, I'd disable interrupts, then change SS, then change ESP/SP, then enable interrupts again. The reason for this is that it may be possible to get an IRQ after you've changed SS but before you've changed ESP/SP. For example, if SS:SP was left as 0x3000:0x0200 by the BIOS and you're changing it to 0x07C0:0xFFFC, then there's a very small amount of time where SS:SP would be 0x07C0:0x200 (and your code would be trashed if an IRQ occurs). Modern Intel CPUs do actually disable interrupts for one instruction after loading SS so this doesn't happen (mostly because of old buggy software), but there's no guarantee that CPUs from all other manufacturers do this.

Misaligned data slows the CPU down, and everything should have "natural" alignment to improve performance (words with 2 byte alignment, dwords with 4 byte alignment, etc). Setting SP to 0xFFFF means that every push and every pop is misaligned.

I also tend to use 32-bit registers for addresses (in real mode) because the CPU is more flexible with 32-bit addressing modes. For example, you can't do "mov cx,[ax*4+sp]" but you can do "mov cx,[eax*4+esp]". It's also possible to use the same stack in real mode, 16-bit protected mode and 32-bit protected mode, where you change SS but leave ESP to whatever it was (although this can cause alignment problems if you switch from a 16-bit stack to a 32-bit stack).
sancho1980 wrote:ps: and by the way, the bootloader doesnt really load the kernel at 0x0020:0x0000! the bootloader itself is loaded at 07C0h:0 and it loads the kernel exactly 512 bytes after that ;)
Are you sure? The boot loader from your first post in this topic has the following:

Code: Select all

    call 020h:0 ;call the kernel
It also uses the BIOS to read a sector from disk to ES:BX (where ES:BX = 0x0020:0x0000).
sancho1980 wrote:And also one more question please: when going back into real mode from protected mode, why is it necessary to first load a 16-bit protected mode segment? why can i not jump directly to the 16-bit real mode segment straight after turning off pm??
Sometimes the CPU does things to improve performance. For segment registers, it keeps track of some details (e.g. the default addressing size, the default operand size, the segment limit and the segment base) so that it doesn't need to constantly look up the corresponding entry in the GDT in protected mode. However, in real mode the only thing that changes is the segment base address, so to speed things up in real mode the CPU doesn't change the other things it keeps track of when a segment register is loaded.

This means (for example) if you enable protected mode and set the limit on GS to 4 GB and then disable protected mode, then the limit on GS will still be 4 GB (and not 64 KB) even though you're in real mode, because the CPU doesn't reset the segment limit. Some people (including me) take advantage of this behavour to have real mode with no segment limits, so they can access everything above 1 MB while still using real mode BIOS functions.

This also means that if you're executing 32-bit protected mode code with CS limit set to 4 GB, it may be possible to switch back to real mode and still have 32-bit default address size, 32-bit default operand size and a 4 GB CS limit (which will make 16-bit BIOS code crash).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

Brendan wrote:
Misaligned data slows the CPU down, and everything should have "natural" alignment to improve performance (words with 2 byte alignment, dwords with 4 byte alignment, etc). Setting SP to 0xFFFF means that every push and every pop is misaligned.

I also tend to use 32-bit registers for addresses (in real mode) because the CPU is more flexible with 32-bit addressing modes. For example, you can't do "mov cx,[ax*4+sp]" but you can do "mov cx,[eax*4+esp]". It's also possible to use the same stack in real mode, 16-bit protected mode and 32-bit protected mode, where you change SS but leave ESP to whatever it was (although this can cause alignment problems if you switch from a 16-bit stack to a 32-bit stack).
Is that the reason why you choose 0xFFFC in RM for a ss? Other than that, I'd say it's got to be 0xFFFE, because in RM the stack needs only be aligned on a word boundary, right?
Brendan wrote:
sancho1980 wrote:ps: and by the way, the bootloader doesnt really load the kernel at 0x0020:0x0000! the bootloader itself is loaded at 07C0h:0 and it loads the kernel exactly 512 bytes after that ;)
Are you sure? The boot loader from your first post in this topic has the following:

Code: Select all

    call 020h:0 ;call the kernel
It also uses the BIOS to read a sector from disk to ES:BX (where ES:BX = 0x0020:0x0000).
True, thanks again...I thought I had done it differently...
Post Reply