Issue with custom Linux Bootloader

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.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Issue with custom Linux Bootloader

Post by Moffatt »

I'm in the process of writing a Linux Bootloader for 386

I've written some C code that copies the real mode portion of the Linux kernel to 0x90000 and the protected mode portion to 0x100000. This code is running in Protected Mode, the next step from the boot.txt in the x86/i386/ is to be in real mode and jump to the segment of the real mode portion of the Linux Kernel offset offset by 0x20.

After my C code is done coping it will call the LinuxBoot Procedure in the following Code. Please Note that Real Mode code is from the following file.

Code: Select all

.386p

PROT_MODE_CSEG EQU	0x8
PROT_MODE_DSEG EQU	0x10
PSEUDO_RM_CSEG EQU	0x18
PSEUDO_RM_DSEG EQU	0x20

LINUX_SETUP_STACK  EQU		0x9000
LINUX_KERNEL_SEGMENT  EQU	0x9000
LINUX_KERNEL_ENTRY_POINT EQU	0x9020

.code
_TEXT32 SEGMENT PARA PUBLIC USE32 'CODE16'
ASSUME CS:_TEXT32, DS:NOTHING
org 0x0

	;Disable the Interrupts before beginning
	cli

	; Load the Segment Registers, with Segment Limit
        mov     eax, PSEUDO_RM_DSEG
        mov     ss, eax
       	mov     ds, eax
      	mov     es, eax
        mov     fs, eax
        mov     gs, eax

	; Load the Interrupt Vector Table
	lidt 	FWORD PTR idt_real

	; Clear the PE flag in the CR0 register
        mov     eax, cr0            ; Get the current CR0
        and     al, NOT 1           ; Clear the PE bit to enable real mode
        mov     cr0, eax            ; NOW WE'RE IN REALMODE!

	; Reload CS
	; Try a jmp
	;jmp far ptr jumphere
	db	0xea
	dw    	0x20
        dw      0x1000

idt_real:
	dw  0x3ff	;256 Entries, 4b each =1K
	dd  0x0000	;Real Mode IVT is at 0x0000

_TEXT32 Ends

_TEXT16 SEGMENT PARA PUBLIC USE16 'CODE16'
ASSUME CS:_TEXT16, DS:NOTHING

org 0x20

	;Change Bx to linux kernel segment
	mov 	bx, LINUX_KERNEL_SEGMENT
	mov	ss, bx
	mov	sp, LINUX_SETUP_STACK

	mov	ds, bx
	mov	es, bx
	mov	fs, bx
	mov	gs, bx

	;/* jump to start */
	;/* ljmp */
	; Always jump to LINUX_KERNEL_ENTRY_POINT
	db	0xea
	dw	0
	dw 	LINUX_KERNEL_ENTRY_POINT

_TEXT16 ends

end

I build it into a COM file, remove the header and include it in the previous Assembly file as machine code. I realize this probably isn't the best/proper way to do this. However I'm pressed for time and this appears to work.

My assembly is the following:

Code: Select all

.586p

PROT_MODE_CSEG EQU	0x8
PROT_MODE_DSEG EQU	0x10
PSEUDO_RM_CSEG EQU	0x18
PSEUDO_RM_DSEG EQU	0x20

LINUX_SETUP_STACK  EQU		0x9000
LINUX_KERNEL_SEGMENT  EQU	0x9000
LINUX_KERNEL_ENTRY_POINT EQU	0x9020


;Protected Mode Code
_TEXT32 SEGMENT PARA PUBLIC USE32 'CODE32'
ASSUME CS:_TEXT32, DS:NOTHING

PUBLIC LinuxBoot
LinuxBoot PROC


	;Copy Real Mode Code to proper location
	mov ecx, EndRealCode -StartRealCode
	mov esi, StartRealCode
	mov edi, 0x10000
       	cld
	rep  movsb

	;/* just in case, set GDT */
        lgdt    FWORD PTR gdtdesc  ; Load the GDTR

	;Jump and hope for the Best
	db	0xea
	dd	0
	dw	PSEUDO_RM_CSEG

LinuxBoot ENDP


StartRealCode:
; This contains the Machine Code from linuxboot.asm
EndRealCode:


