Trouble with 2 Stage Bootloader - Calling 2nd Stage

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.
lesniakbj
Posts: 13
Joined: Wed Sep 30, 2015 8:03 am

Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by lesniakbj »

Hi All,

I will cut to the chase, I am having trouble loading my second stage of my bootloader, and can not quite figure out why. I don't want to dump code, but I have no easier way to express my issue:

Bootstage01:

Code: Select all

;==================================================
; The following lines of code are interpreted by the
; compiler, and are not a part of the output binary.
; The directives are specified below in comments.
;==================================================
[BITS 16]		; When loaded by the operating system, we are
				; loaded into 16 bit mode. Thus, the kernel
				; boot loader needs to be 16 bits.

[ORG 0x7C00]	; We get loaded into memory at location 0x7C00
				; by BIOS

jmp bootloader_start	; Safely jump ourselves away from any stored
						; data in the data segment.

; OEM Parameter Block
oemName 		db "BrenOS  "	; Must be 8 Bytes long, thus padded with spaces
bytesPerSector:	dw 512
sectorsPerClus: db 1
reservedSectrs: db 1
numberOfFATs:	db 2
rootEntries:	db 224
totalSectors:	dw 2880
media:			db 0xF0
sectorsPerFAT:	dw 9
sectorsPerTrk:	dw 18
headsPerCyln:	dw 2
hiddenSectors:	dd 0
totalSectBig:	dd 0
driveNum:		db 0
unused:			db 0
extBootSig:		db 0x29
serialNum:		dd 0xa0a1a2a3
volLabel:		db "BOS FLOPPY "
fileSystem:		db "FAT12   "

;=======================================
;		CODE SEGMENT
;=======================================

;=======================================
; Function: 
; 	bootloader_start()
; Params:
;	<NONE>
; Description: 
; 	Boot01 Entry Point, does the
; 	following functions:
; 		- Set up Segment Registers 
;		- Set up Early Stack for Use
;		- Load Boot02, which lives
;	  	  just beyond Boot01 on Disk.
;		- Jump control to Boot02. 
;=======================================
bootloader_start:
	; ---------------------------
	; SETUP SEGMENTS, 0000:7C00
	; ---------------------------
	cli				; Clear interrupts before we set segments or
					; the stack...
	xor ax, ax		; 0 out eax to clear junk
	mov ds, ax		; Set the current data segment offset to 0
	mov es, ax		; Do the same with es segment registers

	
	;-------------------------
	; SETUP STACK, 0000:9E00
	;-------------------------
	; Ok, now it's time to set up a stack for our stage01 boot loader
	; to use. This will be used for function calls, and getting ready
	; for our stage02 boot loader. 
	mov ax, 0x9E00		; Set up 4K of stack space after this boot loader
						; code. Start with where this code is loaded
						; from. 
	mov ss, ax			; Point our SS to the segment directly after
						; the boot loader
	mov sp, 4096		; Move our stack pointer to SS:4096, giving us
						; 4K of stack space to work with.
	sti					; ... and restore our interrupts.

	
	;--------------------------;
	; 	 SETUP SCREEN MODE
	;--------------------------;
	call set_screen_mode

	call clear_screen	; Clear the screen before we try to print
						; any strings to the screen

						
	;--------------------------;
	; STACK SETUP INFORMATION
	;--------------------------;
	mov si, stackmsg
	call write_string
	mov ax, ss			; Move SS into AX for printing
	mov dx, ax
	call print_hex
	
	mov si, newline
	call write_string

	mov si, stkptmsg
	call write_string
	
	xor ax, ax
	xor dx, dx
	mov ax, sp
	mov dx, ax
	call print_hex

	mov si, newline
	call write_string
	
	;--------------------------;
	; 		DISK SECTION
	;--------------------------;
	; Now that we have detected our Low Memory, we want to do some
	; set up so that we can read our 2nd stage boot loader from
	; the rest of the drive. 
	call reset_disk
	
	; Ok... Now we need to read some sectors from the disk into our
	; disk buffer space... wait... we need to set up a disk buffer
	; space for ourselves first.
	; Frankly, on second thought, at this level there is no idea of "reserved"
	; space. Rather, I have a slot in memory that I am given to use. 
	; Hopefully we don't overflow...
	mov ax, 0x7E00	; AX = Address where we are going to read a sector
					; into. This is the beginning of the disk buffer, 
					; the first bytes beyond the boot loader.
	mov es, ax		; ES:BX = The where the sectors will be read to
	xor bx, bx		; 0x7E00:0x0000 -> ES:0
	
	; mov byte [disk_count], 5
	call read_disk
	
	; The disk has been read, lets make sure we we read the
	; correct number of sectors
	; cmp al, 1
	; jne disk_error
	
	;--------------------------;
	; 	  CONTROL TRANSFER
	;--------------------------;
	; Now that we have read that sector to the disk, we can jump to it and
	; continue execution! Unfortunately, this will not quite work yet, 
	; as there is no 2nd stage for me to load yet. Thus this is
	; commented out...	
	
	; Now, before we go jumping to the new code, we want to do some sanity
	; checking. That is, I want to ensure that this boot01 code was loaded
	; where I think it was. This checks that the boot signature (0xAA55) 
	; was loaded in the correct location. 
	
	mov si, keymsg
	call write_string
	call wait_for_keypress
	
	jmp 0x7E00
	
	; jmp boot_end
	
