Page 2 of 6

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 6:48 am
by stevewoods1986
stevewoods1986 wrote:
onlyonemac wrote:
stevewoods1986 wrote:So should I do?

Code: Select all

mov ah, 2
mov al, 1
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0
mov bx, 0x7c00
mov es, bx
mov bx, 0x00
int 13h

mov ah, 2
mov al, 1
mov ch, 0
mov cl, 3
mov dh, 0
mov dl, 0
mov bx, 0x7c00
mov es, bx
mov bx, 0x00
int 13h

jmp 0x7c00:0x00
What do I do?
I haven't tried your code but at a glance it seems to me that it will write the second sector to the same place that it wrote the first sector. Your second

Code: Select all

mov bx, 0x7c00
should probably be

Code: Select all

mov bx, 0x7e00
otherwise you'll overwrite (in memory) the first sector with the second sector.

Also why are you writing to 0x7c00:0x0000, IIRC that will overwrite your bootsector?
It works. That's why. No problems. I don't think I need 0x7e00 unless I have stack problems... wait. you mean. oh
Nice job. My message won't print. Three lines and a S. This is why?

I'll stick with 0x7c00.

What do I do to make my kernel load even if is over 512 bytes? I have tried changing al to this and that. I have tried doing two int 13hs and changing the data buffer.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 7:09 am
by Octacone
Just something random that could possibly be somewhat relevant:
Are you sure your second sector is located exactly where you think it is. Does it actually physically exist, the code you want to run?
Are you sure you are not switching zeros and ones, this is reading related catch I think, there is a place (register when calling int 13) that doesn't start from 0 or something like that.
Are you sure you are not loading one sector on top of another?
Perhaps segment registers could be set incorrectly, resulting into different memory locations than wanted.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 7:12 am
by neon
Hello,
What? So it's not 0x7c00? I don't need to jump to DS:BX?

I don't know where the stack is... Is it at 0x7c00?
Do I have to do mov sp, 0x7c00?
You load and then call 0x7c00:0. This is 0x7c000 linear not 0x7c00 (where your boot code is) - hence why it does not overwrite your boot record in memory. You understand that, if you have a flat binary program, you can't just load parts of it at different locations. It should be loaded "as-is" into consecutive memory space. Since the first 512 bytes are already loaded to 0x7c0:0, you should load the next 512 bytes to 0x7e0:0 and the next to 0x800:0 and so on. That is, your program would then be at 0x7c00 - 0x81ff. Does that clarify?
I don't know where the stack is... Is it at 0x7c00?
Do you ever set SS:SP? If not, do it at the start so its at a good known location. Remember the stack grows down; one possible suggestions is to set it to 0x7c0:0.
I'll stick with 0x7c00
Except you aren't loading anything to 0x7c00.

You don't have two separate programs. You have one program in one source file that is at consecutive sectors. So you should load them in consecutive memory.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 8:05 am
by onlyonemac
neon wrote:- What happens if you load it to 0x7e0:0 and 0x800:0? You should not need a jump since the sectors would be loaded in consecutive memory.
You would probably still need a jump. Your first stage bootloader probably doesn't fill the entire boot sector, and there's the boot signature at the end of the boot sector that you probably don't want to execute.
Schol-R-LEA wrote:it's not not writing to 0000:7c00 (computed absolute address 7c00 hex, or 31,774 decimal), where the BIOS loads the boot sector) but to 7c00:0000 (computed absolute address 7c000, or 507,904 decimal)
This is probably a mistake. I missed this at first, as it's been a while since I've worked with real-mode segmentation, but BX should be 0x07C0, not 0x7C00.
Schol-R-LEA wrote:You're point about it overwriting the first loaded sector is correct. However, in this instance, the OP would probably use something closer to this, assuming that ES and BX are not clobbered by the BIOS:

Code: Select all

add bx, 0x10    ; advance the load location by 512 bytes
int 0x13
Yes, you can do this as well. In my own bootloader I change the segment register, because it scales beyond 64KB and avoids complications with offsets wrapping around, and it simplifies the code because you're only setting/changing one value rather than two.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 8:44 am
by Octocontrabass
neon wrote:Remember the stack grows down; one possible suggestions is to set it to 0x7c0:0.
The stack also wraps around in its 64kB segment, so the top of the stack will be at linear address 0x17C00 instead of 0x7C00 where you want it.