;/*
; * This is the Global Descriptor Table
; *
; *  An entry, a "Segment Descriptor", looks like this:
; *
; * 31          24         19   16                 7           0
; * ------------------------------------------------------------
; * |             | |B| |A|       | |   |1|0|E|W|A|            |
; * | BASE 31..24 |G|/|0|V| LIMIT |P|DPL|  TYPE   | BASE 23:16 |
; * |             | |D| |L| 19..16| |   |1|1|C|R|A|            |
; * ------------------------------------------------------------
; * |                             |                            |
; * |        BASE 15..0           |       LIMIT 15..0          |
; * |                             |                            |
; * ------------------------------------------------------------
; *
; *  Note the ordering of the data items is reversed from the above
; *  description.
; */


gdt:
	; 0(selector=0x0000): Null descriptor
	dw	0x0000
	dw	0x0000
	db	0x00
	db	0x00
	db	0x00
	db	0x00

	; 1(selector=0x0008): Code segment
	dw	0xFFFF		; limit: xFFFF
	dw	0x0000          ; base : xxxx0000
	db	0x00            ; base : xx00xxxx
	db	0x9A            ; Code e/r, Present, DPL0
	db	0xCF            ; limit: Fxxxx, Page Gra, 32bit
	db	0x00            ; base : 00xxxxxx

	; 2(selector=0x0010): Data Segment
	dw	0xFFFF		; limit: xffff
	dw	0x0000          ; base : xxxx0000
	db	0x00		; base : xx00xxxx
	db	0x92            ; Data, Present, DPL0
	db	0xCF		; limit: Fxxxx, Page Gra, 32bit
	db	0x00            ; base : 00xxxxxx

	; 3(selector=0x0018): 16 bit Real Mode Code Segment
	dw	0xFFFF		; limit: xffff
	dw	0x0000          ; base : xxxx0000
	db	0x01            ; base : xx01xxxx
	db	0x9E            ; Code Conform, Present, DPL0
	db	0x00            ;
	db	0x00            ; base : 00xxxxxx

	; 4(selector=0x0020): 16 bit Real Mode Data Segment
	dw	0xFFFF		; limit: xffff
	dw	0x0000          ; base : xxxx0000
	db	0x01            ; base : xx01xxxx
	db	0x92            ; Data, Present, DPL0
	db	0x00            ;
	db	0x00            ; base : 00xxxxxx


;/* this is the GDT descriptor */
gdtdesc:
	dw	0x27		; size 39 (8 * 5 -1)bytes
	dd 	gdt    		; address

_TEXT32 ENDS

END
Any assistance is appreciated.

-Moffatt
ru2aqare
Member
Member
Posts: 342
Joined: Fri Jul 11, 2008 5:15 am
Location: Hungary

Re: Issue with custom Linux Bootloader

Post by ru2aqare »

Moffatt wrote:I'm in the process of writing a Linux Bootloader for 386...
Any assistance is appreciated.
-Moffatt
But what exactly is your question?

Also MS-DOS COM files have no header. They are loaded at offset 0x100 of any suitable (64K) segment.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

Sorry I explained everything thing I have done not the problem. I'm getting a little scatter brained from all of this.

My issue is that my target will just halt, the kernel doesn't appear to be executing. I currently suspect my assembly code that is changing back to real mode.

When I compile the COM file I remove the first 32 bytes, and only place the executable code into my other assembly file.

-Moffatt
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Issue with custom Linux Bootloader

Post by Brendan »

Hi,
Moffatt wrote:I currently suspect my assembly code that is changing back to real mode.
Where is the assembly code that changes back to real mode?

[EDIT] Doh - my mistake - I was looking in the protected mode code for the part that switches back to real mode... [/EDIT]
Moffatt wrote:When I compile the COM file I remove the first 32 bytes, and only place the executable code into my other assembly file.
Is there a good reason for this? I'm used to assemblers that let you mix 32-bit code and 16-bit code in the same binary (but then I'm used to assemblers that will let you write a tricky JMP instruction without resorting to "db 0xEA" too - maybe I've been spoilt for the last decade?)... ;)


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.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

I am currently using Watcom's Assembler.