;=======================================
; Function: 
;	set_screen_mode()
; Params:
;	AX = Screen Mode
;		0x0003: 80 x 50 Text Mode
;		0x1112: 8 x 8 Font
; Description: 
;	Sets the screen to the desired
;	resolution (80x50 with an 8x8 font).
;	Requires int 0x0010 calls.
;=======================================
set_screen_mode:
	
	mov ax, 0x0003		; First set the screen mode to 80x50 Text Mode
	int 0x0010			; BIOS Video function to set mode.
	
	xor bx, bx			; Clear bx
	mov ax, 0x1112		; Now we want to set the font to an 8x8 Font
	int 0x0010
	
	ret

clear_screen:
	mov ah, 0
	int 0x10
	ret

write_string:
	lodsb					; Load the string buffer at ds:si
	or al, al				; or the current character to ...
	jz .write_string_end	; If it is 0 (null terminator) jump to the
							; end print function. Else continue.
	call write_character	; Write the character that is in the buffer
	jmp write_string		; Do this until the buffer end is reached

.write_string_end:
	ret

write_character:
	mov bl, 0x04		; Don't make any register assumptions, always
						; set to wanted color here.
	mov ah, 0x0E
	int 0x10
	ret

print_hex:
	mov cx, 4	; Start the counter. AX contains 4 "characters"
				; and thus our counter is 4.  4 bits per char.
				; Control continues to hex_to_char_loop
				
.hex_to_char_loop:
	dec cx				; Pre decrement our counter

	mov ax, dx			; Copy DX to BX so we can mask it
	shr dx, 4			; Shift it 4 bits to the right, so we are 
						; dealing with a value such:
						; 0xF29A  -->  0x00F2
	and ax, 0x0F		; Get the last 4 bits, one character

	mov bx, hex_16_out	; Set BX to our output memory address
	add bx, 2			; Skip the '0x' char in the string
	add bx, cx			; Add the counter to the address so
						; we can address the correct byte in the string

	cmp ax, 0xA			; Check to see if this is a letter or number
	jl .set_letter		; If its a number, set the value immediately
						; otherwise there is some preprocessing
	add byte [bx], 7	; If its a letter, add 7 to offset to ASCII
						; values

	jl .set_letter

.set_letter:
	add byte [bx], al		; Add the value of the byte to the char at bx

	cmp cx, 0				; Check the counter to see if we are done
	je .print_hex_done		; If we are done, head on out of here
	jmp .hex_to_char_loop	; Otherwise, leads head back to the printing
							; loop so we can print the value

.print_hex_done:
	mov si, hex_16_out	; Now that we are done converting to char,
						; lets print that out
	call write_string
	ret

reset_disk:
	mov ah, 0x00		; Move 0 into AH, the function we want to call
						; 0 = reset floppy function
	mov dl, 0x00		; dl = drive number, 0 the current drive
	int 0x13			; Call BIOS reset function
	jc reset_disk		; If the carry was set there was an error
						; resetting the disk, try again.
	ret
	
read_disk_retry:
	call reset_disk
	