Personally, I'd recommend avoiding segmentation entirely to begin with. Early in your boot sector, do a far jump to 0:some_label, set DS, ES, and SS to zero, and set SP to something reasonable like 0x7C00. (Be careful to ensure no interrupts occur while you're setting up the stack.) You can start messing with segmentation for real once you're able to make things work without it.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 1:53 pm
by stevewoods1986
This is making me confused. How do I load my kernel if has more sectors with INT 13h? Do I repeat it? What do I do? I've tried experimenting.

This is just getting more confusing.

By the way, should I read the whitepaper done by Microsoft (from the FAT page)? Where is a good resource about the filesystem?

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 2:50 pm
by iansjack
What's confusing?

There are two possibilities:

1. You put the whole kernel in a fixed place, e.g. sectors 1, 2, 3, etc. You read those sectors into memory. That way you don't need a filesystem.

2. More realistically, you parse the filesystem, find the file containing your kernel, determine which sectors make up that file, and load them.

This is very basic stuff.

As for a reference to FAT - you have already identified it.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 2:53 pm
by neon
Hello,

For now, set all segment registers to 0 (as recommended above.) Also put "org 0x7c00" at the top of the file if not already done so. To set CS, do a jmp 0:.fix_cs instruction. This way you don't have to worry about segmentation too much.

So...You have an assembly file that %include's other files. When you assemble it, you should only get one binary file. So all you need to do is load that file into memory and execute it, right? The BIOS already loaded the first 512 bytes for you. So you just need to load the rest.

Copy your binary file to the disk image. It should span multiple sectors. Sector 1 is already loaded by the BIOS to 0x7c00. So you just need to load sector 2 to 0x7e00 and sector 3 to 0x8000. Does that make sense?

For now, just load sector 2 to 0x7e00 and see if it works. That is, make your program only 2 sectors in size and try it. After you get that working, try to make a generic "ReadSectors" function that uses int 13h in a loop to read multiple sectors.
By the way, should I read the whitepaper done by Microsoft (from the FAT page)? Where is a good resource about the filesystem?
Sure, if you want to implement the FAT file system. But you'll need to be able to read sectors first. ;)

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 2:54 pm
by Schol-R-LEA
stevewoods1986 wrote:This is making me confused.
Trust me, that happens to everyone when they are learning this stuff. It's all a great big mess, but sadly, it's a great big mess that can't be avoided at some point in any x86 PC boot loader project. It is a big part of why we usually recommend using an existing boot loader instead of rolling your own, though doing one yourself at least it does give a lot of interesting experience - albeit experience which becomes almost entirely irrelevant the moment the OS switches into p-mode.

It might be worth while reviewing Real Mode memory addressing in detail, since I suspect that's part of the problem. I don't know how much of this you already know or not, and most of the problem parts aren't specific to reading from disk, or even to the PC in general, but are a direct consequence of the design of the x86 processor and how they manage memory in real mode.

Basically, the 8086 (the original Intel CPU of the x86 series from 1978) and its 8/16 bit version, the 8088 (which is what was used in the original IBM PC in 1981) used a system called 'segmented memory', in which two 16-bit values - a segment address, and an offset from that segment address - are combined to form a 20-bit physical memory address, giving a 1 MiB address space (and some change due to the way the segments and offsets overlap).

OK, so I am pretty sure if you've done any x86 assembly at all, you know that much. What you probably don't know is the historical reasons for it, and the actual details of how it works - if you are like most of us starting out, you have some idea of what it means but don't understand it as well as you think you do.

You will note that normally two 16-bit values, if used together, would form a 32-bit value. This is what most people would have expected the designers to do, and is how the earlier 8-bit architectures like the Intel 8080, the Zilog Z80 (which was mostly a clone of the 8080), the Motorola 6800, and the MOStek 6502 got a 16-bit (64KiB) address space. A number of other 16-bit systems such did just that, though for the most part chip designers skipped 16-bit designs and went straight to 32-bit ones such as the Motorola 68000. Intel, however, went with a rather more complicated affair that reduced the address space significantly.