The code that changes to real mode cannot be easily compiled into my protected mode code, due to the fact when I link it all together the offset is set to 0x10000000.

This is dictated to me, and I cannot change. So if I attempt to place the real mode code into this binary it will be left out.

That's why I've copied the machine code into my Protected mode assembly.

Possibly off topic question is anyone aware of a way to boot the 2.6.21 Linux kernel in protected mode? If this is possible I don't need to change back to Real Mode at all.

-Moffatt
PatrickV
Member
Member
Posts: 151
Joined: Sun Jul 06, 2008 7:50 pm
Location: New Zealand
Contact:

Re: Issue with custom Linux Bootloader

Post by PatrickV »

I would say that it is very difficult to get a 386 in p mode. I have a 486 which can not handel more than 16mb of ram which windows 95/98 can not handel. I know that windows 95/98 works fine on pentum II with 64 mb ram. But not 486. I don't know. I 've tried loading some of the operating system that people make around and some of them don't work on a 486 platform
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Issue with custom Linux Bootloader

Post by Combuster »

I have win'98 running off a 486DX. Not the fastest setup but I can guarantuee that the 486 is *not* the problem. And I see no reason to blame a 386 either.

Needless to say there are things that are not supported on older processors, but for a bootloader you won't quickly come across any such things.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Issue with custom Linux Bootloader

Post by Brendan »

Hi,
Moffatt wrote:Possibly off topic question is anyone aware of a way to boot the 2.6.21 Linux kernel in protected mode? If this is possible I don't need to change back to Real Mode at all.
I found a document called THE LINUX/I386 BOOT PROTOCOL which describes everything, including a "32-bit BOOT PROTOCOL" (the last section). I also took a quick look at some of the GRUB source code (e.g. "stage2/boot.c") which is interesting if you want to know about various bugs in different Linux kernels...

Note: I did take a look at your "return to real mode" code, and didn't find much wrong - I'd recommend switching to real mode and then loading all the segment registers a second time though (so they contain real mode compatible values, rather than "PSEUDO_RM_DSEG").


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.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

The 32-bit Boot Protocol is not implemented in Linux Kernel 2.6.21, which is what I'm using. However I will definetly look into that for when I change kernels.

Thanks for looking over my code, maybe I miss understand, but is the following reloading all the segment registers with valid values:

Code: Select all

 ;Change Bx to linux kernel segment
   mov    bx, LINUX_KERNEL_SEGMENT
   mov   ss, bx
   mov   sp, LINUX_SETUP_STACK

   mov   ds, bx
   mov   es, bx
   mov   fs, bx
   mov   gs, bx
Where the LINUX_KERNEL_SEGMENT and LINUX_SETUP_STACK are defined as the following:

Code: Select all

LINUX_SETUP_STACK  EQU      0x9000
LINUX_KERNEL_SEGMENT  EQU   0x9000
Another issue that I'm experiencing is that when I attempt to send a byte out the serial port in any of the code after I make the following jump

Code: Select all

   ;Jump and hope for the Best
   db   0xea
   dd   0
   dw   PSEUDO_RM_CSEG
I get nothing, however before that all jump any bytes I send are received correctly.

The following is my code for writing a byte out the serial port. The serial port is already configured for correct baud rate and such.

Code: Select all

COM_BASE   EQU    0x3f8
COM_TX_BUFFER_REG  EQU    (COM_BASE + 0x00)
COM_LINE_STATUS_REG  EQU    (COM_BASE + 0x05)
LS_THR_EMPTY   EQU    0x20

;Macro for Writing a Byte out the Port for Debug
writeDebugByte MACRO character

            mov     dx, COM_LINE_STATUS_REG
	    @@:
            in      al, dx
            and     al, LS_THR_EMPTY
            jz      @b
            mov     dx, COM_TX_BUFFER_REG
            mov     al, character
            nop
            out     dx, al

ENDM

-Moffatt
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Issue with custom Linux Bootloader

Post by Brendan »

Hi,

Moffatt wrote:Thanks for looking over my code, maybe I miss understand, but is the following reloading all the segment registers with valid values:

Code: Select all

 ;Change Bx to linux kernel segment
   mov    bx, LINUX_KERNEL_SEGMENT
   mov   ss, bx
   mov   sp, LINUX_SETUP_STACK

   mov   ds, bx
   mov   es, bx
   mov   fs, bx
   mov   gs, bx