read_disk:
	; dec byte [disk_count]
	mov ah, 0x02					; Function 0x02 = Read Disk Sector
	mov al, 1						; AL = # of sectors to read, we want the first sector
	mov ch, 0						; CH = Track number to read from, we are on the
									; 1st track, along with the data
	mov cl, 1						; CL = Sector to Read, we want the second sector (passed 
									; the boot loader code)
	mov dh, 0						; DH = Drive Head Number, the 0th head
	mov dl, 0						; DL = Drive Number, 0th drive is the floppy drive
	int 0x13						; BIOS call to read the sector based on the params
									; set up in the previous block
	; cmp byte [disk_count], 0
	; jne read_disk_retry				; If there is an error, and we haven't tried 5 times, try again

	ret

disk_error:
	mov si, dskerrmsg
	call write_string

	cli
	hlt

wait_for_keypress:
	xor ax, ax
	int 0x16

	ret

boot_end:
	mov si, nobootmsg
	call write_string
	
.boot_finish:
	mov ax, [0x7DFE]	; Before we exit, put the bootsig
						; into AX, so we can verify that we are
						; loaded into the location that we expect.
	mov bx, [bootsig]	; Verify the results with bootsig location.
	jmp .boot_finish
	

;==================================================
; 		DATA SEGEMENT
;==================================================	
; Boot loader Static Messages / Data
newline 	db 0x0A, 0x0D, 0
stackmsg 	db "Stack Segement set to: ", 0
stkptmsg 	db "Stack Pointer setup to: ", 0
dskerrmsg	db "Error reading sector from disk! PANIC!", 0
keymsg 		db "Waiting for keypress to hand control to Boot02..", 0x0A, 0x0D, 0
nobootmsg	db "No Boot02!", 0

; Boot loader data output swap space
hex_16_out: db '0x0000', 0
disk_count	db 0

; Drive information about absolute load location
bootSector 	db 0x00
bootHead	db 0x00
bootTrack	db 0x00
	
TIMES 510 - ($ - $$) db 0	; Compiler macro ($ and $$) that
							; fills all the intermediate space with
							; 0 bytes.

bootsig dw 0xAA55	; Finally, put the boot sector signature
					; at the end of the file.
followed by the 2nd stage bootloader, bootstage02.

Bootstage02:

Code: Select all

; We will be using this Stage02 shell
; to eventually load us into 32 bit mode, along with
; seup our correct segmenting modes.

; NOTE: We are no longer limited to 512 bytes,
; this section can be as large as it needs/wants to ne
; and can consist of a large amount of pre initialization
; to the kernel. 

[ORG 0x7E00]		; We offset to 0 for now, after the shell is done
				; we will use correct segmentation
[BITS 16]		; Yep, still in 16bit Real Mode

jmp boot_stage02_main

; =========================
; 	CODE SEGMENT
; =========================
boot_stage02_main:
	mov si, boot2msg
	call write_string
	
	jmp loop_stub

write_string:
	lodsb
	or al, al
	jz .print_done
	mov ah, 0x0E
	int 0x10
	jmp write_string

.print_done:
	ret

loop_stub:
	jmp loop_stub

; =======================
;     STAGE02 DATA
; ======================
boot2msg db 'Welcome to 2nd stage...', 0
Now, if I am ONLY RUNNING Stage01, I don't run into any issues with the following process:

Code: Select all

nasm bootstage01.asm -f bin -o boot01.bin
qemu-system-x86_86 boot01.bin

-- or --
...
truncate boot01.bin -s 1200k
mkisofs -o iso/boot01.iso -b boot01.bin ./
bochs
The first way of building runs the binary straight in qemu, while the second builds a El Toritoro bootable ISO (if I understand correctly).

My problem arises on the second stage. Here is what I have tried:

Code: Select all

-- THIS VERSION RUNS BUT TRIPLE FAULTS ON 
-- CONTROL TRANSFER
nasm bootstage01.asm -f bin -o boot01.bin
nasm bootstage02.asm -f bin -o boot02.bin
cat boot01.bin boot02.bin > bootloader.bin
qemu-system-x86_64 bootloader.bin

-- or --

-- THIS VERSION FAILS TO BOOT (NO BOOTABLE
-- MEDIA INSERTED)
..
truncate bootloader.bin -s 1200k
mkisofs -o iso/bootloader.iso -b bootloader.bin ./
bochs