Now, you might well wonder why in Eris' name they would do something so daft, but the designers had Good Reasons At The Time to do this.

First off - and this is a key to understanding a lot of what came later - Intel wasn't intending the 8086 to be a home computer system, and they didn't expect it to be used for very long. Intel had two different markets in mind - embedded microcontrollers, which they had more or less invented with the 4004 ten years earlier, and the emerging professional workstations, which they intended to get a jump on with a brand-new and ultra-powerful 32-bit design, the i432. They were also planning a few new series of microcontrollers, but in 1978, they had what was seen as an immediate need to extend the life of the 8080, especially given they way Zilog had caught them off-guard with the Z80. They had thrown together the 8080A in 1977, but they needed a good stopgap to buy them three or four years, and they knew other manufacturers were planning 16 and even 32 bit systems, so they wanted to beat those to the market.

Note that they were deliberately ignoring the 'microcomputer' market at this time, because they figured it was a dead end - what sort of weirdo wants a computer in their own home, anyway? - and that if they left that market to Zilog et al., they would be too focused on those absurd toys to focus on the vastly more important microcontrollers (they were right about that part - even ignoring the maker market, which they mostly have, the market for microcontrollers for things like toasters and traffic signs is about twenty times what the PC and mobile markets are combined, and that remains Intel's bread and butter) and maybe not notice what was going on with workstations. After all, if you were going to take on up and coming giants like the Xerox Alto and the Lisp Machine, you needed to be on your game, right? :oops: :lol: :cry:

(I so wish that LispMs hadn't come crashing down... but that was all on the companies that made them, not the technology they used - sure, let's keep building hand built wire-wrapped systems, who needs microprocessors, anyway!) and besides it is thirty-five years too late to do anything about it.)

This sort of leads into the next part, which is that they were assuming that most of the programming would be in assembly, by professional programmers who had already used the 8080. Thus, they wanted to make porting assembly code from the 8080 to the 8086 as easy as possible, which is no mean feat. They chose segmentation primarily to make it so that programs written for a 16-bit address space could be ported without having to change how the addressing was handled - as long as it used 'short' (PC relative 8-bit) and 'near' (16-bit segment offset) addresses within a single segment, the program would need a lot fewer alterations.

This was to become known as the 'tiny' memory model, and when MS-DOS came along, it had a separate executable file format, .COM, just for programs like that.

Finally, using a full 32-bit address space would have required more pins on the chip's Dual-Inline Package (DIP), and additional address lines for all the memory components. While they could have done what Motorola did around the same time with the 68K - namely, just not give it a full set of address pins, figuring they could work in a 24-bit (16MiB) address space - they opted to go with a 20-bit space, since after all, who would be able to afford a full megabyte of memory for a microcontroller?

Segments were not an invention of Intel's; but after they were used in the IBM System/360 in 1966, with unpleasant consequences, most of the industry realized that they were not a particularly wise idea and avoided them. Intel, however, figured it wouldn't hurt, since they anticipated that the 8086 would only be used for maybe five or six years. Isn't the irony wonderful?

The particular approach they used took the segment nase, shifted upwards by one byte, and added the segment offset to that to get the absolute address. So, for 7c00:0000, the absolute address is:

Code: Select all

7c000
00000
-----
7c000
For 7c00:0200 (not 7c00:0010 - I made a mistake earlier, I meant to say you needed advance the loading location by 512 bytes, not 16 bytes), it would be

Code: Select all

7c000
00200
-----
7c200
However, you will note that these segments overlap at a period of 16, meaning that

Code: Select all

7c200
00000
-----
7c200
and

Code: Select all

7b100
01100
-----
7c200
and

Code: Select all

70f0
01100
-----
7c200
and even

Code: Select all

7b020
010f0
-----
7c200
This arrangement is, not to put too fine a point on it, a real PITA to really learn properly, and even experienced x86 coders frequently get confused by it if they aren't paying close attention.

