Rip apart my boot sector(s)

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

Rip apart my boot sector(s)

Post by Kemp »

I've written a boot sector to boot from a FAT32 hard drive as shown here:

http://www.iualdii.net/boot.asm.html

Don't worry, you don't need to read the whole thing, it's towards the end of the first sector that matters. I have a load of sectors together like that as it allows me to simply copy over a single binary for the whole first load of sectors. The output I get from it is:

Starting boot...
Loading second stage...
Jump error

So basically, it all works fine up til it loads the second stage. It tests the value at the location it loaded it (well... 4 bytes further) and finds the wrong thing there. The code for the checking etc isn't meant to be completely coherent at the moment as once I get it working I'm gonna rewrite it a bit anyway, it's just for testing purposes right now.

My main problem is finding out whether it loads it incorrectly or if it tests the value incorrectly, so I'd be very grateful if someone could help me out there.

Also, what other miscellaneous issues are relatively obvious? It all appears to work up to that point (to be honest, there's not much done up to there), but as it was written from scratch with limited references I'm guessing there will be a lot of bad practices going on.

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

Re:Rip apart my boot sector(s)

Post by Brendan »

Hi,

Kemp wrote:My main problem is finding out whether it loads it incorrectly or if it tests the value incorrectly, so I'd be very grateful if someone could help me out there.
I think for the test:

Code: Select all

        MOV EBX, 2024h
        MOV EAX, [EBX]
        CMP EAX, 'Kemp'
        JNE JumpFail
That DS is still set to 7C0h rather than 0FFDBh, and everything else that gets data using DS is using the old copy at 07C00h (except the code that sets up the floppy geometry, which pushes then pops DS after).
Kemp wrote:Also, what other miscellaneous issues are relatively obvious?
I'm not sure what you've got planned for partitioning, but normally (for a hard drive boot sector) you'd leave 64 bytes at 01BEh for a partition table.

I'd also me tempted to use "JMP $" rather than "CLI; HLT" as the user can still press control+alt+delete if there's any error message.

For this part (just after relocation):

Code: Select all

        MOV AX, 0FFDBh                  ; Segment
        PUSH AX
        MOV AX, 0086h                   ; Offset where the code below is compared to code start
        PUSH AX
        RETF
It'd be easier to use a far jump. That way you'd be able to use a label instead of a hard coded value - "JMP 0FFDBh:somewhere".

I'm not sure which assembler you're using, but is there a way to get the assembler to work out the padding itself? IMHO adjusting the "36 DUP (0)" each time the code is changed would be a hassle - in NASM it'd be a "times $-$$+512-8 db 0"..

BTW the syntax highlighting looks good - something I should add to my converter :)..


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.
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

I think for the test:

Code: Select all

        MOV EBX, 2024h
        MOV EAX, [EBX]
        CMP EAX, 'Kemp'
        JNE JumpFail
That DS is still set to 7C0h rather than 0FFDBh, and everything else that gets data using DS is using the old copy at 07C00h (except the code that sets up the floppy geometry, which pushes then pops DS after).
Dammit, I guessing my segment registers would be a problem, I really do hate having to use segments, wish I could just leave them at 0 forever, damn 16 bit code. (post-preview thought: why is damn censored?
I'm not sure what you've got planned for partitioning, but normally (for a hard drive boot sector) you'd leave 64 bytes at 01BEh for a partition table.
Yeah, I'm not doing files right now, the rest of the drive is a barren wasteland as far as my code is concerned. Once I get the second stage loaded properly I'm going to start caring as that's when my kernel will get loaded.
I'd also me tempted to use "JMP $" rather than "CLI; HLT" as the user can still press control+alt+delete if there's any error message.
Hmmm... for me, turning off interrupts stopped ctrl+alt+del working, is that just a result of using virtual pc? Besides, I don't mind the user rebooting if there's an error, they'll just get the same one again :P

Edit: I see what you mean, I thought you were warning me that they could press it with my method ::)
For this part (just after relocation):

Code: Select all

        MOV AX, 0FFDBh                  ; Segment
        PUSH AX
        MOV AX, 0086h                   ; Offset where the code below is compared to code start
        PUSH AX
        RETF
It'd be easier to use a far jump. That way you'd be able to use a label instead of a hard coded value - "JMP 0FFDBh:somewhere".
Ok, I'll try that, I got this method from one of my books and I've seen a few other people use it since then so I assumed it was a nice way. I'll try your way out, though I will admit that (strangly) the simple jump is harder for me to see than the fake return. It would be getting the offset for 'somewhere' from the appropriate label next to the next line but using it as the offset into the new segment, correct?
I'm not sure which assembler you're using, but is there a way to get the assembler to work out the padding itself? IMHO adjusting the "36 DUP (0)" each time the code is changed would be a hassle - in NASM it'd be a "times $-$$+512-8 db 0"..
Yes there is a way (the way you just told me :P ) and yes it is a hassle. Thanks for that one.