-- or --

-- ALSO FAILS WITH NO BOOT
nasm ..
nasm ..
cat ..
dd if=bootloader.bin of=iso/bootloader.iso
bochs
I can't see what I am missing, but I bet it is blindingly obvious.

Note: If I did something wrong with this post (dumping code, formatting, etc...) please let me know and I can clean it up and try to make it succinct.

Note 2: Comments in code are semi-relevant to the code (about 95% of the time they are correct). Unfortunately this has been rolling code, and the comments haven't had the same update love the code has.
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by iansjack »

You appear to be loading the code to 0x7e00:0x0 but trying to run it from 0x0:0x7e00. That's not going to work, is it.
sebihepp
Member
Member
Posts: 190
Joined: Tue Aug 26, 2008 11:24 am
GitHub: https://github.com/sebihepp

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by sebihepp »

Your Stack Setup is also corrupt.

Code: Select all

   ;-------------------------
   ; SETUP STACK, 0000:9E00
   ;-------------------------
   ; Ok, now it's time to set up a stack for our stage01 boot loader
   ; to use. This will be used for function calls, and getting ready
   ; for our stage02 boot loader. 
   mov ax, 0x9E00      ; Set up 4K of stack space after this boot loader
                  ; code. Start with where this code is loaded
                  ; from. 
   mov ss, ax         ; Point our SS to the segment directly after
                  ; the boot loader
   mov sp, 4096      ; Move our stack pointer to SS:4096, giving us
                  ; 4K of stack space to work with.
   sti               ; ... and restore our interrupts.
Your code setup the stack at 0x9E00:0x1000. So your stack is from linear 0x9E000 - 0xAE000 which overwrites the EBDA if it exists.
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: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by Combuster »

Your "image" isn't a multiple of 512 bytes. Some emulators won't load the incomplete sector - i.e. the entire second stage.
"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 ]
lesniakbj
Posts: 13
Joined: Wed Sep 30, 2015 8:03 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by lesniakbj »

Well... lets start here..
Combuster wrote:Your "image" isn't a multiple of 512 bytes. Some emulators won't load the incomplete sector - i.e. the entire second stage.
Correct. I found this out after browsing youtube and the corners of the internet. Padding the second sector should make it work. I did have an incomplete 2nd sector. There is one question related to this posted at the end, although it may be simply due to the fact I was doing segmentation incorrectly. On to the next point...
iansjack wrote:You appear to be loading the code to 0x7e00:0x0 but trying to run it from 0x0:0x7e00. That's not going to work, is it.
Correct again, although I am still unclear exactly on how segmentation works. Again, after searching I found that I was doing my segmentation completely incorrectly, but still don't quite understand it. Bringing me to another question at the end of the post. Finally...
sebihepp wrote:Your Stack Setup is also corrupt.

-- SNIP --

Your code setup the stack at 0x9E00:0x1000. So your stack is from linear 0x9E000 - 0xAE000 which overwrites the EBDA if it exists.
Thank you for pointing this out. My 2nd stage bootloader was going to be the one that detects the BIOS memory map, but that would have not been corrupted. While not the end of the world, it is important to point that out. Thank you.

Now that I have addressed your comments, I have a couple more questions that I want to clarify before I get back at it.

1) Segmentation and the Segment Registers. I have a lot of confusion when it comes to memory addressing (clearly, as you all pointed out). Does anyone have a decent explanation of how the segment registers should be used/setup, or how segmentation works in Real Mode?

2) Combuster mentioned that I have an "Image" implying that my flat binary file is not a complete image. How do I go about make a proper portable image to use? I want to be able to test this image in various emulators/VM's (Bochs, Qemu, Hyper-V, VMWare, etc..)

3) How do I determine which drive the boot loader code was loaded off of? In a minimalist example I found, right after the boot loader code was loaded off of the drive, dl was still loaded with the drive number. Is this assumption correct?

I was able to make something that minimally reads from the drive and loads the 2nd sector into memory, but I was only able to make it work when I have the code saved as 1 contiguous file (Note: The included functions are a write_hex/write_string function, if they are needed I will post them):

Code: Select all

[ORG 0x7C00]

mov bp, 0xFFFF
mov sp, bp