Since the registers were all 16-bit, including the instruction pointer, there was no way to represent an absolute address in the registers - it wouldn't fit. Thus, all addressing had to be done from a segment base. By default, the instruction pointer (IP, also called the Program Counter or PC in other systems) is always from a segment offset in the Code Segment (CS) register, and the stack pointer (SP) is similarly always from Stack Segment (SS). With data, a 16-bit address defaults to an offset from the Data Segment (DS), but can be used with the Extra Segment (ES).

A basic assumption of all of this was that most addresses would be local to the segment pointed to by DS ('near' addresses); some instructions could only work on near addresses, just as some could only use AX as an implicit register target (the original version of IMUL was like this). To indicate a different base segment, the instruction opcodes had to have fields indicating the type of addressing (short, near, or far) and if far, which segment to use for the base.

This doesn't even touch the mess that is 16-bit and 32-bit protected mode segmentation; fortunately, in 32-bit p-mode, you actually have 32-bit address registers, so for a 4GiB address space you only need to set all the segments to a single base for each task record's virtual memory space, and treat it as if it were a sensible flat memory instead. Unfortunately, you still need to deal with real mode until you've switched to p-mode, so you do need to know about this stuff.

Wicked-Pedo has a much more extensive explanation of this, as does the OS Dev wiki on the page explaining Real Mode, though this should be enough to get you started - or terminally confused.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 3:22 pm
by Schol-R-LEA
onlyonemac wrote:
Schol-R-LEA wrote:You're point about it overwriting the first loaded sector is correct. However, in this instance, the OP would probably use something closer to this, assuming that ES and BX are not clobbered by the BIOS:

Code: Select all

add bx, 0x10    ; advance the load location by 512 bytes
int 0x13
Yes, you can do this as well. In my own bootloader I change the segment register, because it scales beyond 64KB and avoids complications with offsets wrapping around, and it simplifies the code because you're only setting/changing one value rather than two.
Actually, I gave the wrong numbers; adding 0x10 to the offset in BX (assuming no changes to the base in ES) would only be 0x10 (decimal 16) addresses higher. The correct offset increment, as I explained in my last post, should have been 0x0200 (decimal 512).

Sorry for the sequential posts, but I felt this needed to be highlighted.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 3:58 pm
by LtG
iansjack wrote: 2. More realistically, you parse the filesystem, find the file containing your kernel, determine which sectors make up that file, and load them.
Realistically however, you may need to load more sectors first, and then parse the filesystem. Otherwise you only have 512B worth of code and data to do the parsing of the FS.

So in practice, BIOS loads 512B (MBR) for you, that code needs to load more sectors, with that 512B+more_sectors you have enough code to parse the FS of your choice (FAT for instance) and you go from there.

Part of the confusion may also be that there's plenty of options to choose from and you can do things any way you like, that's just freedom... osdev comes with a lot of design choices that _you_ have to make.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Mon Aug 14, 2017 7:16 pm
by azblue
stevewoods1986 wrote:This is making me confused. How do I load my kernel if has more sectors with INT 13h? Do I repeat it? What do I do? I've tried experimenting.

This is just getting more confusing.

By the way, should I read the whitepaper done by Microsoft (from the FAT page)? Where is a good resource about the filesystem?
Earlier you said you don't know where your stack is. That's huge red flag! Set your stack and all your segments early on.

Your code suggested you don't understand segmentation -- others have already posted how it works, but a trick you can do is to set all your segments to 0 and then ignore them. That limits you to 64KB, but that will be fine for now.

Here's an example of how you can load >1 sector from a floppy:

Code: Select all

org 7c00h
xor ax, ax ;clear ax
mov ds, ax ;set ds
mov es, ax ;set es
mov ss, ax ;set ss no need to clear interrupts; processor does that automatically when setting ss:sp
mov sp, 7c00h ; set stack to just below you
jmp far 0:afterjump