Edit: Actually, I'm using TASM and it doesn't seem to like that. I changed it to
Padding DB $-$$+512-8 DUP 0
And it says $$ is an undefined symbol

BTW the syntax highlighting looks good - something I should add to my converter :)..
Thanks, it was designed as more of a looking good/easier to follow thing than an actual documenting tool (though no doubt I'll work on it when I get bored).

Thanks for all the help, I'll play around with your suggestions and see if I can get it going.

Edit: Just noticed half my comments (in the source) refer to old versions of code, sorry about that
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

Ok, I seem to have got somewhere (in the loosest sense of the word).

http://www.iualdii.net/boot.asm.html

This now displays the first two messages but not the third anymore, which tends to mean it's trying to find the message string in the wrong segment, but it's a change at least. I think I've made it so the segment registers always point to where the code currently is and I've made it so that the comparison is actually comparing the right piece of memory (or I think I have). At a loss currently, but hopefully my continued playing well work something out.

Quick extra question (in addition to any raised in my second post :P ), why does the line

JMP 000:2020h

give me an error "Near jump or call to different CS"? The compiler can obviously see it's meant to be a far jump in order to give me the error so why not assemble it as one as it did towards the beginning of my code?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Rip apart my boot sector(s)

Post by Brendan »

Hi,

Ok, I spent a while looking through it and realized something :)....

At the start of your code you relocate everything to 0FFDBh:0000h, which is 000FFDB0h, which is near the end of the BIOS - in ROM.

Now I can't figure out why it gets past the relocation - the "JMP 0FFDBh:NewLoc" should jump into the middle of the BIOS's code while ESP = 000FFC00h (also in ROM). Somehow you must be writing to "read only memory" for it to survive long enough to display any messages..

I'm wondering if it's running from the CPUs cache (even though the area from 0x000F0000 to 0x000FFFFF shouldn't be cachable).

Would you mind putting a "WBINVD" instruction in there just before the "JMP 0FFDBh:NewLoc"? That would flush the CPU's cache, so the CPU jumps to the BIOS's code and crashes (it'd help to convince me that I'm not halucinating ::)).

Anyway, the last jump ("JMP 0000:2020h"), and the data segment registers used in your second stage aren't right either - they don't match your "ORG 0h", so "MOV SI, OFFSET msgStart2nd" will get the wrong address.

The second stage starts 4096 bytes into the file, so the assembler will think the label "msgStart2nd" is (roughly) equal to 0102Ch. So DS:0102Ch = 0102Ch but it's actually loaded at 0202Ch. To fix it, you could use 0100h for your segment registers (because "(0100h << 4) + 0102C = 0202Ch"), or you could load the sector to 0:01000h (or 0100h:0).
Kemp wrote:Quick extra question (in addition to any raised in my second post :P ), why does the line

JMP 000:2020h

give me an error "Near jump or call to different CS"?
Sorry - I've never used TASM or MASM. Other assemblers (NASM, YASM, FASM, GAS/AS) wouldn't have a problem, but these assemblers don't try to keep track of which segment register is being used for what...


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.
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

*Goes back and studies the list of what's used by what in memory*

Hmmm.... I chose that value for the new location as it was about the highest I could fit into a 2-byte segment register. My reference boot sector I was referring to moved stuff up to 9000:whatever, and I assumed that going higher up would be ok, which was of course a stupid assumption.

On the subject of the WBINVD, I get an error:
"Illegal instruction for currently selected processor(s)"
when I try to assemble it.
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

Also, a quick question. Everyone I've seen loads their GDT/IDT/whatever else at 0000:0000 (ie, at the first location possible in memory), is there any benefit to loading it above the 1Mb mark (after enabling A20) and thus preserving the contents of the stuff that's already in memory?


Progress, the code makes more sense to me now :) If you check the usual link to the source I think I've solved 'incorrect segment' and 'taking over memory' related problems, but no doubt I've messed it up a bit more ::) I might actually manage to squeeze it all into one sector (I'm not doing much at all and yet still rapidly running out of space). 40 bytes to go...

I'm going to love loading the GDT, switching into protected mode and finally flipping the segment registers the finger ;D

Edit:
New link to source:
http://kempos.iualdii.net/boot.asm.html
http://kempos.iualdii.net/boot2nd.asm.html

Now I'm getting a different error message:
Unable to load second stage
Which means that my hard drive code is screwed up now :-\

The kernel is going to be simple compared to this ;)
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Rip apart my boot sector(s)