call read_from_disk

mov si, BOOT2_SAMEFILE_MSG
call write_string

jmp $

%include "funcs/output_functions.asm"

read_from_disk:
	mov ah, 0x02	; Read Sector Function
	
	mov al, 1		; Number of Sectors to Read
	mov ch, 0		; Use the 1st Cylinder/Track
	mov dh, 0		; Use the 1st Read/Write Head
	mov cl, 2		; Read the 2nd Sector
	
	; Where to buffer the disk read to...
	; ES:BX
	mov bx, 0
	mov es, bx		; ES -> 0
	mov bx, 0x7E00	; BX -> 0x7E00 = 0x0000:0x7E00
	
	int 0x13
	
	jc .disk_read_error
	ret 

.disk_read_error:
	mov si, READ_ERROR
	call write_string
	jmp $
	
;===================;
;	BOOT-1 DATA
;===================;
READ_ERROR: db "Error reading disk!", 0
	
TIMES 510 - ($ - $$) db 0x00 
dw 0xAA55

BOOT2_SAMEFILE_MSG:	db "Has this been read into memory yet?!?", 0

; NOTE:
; ======================
; Some emulators and disk drives will
; not read a sector unless it is fully 
; padded out, thus we need to pad this
; sector or it will not be read. This is
; true of all sectors we read in some 
; emulators. Thus, the last sector of every
; code segment must be padded.
TIMES 512 db 0x00
4) sebihepp mentioned the EBDA which I was overwritting with my stack. When is the appropriate time to detect the Bios Memory Map to ensure I setup the stack in a valid location? Stage01 or Stage02?

Thank you all for sticking with me. I've already learned a great deal.
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: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by Combuster »

The real mode segment:offset address 9E00:1000 is actually 9F000 physical (i.e. 1000 + (9e00 * 16) and not (1000 + 9E00) * 16 as sebihepp calculated). The EBDA always ends at 9FFFF, and goes down to somewhere in 9XXXX, and on rare occasions even 8xxxx. You can detect available memory, or simpler, you can simply stay out of the 80000-9FFFF area when doing your first things. Regardless, 9F000 is high up in this area and thus a very big risk of crashing your computer. A typical location for a stack is 0000:7C00, right before the bootloader.

A floppy image is 2880 sectors exactly, and you get various amounts of complaining and/or bugs if it's anything less. You'll have to explicitly say it's a floppy or it'll get most likely treated as a harddisk with all additional problems thereof (unlike the other thread that just spawned).
"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 ]
lesniakbj
Posts: 13
Joined: Wed Sep 30, 2015 8:03 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by lesniakbj »

Combuster wrote:A typical location for a stack is 0000:7C00, right before the bootloader.
On this point, just so I am sure I am understanding correctly, you suggest to place the stack BEFORE the bootloader, due to the fact that the stack grows downward, and placing if AFTER could potentially lead to memory corruption if the stack overflows?
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by Ready4Dis »

It is recommended so that you don't put it anywhere that has anything important in it. Like the BDA, EBDA, VGA area, etc.

I agree, I would set SS to 0x0000 and SP to 0x7c00, as your unlikely to use a stack large enough to run into the IVT in a boot loader.

Segmentation is just the segment descriptor shifted left 4 times (multiply by 16) + offset... so 0x0000 shifted left = 0x00000 + 0x7c00 = 0x07c000.

I suggest you read this page for more information: http://wiki.osdev.org/Real_Mode

Once you realize that there is more than one way to access the same memory address in real mode, it makes more sense: Example, your boot loader is at 0x7c00....
To access the data, you can use DS:BX. DS = 0x0000 and BX = 0x7c00 will put you at 0x07c00 physical.
You *could* also use DS = 0x07c0 and BX = 0x000.... this ALSO puts you at 0x7c00 physical.
You could set DS = 0x0700 with BX = 0xc00... this would also put you at 0x7c00 physical.

This is very important to understand in real mode, knowing how to map a segment/register pair to a physical memory address. Otherwise you'll be doing things like putting your stack in the middle of the EBDA or possibly writing your stack to the screen buffer :).
lesniakbj
Posts: 13
Joined: Wed Sep 30, 2015 8:03 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by lesniakbj »