That should work, as long as it's done after protected mode is disabled (and before you jump to the Linux setup code). For example (NASM syntax):

Code: Select all

      bits 32

switch_to_real_mode:

      lidt [PTR idt_real]

      jmp PSEUDO_RM_CSEG:.l1   ;Make sure CS is 16-bit

      bits 16

      ;Load 16-bit protected mode selectors
.l1:  mov ax,PSEUDO_RM_DSEG    ;ax = 16-bit data selector
      mov ds,ax
      mov es,ax
      mov fs,ax
      mov gs,ax
      mov ss,ax

      ;Disable protected mode
      mov eax, cr0
      and al, 0xFE
      mov cr0, eax

%define THIS_CODE_SEG	0x1000

      jmp THIS_CODE_SEG:.l2    ;Flush instruction pipeline and load CS with real mode value
.l2:
      ;Load real mode values into segment registers
      mov  bx, LINUX_KERNEL_SEGMENT
      mov ds, bx
      mov es, bx
      mov fs, bx
      mov gs, bx
      mov ss, bx
      mov sp, LINUX_SETUP_STACK

      ;Jump to Linux kernel

      jmp LINUX_KERNEL_SEGMENT:LINUX_KERNEL_ENTRY_POINT
Moffatt wrote:Another issue that I'm experiencing is that when I attempt to send a byte out the serial port in any of the code after I make the following jump

Code: Select all

   ;Jump and hope for the Best
   db   0xea
   dd   0
   dw   PSEUDO_RM_CSEG
I get nothing, however before that all jump any bytes I send are received correctly.

The following is my code for writing a byte out the serial port. The serial port is already configured for correct baud rate and such.
Is the code for sending a byte to the serial port 16-bit code, or is it 32-bit code? If it's assembled as 16-bit code then it'll only work in in 16-bit protected mode (CS = PSEUDO_RM_CSEG) or in real mode (CS = 0x1000 I think), and if it's 32-bit code it'll only work in 32-bit protected mode (CS = PROT_MODE_CSEG).

I'm guessing you need 2 versions of this code - a 16-bit version and another 32-bit version. Even if you use exactly the same instructions, the bytes of code generated by the assembler will be different.


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.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

I've attempted to implement what you suggested, it attached below.
I'm using MASM syntax, and had to hardcode some of the jumps.

When I execute it will be fault somwhere in this code, and does not output the 0x46 to the serial port.

Code: Select all

.386p

PROT_MODE_CSEG EQU	0x8
PROT_MODE_DSEG EQU	0x10
PSEUDO_RM_CSEG EQU	0x18
PSEUDO_RM_DSEG EQU	0x20

LINUX_SETUP_STACK  EQU		0x9000
LINUX_KERNEL_SEGMENT  EQU	0x9000
LINUX_KERNEL_ENTRY_POINT EQU	0x9020


COM_BASE   EQU    0x3f8
COM_TX_BUFFER_REG  EQU    (COM_BASE + 0x00)
COM_LINE_STATUS_REG  EQU    (COM_BASE + 0x05)
LS_THR_EMPTY   EQU    0x20


writeDebugByte MACRO character

            mov     dx, COM_LINE_STATUS_REG
	    @@:
            in      al, dx
            and     al, LS_THR_EMPTY
            jz      @b
            mov     dx, COM_TX_BUFFER_REG
            mov     al, character
            nop
            out     dx, al

ENDM



.code
_TEXT32 SEGMENT PARA PUBLIC USE32 'CODE16'
ASSUME CS:_TEXT32, DS:NOTHING
org 0x0

	writeDebugByte 0x46

	;Disable the Interrupts before beginning
	cli

	; Load the Interrupt Vector Table
	lidt 	FWORD PTR idt_real

	; Reload CS with 16-bit value
	; Try a jmp
	db	0xea
	dw    	0x50
        dw      0x1000

idt_real:
	dw  0x3ff	;256 Entries, 4b each =1K
	dd  0x0000	;Real Mode IVT is at 0x0000

_TEXT32 Ends