Post by Brendan »

Hi,

WBINVD was introduced with 80486, while you're using ".386" - I don't think there's a way to flush the cache on an 80386 (I don't think 80386 had internal caches either). I'd assume using ".486" would tell the assembler to allow the WBINVD instruction.
Kemp wrote:Everyone I've seen loads their GDT/IDT/whatever else at 0000:0000 (ie, at the first location possible in memory), is there any benefit to loading it above the 1Mb mark (after enabling A20) and thus preserving the contents of the stuff that's already in memory?
This depends on your OS and what it will use later on. If you intend to use the BIOS later (e.g. virtual 8086 or real mode APM shutdown) you'd probably want to keep the first 2KB (or 4 KB) and the EBDA (if any).
Kemp wrote:Progress, the code makes more sense to me now :) If you check the usual link to the source I think I've solved 'incorrect segment' and 'taking over memory' related problems, but no doubt I've messed it up a bit more ::) I might actually manage to squeeze it all into one sector (I'm not doing much at all and yet still rapidly running out of space). 40 bytes to go...
You could squeeze a lot more space out of it - for example, if you know the second stage is going to be at sector 8 and you know that there will never be a disk with less than 8 sectors per track, then you could just load cylinder 0, head 0, sector 8 without calculating. As you're only loading one sector you can probably leave the part that sets up the BIOSs floppy timing until later.

That would leave some preliminary setup, a single "read from disk" BIOS call, your consistancy check and the code to display a couple strings - the rest could be shifted to the second stage.

Of course it might be a good idea to decide what parts of memory you intend to use for everything and how your entire OS boots. For example, once you switch to protected mode you won't be able to load sectors using the BIOS, so somehow everything you're going to need to load data in protected mode will have to be loaded into memory before switching to 32 bit. This could be your entire kernel, many device drivers (floppy, ATA/IDE, SCSI, ethernet) and support for different file systems and network protocols. This is why I load a "boot image" that contains many files (including a second stage) rather than just a second stage.
Kemp wrote:I'm going to love loading the GDT, switching into protected mode and finally flipping the segment registers the finger ;D
And then trying to sort out paging and multi-threading! :)...


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.
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

WBINVD was introduced with 80486, while you're using ".386" - I don't think there's a way to flush the cache on an 80386 (I don't think 80386 had internal caches either). I'd assume using ".486" would tell the assembler to allow the WBINVD instruction.
Yeah, sorry, forgot to mention I changed that while I tried it, same effect.
You could squeeze a lot more space out of it - for example, if you know the second stage is going to be at sector 8 and you know that there will never be a disk with less than 8 sectors per track, then you could just load cylinder 0, head 0, sector 8 without calculating. As you're only loading one sector you can probably leave the part that sets up the BIOSs floppy timing until later.
I might as well do that then, save me worrying that I screwed up the absolute sector -> CHS conversion as well. And as for the floppy timing code, I'm gonna leave that in for now, I'm shifting the table to where I want it so I might as well stick the values in while I'm at it.
For example, once you switch to protected mode you won't be able to load sectors using the BIOS, so somehow everything you're going to need to load data in protected mode will have to be loaded into memory before switching to 32 bit. This could be your entire kernel, many device drivers (floppy, ATA/IDE, SCSI, ethernet) and support for different file systems and network protocols. This is why I load a "boot image" that contains many files (including a second stage) rather than just a second stage.
Yeah, I'm still thinking about how to load the file system driver when I need to be able to access the disk to get it in the first place. I have various ideas, gonna work them out on paper (you should see my pages of frantic scribbling for various things :) ).
I'm going to love loading the GDT, switching into protected mode and finally flipping the segment registers the finger

And then trying to sort out paging and multi-threading! ...
*super-hero voice* Ha, I am invulnerable to threats of paging and multi-threading! ;D
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Rip apart my boot sector(s)

Post by Brendan »

Hi,
Kemp wrote:Yeah, I'm still thinking about how to load the file system driver when I need to be able to access the disk to get it in the first place. I have various ideas, gonna work them out on paper (you should see my pages of frantic scribbling for various things :) ).
It's probably be best to work this out before you get too far. For example, I'm using a 4 stage boot with a 38.2 KB second stage, while others are using a single stage boot that goes directly into the kernel (and there's plenty of variations inbetween) - the code you're writing now may be unsuitable for your OS...


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.
Kemp

Re:Rip apart my boot sector(s)

Post by Kemp »

Well, I have a basic idea of my plan and as far as I can tell, my two-stage loader will work with it fine. Of course in the worst case I may end up scrapping the loader and starting over, but if I get these last few bugs worked out of this design at least I'll have working reference material to use.
Post Reply