Ready4Dis wrote: Example, your boot loader is at 0x7c00....
To access the data, you can use DS:BX. DS = 0x0000 and BX = 0x7c00 will put you at 0x07c00 physical.
You *could* also use DS = 0x07c0 and BX = 0x000.... this ALSO puts you at 0x7c00 physical.
You could set DS = 0x0700 with BX = 0xc00... this would also put you at 0x7c00 physical.
So, let me try this out:

I am loaded, at say, 0x9EE0.
I can access this data in any of the following ways:

Code: Select all

DS = 0x0000 and BX = 0x9EE0, 0x0000:0x9EE0 = (0x0000 << 4) + 0x9EE0 = 0x9EE0
-- or --
DS = 0x09EE and BX = 0x0000, 0x09EE:0x0000 = (0x09EE << 4) + 0x0000 = 0x9EE0
-- or --
DS = 0x09CE and BX = 0x0200, 0x09CE:0x200 = (0x09CE << 4) + 0x0200 = 0x9EE0
am I understanding this concept correctly now?
User avatar
JAAman
Member
Member
Posts: 879
Joined: Wed Oct 27, 2004 11:00 pm
Location: WA

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by JAAman »

looks right, yes
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by Ready4Dis »

Yes sir, that looks correct.
And keep in mind, DS isn't the only segment register, CS is very important as well. For example, your boot loader resides at 0x7c00, this means the BIOS could hand you a DS or be in a CS that is not 0x0000.
The first thing my bootloader does is reset DS/CS to a known value. Imagine this:

Bios decides to load your bootsector to 0x7c0:0x000 (DS:BX). It then jumps to the code 0x7c0:0x0000 (CS:EIP). Imagine what happens if you try to ready data and *assume* that DS was set at 0x7c00 :).

For completeness, I have included my First stage boot loader for Fat12/Fat16: By storing two seperate readfunctions but tying them in through an indirect jump, it allows my second stage bootloader to simply call ReadSector with an LBA and size and have it work whether it's LBA or CHS. This means my second stage doesn't need to worry about disk type, it just has a function to load more sectors. I think it screwed up some of my formatting too, because all my comments where lined up in my editor ;).

Code: Select all

[bits	16]
[org	0x7c00]

jmp		short	Start							;0-2
nop

;Lets make our BPB
OEMName				db		'PhatOS  '
BytesPerSector		dw		512					;512 bytes is normal ;)
SectorsPerClustor	db		1					;Sector == cluster
ReservedSectors		dw		1					;Reserve the boot sector
FatCount			db		1					;1 copy of the fat (some things require this)
RootEntries			dw		224					;# of root entries
TotalSectors16		dw		0					;# of sectors on the disk
MediaType			db		0xF8				;Ignored for most things
FatSize16			dw		0					;Size of a single fat in sectors
SectorsPerTrack		dw		0					;Sectors Per Track
Heads				dw		0					;Head count
HiddenSectors		dd		0					;# of hidden sectors
TotalSectors32		dd		0					;# of sectors, 32-bit!

;Start of our Fat12/16 info
DriveNum			db		0					;Drive number (0x0x - floppy, 0x8x - hd
Reserved			db		0					;Reserved, set to 0
BootSig				db		0x29				;Set to 0x29 if next 3 values are present
VolumeID			dd		0					;Volume's ID, we don't care for it ;)
VolumeLabel			db		'PhatOS     '		;11 bytes
FileSystem			db		'FAT     '			;File system

Start:
	cld
	
	;Set up our stack at	0x7c00 (goes downwards!)
	xor		ax,		ax
	mov		ss,		ax
	mov		ds,		ax
	mov		es,		ax
	mov		ax,		0x07c00
	mov		sp,		ax

	mov		[BootDisk],			dl		;Store boot disk

;Determine which read function to use...
	test	dl,		0x80							;Hard Disk Bit?
	jnz		.KernelHD								;Lets use LBA if bit set
	mov		dword [ReadSectorFunc],	ReadSectorFD	;Otherwise it's CHS for a floppy disk
	jmp		DidntFindMBR							;Assume no MBR if this is a floppy device
.KernelHD:
	mov		dword [ReadSectorFunc],	ReadSectorHD	;Use HD function