_TEXT16 SEGMENT PARA PUBLIC USE16 'CODE16'
ASSUME CS:_TEXT16, DS:NOTHING

org 0x50

	; Load the Segment Registers, with Segment Limit
        mov     ax, PSEUDO_RM_DSEG
        mov     ss, ax
       	mov     ds, ax
      	mov     es, ax
        mov     fs, ax
        mov     gs, ax

	; Clear the PE flag in the CR0 register
        mov     eax, cr0            ; Get the current CR0
        and     al, NOT 1           ; Clear the PE bit to enable real mode
        mov     cr0, eax            ; NOW WE'RE IN REALMODE!

	; Reload CS with Real Mode value
	; Try a jmp
	db	0xea
	dw    	0x150
        dw      0x1000

org 0x150

	;Change Bx to linux kernel segment
	mov 	bx, LINUX_KERNEL_SEGMENT
	mov	ss, bx
	mov	sp, LINUX_SETUP_STACK

	mov	ds, bx
	mov	es, bx
	mov	fs, bx
	mov	gs, bx

	;/* jump to start */
	; Always jump to LINUX_KERNEL_ENTRY_POINT
	db	0xea
	dw	0
	dw 	LINUX_KERNEL_ENTRY_POINT

_TEXT16 ends

end


Thanks again for your assistance.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Issue with custom Linux Bootloader

Post by Brendan »

Hi,
Moffatt wrote:I've attempted to implement what you suggested, it attached below.
I'm using MASM syntax, and had to hardcode some of the jumps.

When I execute it will be fault somwhere in this code, and does not output the 0x46 to the serial port.
It all looks right to me.

Have you considered trying to boot it with Bochs? That way you'd be able to use the Bochs debugger to find out exactly where the problem is (single step through instructions, set breakpoints, examine the contents of memory and registers, etc).


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.
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

Funny, that's what I going to attempt to do today.

Thanks for your input, I'll make sure to update and note what I was doing wrong.

-Moffatt
Moffatt
Posts: 8
Joined: Thu Nov 13, 2008 12:31 pm

Re: Issue with custom Linux Bootloader

Post by Moffatt »

In the process of my attempting to emulate this scenario with BOCHS, I'm trying to copy the real mode portion of the Kernel from a Floppy, and place it into Memory.

The Real Mode Portion of the Kernel is at 0x90000 in my Floppy image. The following is my code attempting to load the floppy.

Code: Select all

         mov ah, 0x02 							; read
	; The RealMode code is 0x1c00 bytes long, therefore 14 sectors
	mov al, 0x0e					
       ; Select the First Floppy Drive
	mov dl, 0x0				
	; Select the First Head	
	mov dh, 0x00 							; head
	; Placed Real Mode code at 0x90000 therefore Track number is 64
	mov ch, 0x40 							; track
	; And Sector Number is 0
	mov cl, 0x0
	; Need to place the Real Mode code at 0x90000
	mov bx, 0x9000
	mov es, bx
	mov bx, 0x0
        ; Call the Function
        int 0x13
It doesn't appear to copy anything, when I check the Memory with BOCHS.

-Moffatt
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Issue with custom Linux Bootloader

Post by Brendan »

Hi,
Moffatt wrote:It doesn't appear to copy anything, when I check the Memory with BOCHS.
It should load something, just probably not what you want... You could preset the area to something first (e.g. fill the area from 0x90000 to 0x91BFF with 0xA5 bytes) so that you know something was loaded if all those bytes are changed to zeros.

Code: Select all

	; Placed Real Mode code at 0x90000 therefore Track number is 64
	mov ch, 0x40 							; track
	; And Sector Number is 0
	mov cl, 0x0
If the Linux's real mode code is stored on the disk after 0x90000 bytes, then that works out to sector number 1152.

For a 1440 KiB floppy there's 18 sectors per track and 2 heads (or 36 sectors per cylinder). That means the first sector would be on track 32, head 0 (not track 64, unless it's a single sided 720 KiB disk, or a 2880 KiB disk with 36 sectors per track and 72 sectors per cylinder). Also, for the BIOS the first sector in a track is sector number 1 (not sector number 0 like a sane person would expect), so you'd want "mov cl, 0 + 1".


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.
Post Reply