Second-Stage Bootloader Bug
Re: Second-Stage Bootloader Bug
In case you were using my assembler, there is no ORG. My executable file format has a list of absolute addresses that must be patched. My Loader takes care of that, normally, except in my OSMain module. It must patch itself. The start-up assembly code and my boot-loaders must deal with almost no support for data segment variables. It's basically like an ORG 0. What they do is look at the code segment and figure-out where they were loaded. My boot-loaders actually copy themselves to just under 0x80000 in real mode and load the next stage into 0x7C00.
- Love4Boobies
- Member
- Posts: 2111
- Joined: Fri Mar 07, 2008 5:36 pm
- Location: Bucharest, Romania
Re: Second-Stage Bootloader Bug
If you show us your code, we can tell you for sure where the problem lies.Rolice wrote:Thank you for you explanation. Now I verified the source of the problem. Yes, in the second boot loader I have some short jumps, that is possibly why the code did not brake.
"Computers in the future may weigh no more than 1.5 tons.", Popular Mechanics (1949)
[ Project UDI ]
[ Project UDI ]
Re: Second-Stage Bootloader Bug
Ok I will. The second stage was problematic. I have placed a ;;; NOTE on the keypoints - where I inverted the values from 1000h -> 0h...
Here goes the first stage...
Now we have the second stage - the problem was lying here:
The other files are separated as includes for the common routines.
This is the prepare.inc.asm:
... and print.inc.asm:
This way the loader is quite working.
I have prepared kernel entry linked with ld, containing the linked preparational assembly and C void kmain function, which I have to load now... step forward, which of course will include some reading about file systems, before acting.
Here goes the first stage...
Code: Select all
bits 16
org 07c00h
loader:
jmp short start ; go to start
nop
; -------------------------------------------- CODE SEGMENT --------------------------------------------
%include "include/print.inc.asm"
ResetFloppy:
mov ah, 0h ; Reset the floppy drive
mov dl, 0h ; Drive number 0 for floppy drive
int 13h ; Call BIOS to do this thing for us
jc ErrorHandler ; Problem with resetting - Carry Flag (CF) - try again
mov ax, 1000h ; Setting the address where to store the read data - 0x1000:0 - THIS IS LIKE IT HAS TO BE
mov es, ax
xor bx, bx
LoadSecondLoader:
mov ah, 02h ; BIOS function no. 2 - read from floppy
mov al, 1h ; Read 1 sector
mov ch, 0h ; We are reading the next sector that this one, so it is on the same track (1) IMPORTANT THE TRACK IS 0!!! NOT 1
mov cl, 2h ; The second (2) sector to be read from
mov dh, 0h ; Head number 0
mov dl, 0h ; Drive number - 0 - floppy drive
int 13h ; Call the BIOS to do the reading for us
jc ErrorHandler
jmp 1000h:0000h
ErrorHandler:
mov bl, 07h
mov si, error
call PrintString
cli
hlt
start:
cli
mov ax,cs ; Setup segment registers
mov ds, ax ; Make DS correct
mov es, ax ; Make ES correct
mov ss, ax ; Make SS correct
mov bp, 7c00h
mov sp, 7c00h ; Setup a stack
sti
mov bl, 0ch
mov si, title
call PrintString
mov bl, 02h
mov si, sep
call PrintString
mov bl, 04h
mov si, info
call PrintString
call ResetFloppy
cli
hlt
title db "Some title", 0dh, 0ah, 0
sep db "============================================", 0dh, 0ah, 0
info db "Bootloader entered stage 1, loading stage 2.", 0dh, 0ah, 0
error db "An unrecoverable error occurred. System will now halt.", 0dh, 0ah, 0
times 510-($-$$) db 0 ; Rest of the bootloader is filled with zeros
dw 0xAA55 ; Last bootloader signature 2 bytes
Code: Select all
org 0h ;;; NOTE: Here instead of 1000h I use 0h now
bits 16
loader:
jmp short start ; go to start
%include "include/print.inc.asm"
%include "include/prepare.inc.asm"
start:
;push cs
;pop ds
cli
xor ax, ax
mov ax, 1000h ;;; NOTE: Here instead of moving cs into ax (mov ax, cs) or just clearing it with the xor above I put 1000h which fixed the problem
mov ds, ax ; Make DS correct
mov es, ax ; Make ES correct
mov ss, ax ; Make SS correct
;mov bp, 1000h
;mov sp, 1200h ; Setup a stack
sti
cld
;mov ah, 0eh
; mov al, 'H'
; xor bx, bx
; int 10h
mov si, info
call PrintString
jmp $ ; Endless loop in order to ensure a control point
cli
hlt
info db "Bootloader entered stage 2, setting up general environment and loading kernel.", 0dh, 0ah, 0
times 512-($-$$) db 0
This is the prepare.inc.asm:
Code: Select all
bits 16
EnableA20:
mov al, 02h ; set bit 2 (enable a20)
out 92h, al ; writing a byte to port 0x92
ret
Code: Select all
bits 16
PrintCharacter: ; Print Character on the screen
mov ah, 0eh ; Tell the BIOS we will print char
mov bh, 0h ; Page 0
mov bl, 07h ; Lightgray color
int 10h
ret
PrintString:
lodsb ; Grab a byte from SI
or al, al ; Logical or of AL itself
jz PrintString_Return ; End of the string - RETURN
mov ah, 0eh ; Tell the BIOS we will print char
int 10h ; Print the character
jmp PrintString ; Next symbol
PrintString_Return:
ret
HexToStr:
xor bx, bx ; Setting bx to 0
mov bl, al ; Move the decimal value in bl
mov al, [HEXIDECIMAL_CHARACTERS + bx] ; Getting the corresponding character of hex value in al, back
mov bl, 17h ; Setting color style for output
call PrintCharacter ; Print the character representing the hex value
ret
HEXIDECIMAL_CHARACTERS db "01234567890ABCDEF"
I have prepared kernel entry linked with ld, containing the linked preparational assembly and C void kmain function, which I have to load now... step forward, which of course will include some reading about file systems, before acting.
Re: Second-Stage Bootloader Bug
well, the first thing that comes into my mind, is you are either constantly repeating the same typo, or you don't understand segmentation
the segment is not the same as the offset, it is shifted by 4bits (1 hex digit) before adding it to the offset to create the address
1000:0 is not the same as 0:1000 -- those are 2 different addresses
if you set org=0, and DS = 1000, you are using absolute address 1_0000 (65536 decimal)
if you set org=1000 and DS = 0, you are using absolute address 0_1000 (4096 decimal)
so basically, you are loading the code to address 1_0000 (ES=1000, BX=0), and then trying to use it at address 0_1000 (DS=0, org=1000), so obviously this isn't going to work, since those are 2 different addresses
there really isn't any reason to reset your stack again, unless you want to be able to access data from your new section using SS... but I'm not sure why you would, but it is causing a bug in your code:
you disable interrupts (CLI) before adjusting SS but then re-enable them without setting SP -- this is never a good idea (usually you should always pair them together, and thus never need to disable interrupts since they are automatically disabled for 1 instruction following any modification of SS) thus you can fix this one of 3 ways: either un-comment the line setting SP, remove the cli/sti and put a mov SP line immediately after the mov SS line, or eliminate the SS/SP setting altogether (leave it as you already set it up in the first stage)
the segment is not the same as the offset, it is shifted by 4bits (1 hex digit) before adding it to the offset to create the address
1000:0 is not the same as 0:1000 -- those are 2 different addresses
if you set org=0, and DS = 1000, you are using absolute address 1_0000 (65536 decimal)
if you set org=1000 and DS = 0, you are using absolute address 0_1000 (4096 decimal)
so basically, you are loading the code to address 1_0000 (ES=1000, BX=0), and then trying to use it at address 0_1000 (DS=0, org=1000), so obviously this isn't going to work, since those are 2 different addresses
this should be unnecessary, since CS actually contains 1000 already -- but your org definitely needs to be 0 (same as your jump offset from your stage1)Code: Select all
mov ax, 1000h ;;; NOTE: Here instead of moving cs into ax (mov ax, cs) or just clearing it with the xor above I put 1000h which fixed the problem
there really isn't any reason to reset your stack again, unless you want to be able to access data from your new section using SS... but I'm not sure why you would, but it is causing a bug in your code:
you disable interrupts (CLI) before adjusting SS but then re-enable them without setting SP -- this is never a good idea (usually you should always pair them together, and thus never need to disable interrupts since they are automatically disabled for 1 instruction following any modification of SS) thus you can fix this one of 3 ways: either un-comment the line setting SP, remove the cli/sti and put a mov SP line immediately after the mov SS line, or eliminate the SS/SP setting altogether (leave it as you already set it up in the first stage)
Re: Second-Stage Bootloader Bug
Thanks, yes the conceptions is known to me, but to be honest I got confused. However, I have uptated the loader, since then, and now I load it on 0000:8000h, where the ORG of the second part is 8000h.
Yes the bootloader gave a bunch of bugs, which ate me yesterday, but I find the solution after all. The code for second stage, was also incomplete, due to the time of posting. However I solve this and the next furious problem with entering protected mode (processor-flag+GDT), which one was because of corrupted GDT - the access part of the Data Descriptor (executable bit) was saying it was another code selector.
I am still confused with these segment pointers, cs, ds, es, ss. However I know their purpose, but I got lost within the meaning, and thanks to this:
I forgot for the 4 bit offset, which of couse lead me to wrong usage of the segmentation, and the wrong questions, which ate my day.
For the bp, sp and ss, I am still suspicious. I know that by defining bp and sp I would define the bottom of the stack and the upper limit, ie the stack bounds. But, how ss is related to the sp? As I understand the principle:
First I have to reserve a space in memory for the stack, so SS would point the segment of the area where is the stack (currently it would be on the same segment 0h for 0000:8000h). Then for base (BP) I point the address of that space and SP will contain the upper corner i.e. the stack size (or it was the base + stacksize?).
However I did not set up a stack in stage 2. Would it be a problem, as far as I know it should be, however in some of the includes (including a lot of new ones) I have push/pop, but it is working normally both 16 and 32bit?
For the 32bit I have moved the ESP to 90000h, which is far away from the current location... while SS is 0x10 (the address of the data segment).
I have listened to your advice and I have commented the lines for the setup of the segment registers, all of them, because now I use the 0000:8000h and they sould be all the same, right?
Thank you for your post and explanation
Off topic:
However, I am glad for the excercises I have played. They say, the experience is comming with the bad decisions, whereas good decisions come with experience...
So for other people entered in such black hole area, I would suggest not to quit, but to keep thinking, inspecting and analyzing (keep trying), its not unresolvable.
Yes the bootloader gave a bunch of bugs, which ate me yesterday, but I find the solution after all. The code for second stage, was also incomplete, due to the time of posting. However I solve this and the next furious problem with entering protected mode (processor-flag+GDT), which one was because of corrupted GDT - the access part of the Data Descriptor (executable bit) was saying it was another code selector.
I am still confused with these segment pointers, cs, ds, es, ss. However I know their purpose, but I got lost within the meaning, and thanks to this:
I think I got the link in between (segment pointer after all), with the offset, it got sense.if you set org=0, and DS = 1000, you are using absolute address 1_0000 (65536 decimal)
if you set org=1000 and DS = 0, you are using absolute address 0_1000 (4096 decimal)
I forgot for the 4 bit offset, which of couse lead me to wrong usage of the segmentation, and the wrong questions, which ate my day.
For the bp, sp and ss, I am still suspicious. I know that by defining bp and sp I would define the bottom of the stack and the upper limit, ie the stack bounds. But, how ss is related to the sp? As I understand the principle:
First I have to reserve a space in memory for the stack, so SS would point the segment of the area where is the stack (currently it would be on the same segment 0h for 0000:8000h). Then for base (BP) I point the address of that space and SP will contain the upper corner i.e. the stack size (or it was the base + stacksize?).
However I did not set up a stack in stage 2. Would it be a problem, as far as I know it should be, however in some of the includes (including a lot of new ones) I have push/pop, but it is working normally both 16 and 32bit?
For the 32bit I have moved the ESP to 90000h, which is far away from the current location... while SS is 0x10 (the address of the data segment).
I have listened to your advice and I have commented the lines for the setup of the segment registers, all of them, because now I use the 0000:8000h and they sould be all the same, right?
Thank you for your post and explanation
Off topic:
However, I am glad for the excercises I have played. They say, the experience is comming with the bad decisions, whereas good decisions come with experience...
So for other people entered in such black hole area, I would suggest not to quit, but to keep thinking, inspecting and analyzing (keep trying), its not unresolvable.
Re: Second-Stage Bootloader Bug
Hi,
BP is just another register, like AX, BX, etc. It doesn't have a "hard wired" role like IP or SP or FLAGS.
However, minor differences between various registers make different registers a little better for certain things (e.g. CX tends to be a little better for loop counts, SI and DI tend to be a little better for addresses, AX tends to be a little better for multiplication, etc). For BP the only minor difference is that memory accesses that use BP will use SS as the default segment (e.g. "mov ax,[bp]" really means "mov ax,[ss:bp]" and not "mov ax,[ds:bp]"), which makes it a little better to use for "addresses within the stack" (it saves a segment override prefix). For this reason compilers tend to use EBP/BP as a frame pointer.
Cheers,
Brendan
SS defines where the stack segment starts (and the stack segment's limit). SP determines where the current "top of stack" is within the stack segment.Rolice wrote:First I have to reserve a space in memory for the stack, so SS would point the segment of the area where is the stack (currently it would be on the same segment 0h for 0000:8000h). Then for base (BP) I point the address of that space and SP will contain the upper corner i.e. the stack size (or it was the base + stacksize?).
BP is just another register, like AX, BX, etc. It doesn't have a "hard wired" role like IP or SP or FLAGS.
However, minor differences between various registers make different registers a little better for certain things (e.g. CX tends to be a little better for loop counts, SI and DI tend to be a little better for addresses, AX tends to be a little better for multiplication, etc). For BP the only minor difference is that memory accesses that use BP will use SS as the default segment (e.g. "mov ax,[bp]" really means "mov ax,[ss:bp]" and not "mov ax,[ds:bp]"), which makes it a little better to use for "addresses within the stack" (it saves a segment override prefix). For this reason compilers tend to use EBP/BP as a frame pointer.
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.
Re: Second-Stage Bootloader Bug
Thanks, now this makes some more sense. Yes I have some knowledge of principal register beneficial usage for AX (accumolator - math), CX (cx counters, usage with REG), SI and DI (strings).
I have read some theory long ago and I am primary working in 32/64-bit mode as developer of standard userland apps, some debugging and disassembling, but on low level I had written one very simple bootloader.
Now with this deep (far) jump in low-level I am filling a missing holes of information, for myself, thats why some parts of the code may seem not logical or broken.
Another question, I am already in 32-bit environment, as I mentioned with the previous post and I have GDT, as far as I understand in 64-bit, the GDT is set up, but seems to be unsed, because of the returning to flat model for accessing memory, right? ... which means there are no segment:selectors anymore, but just a direct addresses like 0x0000000000000000?
Thanks
I have read some theory long ago and I am primary working in 32/64-bit mode as developer of standard userland apps, some debugging and disassembling, but on low level I had written one very simple bootloader.
Now with this deep (far) jump in low-level I am filling a missing holes of information, for myself, thats why some parts of the code may seem not logical or broken.
Another question, I am already in 32-bit environment, as I mentioned with the previous post and I have GDT, as far as I understand in 64-bit, the GDT is set up, but seems to be unsed, because of the returning to flat model for accessing memory, right? ... which means there are no segment:selectors anymore, but just a direct addresses like 0x0000000000000000?
Thanks