;Read our MBR to see what partition we booted from
	mov		eax,	0								;MBR
	mov		bx,		0x1000							;Random free address
	mov		cx,		1								;One sector
	call	ReadSector								;Read it in
	xor		ecx,	ecx
	mov		eax,	0x1000+446						;Offset to the MBR table

;Lets determine boot partition + LBA
.CheckNext:
	cmp		byte [eax],	0							;Would be 0x80
	jne		.FoundBootPartition
	inc		cx
	cmp		cx,		4
	je		DidntFindMBR
	add		eax,	16
	jmp		.CheckNext

.FoundBootPartition:
	mov		[BootPartition],	cl				;Store boot partition here...
	mov		ebx,	[eax+8]						;Store into ebx
	mov		[PartitionLBA],	ebx					;Store this here...
DidntFindMBR:
;Lets load our second stage :)
	mov		eax,	1							;Start of 2nd stage
	mov		bx,		0x7c00+512					;Right after boot sector!
	mov		cx,		3							;3 sectors, so maximum boot sector = 4*512, or 2048 bytes!
	call	ReadSector
	jmp		SecondStage							;Go to our second stage loader


BootDisk:										;Disk we booted from
	db		0
BootPartition:									;Partition on disk we booted from (0xFF = no partitions!)
	db		0xFF
PartitionLBA:									;LBA of boot partition
	dd		0
SystemMemory:									;Memory in bytes,4gb max
	dd		0
ReadSectorFunc:
	dd		0									;Which read function to use

ResetDrive:
	pusha
.Retry:
	mov		ax,		0
	mov		dl,		[BootDisk]					;Drive [BootDisk]
	int		0x13
	jc		.Retry								;Didn't reset, lets try again
	popa
	ret
	
;Convert LBA -> CHS
;Inputs:
;	eax - sector
;	cx - length
;	bx - destination
;Outputs: Standard CHS format
GetCHS:
	xor		edx,	edx							;Zero out edx
	div		word [SectorsPerTrack]
	mov		cl,		dl							;Sector #
	inc		cl
	xor		edx,	edx
	div		word [Heads]
	mov		dh,		dl							;Mov dl into dh (dh=head)
	mov		ch,		al							;Mov cylinder into ch
	shl		ah,		6
	or		cl,		ah
	ret

;eax - sector to read
;es:bx - dest
ReadSingleSectorFD:
	a32 pusha
	call	GetCHS								;Grab our CHS
.Retry:
	call	ResetDrive							;Get drive ready..
	mov		dl,	[BootDisk]						;Grab our boot disk
	mov		ax,	0x0201							;Read function, one sector
	int		0x13
	jc		.Retry
	a32 popa
	ret

;Floppy disk code
;eax - sector to read
;es:bx - dest
;cx - length
ReadSectorFD:
	a32 pusha
.ReadNext:
	call	ReadSingleSectorFD
	add		bx,		512							;Next location..
	inc		eax									;Next LBA
    loop    .ReadNext							;Until cx = 0
.Done:
	a32 popa
	ret

;Hard disk code
;eax - sector to read
;es:bx - dest
;cx - length
ReadSectorHD:
	a32 pusha
	mov		dl,		[BootDisk]					;Use boot disk
	add		eax,	[PartitionLBA]				;Start of partition
	mov		[_lba],			eax					;Store linear block address
	mov		[_location],	bx					;Store location
	mov		[_segment],		es					;Store segment
	mov		[_length],		cx					;Length in sectors
	mov		ah,		0x42						;Read function
	mov		si,		_boot_lba					;Location of LBA block
	int		0x13
	a32 popa
	ret

ReadSector:
	jmp		[ReadSectorFunc]					;Jump to the correct function

DataStart		dd	0

_boot_lba:
	db	0x10									;Size of lba block structure :)
	db	0
_length:
	dw	0										;Filled in from above!
_location:
	dw	0x0000									;4k page bounary
_segment:
	dw	0x0000
_lba:
	dd	0										;Sector to read, 8 bytes total since LBA is 48-bits!
_lba_high:
	dd	0

times 496-($-$$)	db	0						;This information is setup from the installation!
ScreenMode			dw	0x143					;Graphics mode to set (800x600x32 default)
ImageLBA			dd	0						;Stores location/size of our OS Image (Low dword)
        			dd	0						;Stores location/size of our OS Image (High dword)