afterjump: ;now all your segments are 0
mov ah, 2 ;read
mov al, 2 ;read 2 sectors, you can do more if you need (don't cross cylinder boundaries)
mov ch, 0 ;cylinder 0
mov cl, 2 ;start with the 2nd sector (you already have the first one)
mov dh, 0 ;head 0
mov bx, 7e00h ;DONT USE 7C00 THAT WILL OVERWRITE YOUR CURRENT CODE!
;don't mess with es, you've already set your segments you can ignore them now
;no need to jump, just keep running your code
That should work and should keep you busy until you get to a point where you're crossing cylinder boundaries (about 18 sectors I think?) From there you can work on code that will properly handle crossing cylinders as well as properly reading your file system.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Tue Aug 15, 2017 2:00 am
by onlyonemac
Schol-R-LEA wrote:
onlyonemac wrote:
Schol-R-LEA wrote:You're point about it overwriting the first loaded sector is correct. However, in this instance, the OP would probably use something closer to this, assuming that ES and BX are not clobbered by the BIOS:

Code: Select all

add bx, 0x10    ; advance the load location by 512 bytes
int 0x13
Yes, you can do this as well. In my own bootloader I change the segment register, because it scales beyond 64KB and avoids complications with offsets wrapping around, and it simplifies the code because you're only setting/changing one value rather than two.
Actually, I gave the wrong numbers; adding 0x10 to the offset in BX (assuming no changes to the base in ES) would only be 0x10 (decimal 16) addresses higher. The correct offset increment, as I explained in my last post, should have been 0x0200 (decimal 512).
I missed that. I'm not particularly familiar with using offsets with int 0x13, so I figured that the offset was multiplied by some value.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Tue Aug 15, 2017 2:05 am
by onlyonemac
neon wrote:Hello,

For now, set all segment registers to 0 (as recommended above.) Also put "org 0x7c00" at the top of the file if not already done so. To set CS, do a jmp 0:.fix_cs instruction. This way you don't have to worry about segmentation too much.

So...You have an assembly file that %include's other files. When you assemble it, you should only get one binary file. So all you need to do is load that file into memory and execute it, right? The BIOS already loaded the first 512 bytes for you. So you just need to load the rest.

Copy your binary file to the disk image. It should span multiple sectors. Sector 1 is already loaded by the BIOS to 0x7c00. So you just need to load sector 2 to 0x7e00 and sector 3 to 0x8000. Does that make sense?

For now, just load sector 2 to 0x7e00 and see if it works. That is, make your program only 2 sectors in size and try it. After you get that working, try to make a generic "ReadSectors" function that uses int 13h in a loop to read multiple sectors.
By the way, should I read the whitepaper done by Microsoft (from the FAT page)? Where is a good resource about the filesystem?
Sure, if you want to implement the FAT file system. But you'll need to be able to read sectors first. ;)
This is not a good approach. Every bootloader that I've ever come across uses two (or more) separate stages. If you do it this way, you'll have to avoid executing the boot signature at the end of the boot sector.

What you should actually do is decide where you want your main (a.k.a. second stage) bootloader to be loaded in memory, then the job of your boot sector (a.k.a. first stage bootloader) is just to read the main (a.k.a. second stage) bootloader from disk and put it at the pre-decided location in memory, then jump to it. Never make a bootloader that "doesn't fit" in the first sector and "spills over" to the next sectors, and is intended to be executed as a single file.

In other words, make your bootloader as two separate binary files. The first binary file should be exactly 512 bytes and this goes in your boot sector. The second binary file can be whatever size you want and you can put it wherever you want on the disk, and the job of the first binary file is to load the second binary file and execute it.

Re: How do I load my sector with if it's more than 512 bytes

Posted: Tue Aug 15, 2017 2:17 am
by glauxosdever
Hi,

The first binary file should be exactly 512 bytes and this goes in your boot sector. The second binary file can be whatever size you want and you can put it wherever you want on the disk, and the job of the first binary file is to load the second binary file and execute it.
Or design a custom filesystem and reserve more space for stage1 and/or other boot files and this way you can extend stage1 after 512 bytes. The first 512 bytes will simply load the rest without parsing any filesystem.

Having said that, I wonder why partition scheme designers didn't think of the possibility of reserving some sectors at the start of a partition for boot files, so the bootloader is filesystem-agnostic and has more space to do error checking.


Regards,
glauxosdever