ImageSize			dd	0						;Size in sectors of our image
times 510-($-$$)    db	0
dw	0xaa55

%include "2ndStage.inc"
lesniakbj
Posts: 13
Joined: Wed Sep 30, 2015 8:03 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by lesniakbj »

Ready4Dis wrote:Yes sir, that looks correct.
And keep in mind, DS isn't the only segment register, CS is very important as well. For example, your boot loader resides at 0x7c00, this means the BIOS could hand you a DS or be in a CS that is not 0x0000.
The first thing my bootloader does is reset DS/CS to a known value. Imagine this:

Bios decides to load your bootsector to 0x7c0:0x000 (DS:BX). It then jumps to the code 0x7c0:0x0000 (CS:EIP). Imagine what happens if you try to ready data and *assume* that DS was set at 0x7c00 :).
Thank you for your explanation and examples, this has helped me tremendously, and I can feel myself making progress in this. Just one point of clarification.

In your bootloader, you:

Code: Select all

   xor      ax,      ax
   mov      ss,      ax
   mov      ds,      ax
   mov      es,      ax
   mov      ax,      0x07c00
   mov      sp,      ax
which I grasp... 90% of the way. We can't access segment registers directly, so we have to use AX to set them. Then we set the following:

Code: Select all

SS -> 0x0000 (the stack grows downward from 0x7C00, SP)
DS (Data Segment) -> 0x0000
ES (Extra? Segment) -> 0x0000
why is CS not set during this procedure? What makes the Code Segment (CS) register special in that we don't want to modify it here? Is there ever a time where it is safe to modify, or should that be left alone? Finally (reading from the wiki), what should be done with the general purpose segment registers (FS and GS)?

Note:
Answered part of my own question, from the wiki:

Code: Select all

Beside CS, segment registers may be loaded with a general register (mov ds, ax) or with the top-of-stack (pop ds).

CS is the only Segment Register that cannot be directly altered. The only time (I'm sure I'm missing one) CS is altered is when the code switches execution into another segment. The only commands that can do this are:
azblue
Member
Member
Posts: 147
Joined: Sat Feb 27, 2010 8:55 pm

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by azblue »

The only time you ever use FS/GS is when you explicitly use a segment override.

For example:

Code: Select all

mov ax, [bx]
Reads the offset bx from the DS register by default, but with a segment override:

Code: Select all

mov ax, [gs:bx]
It now reads from the GS register instead. If you're not overriding default segments with FS and GS, you don't need to ever set them.

A note on "setting" CS: The BIOS may have you set up at 0:7c00 OR 7c0:0; a far jump can set CS to whatever you want (0 would be best in my opinion) so then you know for sure exactly what it is.
User avatar
SpyderTL
Member
Member
Posts: 1074
Joined: Sun Sep 19, 2010 10:05 pm

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by SpyderTL »

lesniakbj wrote:why is CS not set during this procedure? What makes the Code Segment (CS) register special in that we don't want to modify it here? Is there ever a time where it is safe to modify, or should that be left alone?
You have to change CS and IP registers at the same time, which is what you use the JMP instruction for (far jump...).
lesniakbj wrote:what should be done with the general purpose segment registers (FS and GS)?
It's up to you. I set them to zero, and then never use them again, but you can use them if you'd like. I think they are deprecated/no longer used when you switch to 64-bit mode, if I remember correctly.
Project: OZone
Source: GitHub
Current Task: LIB/OBJ file support
"The more they overthink the plumbing, the easier it is to stop up the drain." - Montgomery Scott
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Re: Trouble with 2 Stage Bootloader - Calling 2nd Stage

Post by Ready4Dis »

why is CS not set during this procedure? What makes the Code Segment (CS) register special in that we don't want to modify it here? Is there ever a time where it is safe to modify, or should that be left alone? Finally (reading from the wiki), what should be done with the general purpose segment registers (FS and GS)?
As alluded to earlier, you must do a far jump settings CS and (E)IP at the same time. As long as your using short/relative jumps and you don't care what CS is. In all actuality, for completeness, I probably should do a far jump 0x0000:_start_address just in case.

Yes, FS and GS are segment overrides that you must explicitly use, so if you don't use them, you don't need to set them. If you do use them, then go ahead and set them.
Post Reply