Writing bootloader with includes... [Solved. Mostly...]

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.
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes...

Post by TheGameMaker90 »

Octocontrabass wrote:
TheGameMaker90 wrote:Is it not possible to do it that way?
It's possible, but it definitely won't work if you forget RET at the end of your function, and you need to tell the compiler to emit 16-bit code, and you need to obey the compiler's ABI, and you need to load enough sectors from the disk because compiled C will quickly outgrow the one sector the BIOS loads for you.

You might want to consider splitting the bootloader into two or more stages, where the first stage is written entirely in assembly to fit within the single sector provided by the BIOS.

You might want to consider switching the CPU to 32-bit protected mode for your C code, and using a wrapper around BIOS interrupts that switches the CPU to real mode before calling the BIOS and back to 32-bit protected mode after the call returns.
Interesting... How would I do that? C generates no less than 32-bit code I thought. Perhaps this is one of the holes in my knowledge, and definitely not something I took into consideration. About the multiple disk sectors either.

That is something I was thinking of doing anyway, but with this approach, it seems inevitable based on what you're telling me. The problem is that I really have my heart set on getting it done this way (if for no other reason that learning), but something I'll take into consideration. I already had a basic bootloader at one point (or more like for one of my OSes, I have a couple of dummy projects also for learning). After getting to a cetain point I'd get a black screen. I quickly (or not quickly enough) realized this might be an issue, but only kept adding sectors. I'm not real sure how to do much else with disks, but I will be doing more research.

And isn't that the default way of doing it? Write a bootloader, switch to 32 bit protected mode, then call kmain or your kernel's entry point. I would honestly like to go above the normal way. If I physically can't, I'll no doubt go back to the default setup. I honestly haven't swapped anything out yet, so my OS will continue to use GRUB until I get [something/it] working.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Writing bootloader with includes...

Post by Octocontrabass »

TheGameMaker90 wrote:Interesting... How would I do that? C generates no less than 32-bit code I thought.
The GCC manual says "-m16". The code still requires a 32-bit CPU, but it will run when that CPU is in 16-bit mode. The rest of the ABI is the same as in 32-bit mode, including the requirement for CS, DS, ES, and SS to all have the same base.
TheGameMaker90 wrote:And isn't that the default way of doing it? Write a bootloader, switch to 32 bit protected mode, then call kmain or your kernel's entry point.
Yes, but if you want to call a BIOS function, you have to either do it before you switch to 32-bit protected mode or switch back to real mode. My suggestion falls in the "or switch back to real mode" category.
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Writing bootloader with includes... [solved]

Post by neon »

Hi,

If interested... I can provide a copy of the services method used in our boot loader if it'll be helpful for ideas as it does drop to real mode to call the BIOS... I am aware of some forum members using virtual 8086 mode instead so that may also be an option if you want an even bigger challenge.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes...

Post by TheGameMaker90 »

Octocontrabass wrote:
TheGameMaker90 wrote:Interesting... How would I do that? C generates no less than 32-bit code I thought.
The GCC manual says "-m16". The code still requires a 32-bit CPU, but it will run when that CPU is in 16-bit mode. The rest of the ABI is the same as in 32-bit mode, including the requirement for CS, DS, ES, and SS to all have the same base.
TheGameMaker90 wrote:And isn't that the default way of doing it? Write a bootloader, switch to 32 bit protected mode, then call kmain or your kernel's entry point.
Yes, but if you want to call a BIOS function, you have to either do it before you switch to 32-bit protected mode or switch back to real mode. My suggestion falls in the "or switch back to real mode" category.
Ah, okay. So I tried what you said about the -m16 and it didn't complain or anything, but I'm not seeing the 'X' I want to print as a test to see if it's working. Should I create a git repo for this? Maybe it would be easier to see what I have so far, especially since this isn't official.

Basically in my boot.S I have the following:

Code: Select all

boot.S:

...
.globl initialize_terminal
_start:
    call boot_main
    ret
initialize_terminal:
    mov $'X', %al
    mov $0x0e, %ah
    int $0x10

    ret
...

Code: Select all

bootloader.c:

extern void initialize_terminal(void);

void boot_main(void)
{
    initialize_terminal();
}
For my linker I used:
i686-elf-ld -Ttext 0x7C00 --oformat=binary boot.o bootloader.o -o boot.bin

Ideally, to make sure it's working I expect to see an X in there, but just an empty terminal :(

Edit:
So what should be stored in those registers? I tried "xor %ax, %ax" to zero them out, then moved %ax to all of them, then I tried 0x10. I'm admittedly clueless here...
neon wrote: Hi,

If interested... I can provide a copy of the services method used in our boot loader if it'll be helpful for ideas as it does drop to real mode to call the BIOS... I am aware of some forum members using virtual 8086 mode instead so that may also be an option if you want an even bigger challenge.
Thank you, I'd appreciate that. Perhaps poking around will give me additional insight.
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by neon »

Hi,

Please reference bios.h and su.asm. Note -- this code is specific to our loader and is not designed to be copied. The basic idea is the following:

1. Backup the protected mode IDTR and stack.
2. Install the real mode IVT into IDTR
3. Enter 16 bit protected mode
4. Enter 16 bit real mode
5. Call BIOS
6. Restore 32 bit IDTR
7. Enter 32 bit protected mode
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Writing bootloader with includes...

Post by Octocontrabass »

TheGameMaker90 wrote:

Code: Select all

_start:
    call boot_main
You're missing initialization code to set up an environment appropriate for running C.
TheGameMaker90 wrote:

Code: Select all

    ret
The BIOS does not call your bootloader, so you can't return.
TheGameMaker90 wrote:i686-elf-ld -Ttext 0x7C00 --oformat=binary boot.o bootloader.o -o boot.bin
You'll need a linker script.
TheGameMaker90 wrote:Ideally, to make sure it's working I expect to see an X in there, but just an empty terminal :(
It's simple code, you should be able to step through it with a debugger and see where things are breaking down.
TheGameMaker90 wrote:So what should be stored in those registers?
If you're talking about the segment registers, zero is the best choice. Keep in mind you can't use MOV to set CS.
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by TheGameMaker90 »

neon wrote:Hi,

Please reference bios.h and su.asm. Note -- this code is specific to our loader and is not designed to be copied. The basic idea is the following:

1. Backup the protected mode IDTR and stack.
2. Install the real mode IVT into IDTR
3. Enter 16 bit protected mode
4. Enter 16 bit real mode
5. Call BIOS
6. Restore 32 bit IDTR
7. Enter 32 bit protected mode
Thank you. And I just made a connection. You're the one who created BrokenThorn entertainment? I actually have that page bookmarked because I was looking at it for research at one point. My only problems with it were the use of _asm (non gcc inline assembly) and the use of bochs instead of Qemu. Not that it isn't a good emulator, I just got used to Qemu earlier on. Upon re-reading the tutorials, they are admittedly a bit outdated (like the links for Visual Studio 2005 or 2008). Do you think I could follow using Visual Studio 2017 or 19 Community Edition? I guess I could just continue in a Linux environment if anything. Just the assembly which is going to be the tricky part. I think I'll pick back up on your tutorial series. Everything seems very well commented and documented.

Getting back on topic, I created a git repo for my source as it stands. Perhaps you could give me some pointers on why my C code isn't calling the initialize_terminal function in assembly?

octocontrabass:
As I was about to post this comment, it stopped me and I saw your reply. What initialization code should I use?

And that explains a lot. I just put it there because of what you said about ret and the registers.

So the bootloader will need a seperate linker script from the one I'll use with the kernel later on?

I wasn't aware that there was a debugger. Where would I find one for such a task?

And okay, that's what I figured, I just wanted to be sure. And what do you mean? You're not able to change the value in CS? I know it stands for code segment. Or do you mean I can't do:
"mov $0, %cs"
because that I know. You can only move the accumulator, base, code, or data registers into it (I think that's the only ones).

Here, this is the link to the repo:
https://github.com/gamemaker90/Custom-Bootloader

Edit:
If need be, feel free to make the necessary modifications, but comment them so I can understand them :) .
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by neon »

Hi,
My only problems with it were the use of _asm (non gcc inline assembly) and the use of bochs instead of Qemu. Not that it isn't a good emulator, I just got used to Qemu earlier on. Upon re-reading the tutorials, they are admittedly a bit outdated (like the links for Visual Studio 2005 or 2008). Do you think I could follow using Visual Studio 2017 or 19 Community Edition?
A little off topic but will address... Any later version will work. Any case, we are planning a possible advanced reboot so hopefully all feedback is addressed. (If interested, I personally prefer Virtual Box here.)
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by Octocontrabass »

TheGameMaker90 wrote:As I was about to post this comment, it stopped me and I saw your reply. What initialization code should I use?
You need to set up the segment registers (ideally all zeroes), you need to set up a decent stack (the one set up by the BIOS is too small), and you need to clear the direction flag. I think that's everything...
TheGameMaker90 wrote:I wasn't aware that there was a debugger. Where would I find one for such a task?
Bochs has a built-in debugger that works pretty well for real-mode code. Bochs and QEMU both support GDB as an external debugger. GDB works with some other VMs too, I don't have a full list.
TheGameMaker90 wrote:And what do you mean? You're not able to change the value in CS? I know it stands for code segment. Or do you mean I can't do:
"mov $0, %cs"
because that I know. You can only move the accumulator, base, code, or data registers into it (I think that's the only ones).
No, I mean the MOV instruction cannot be used to load the CS register. If you write "mov <anything>, %cs" it won't work. You have to choose a different instruction to set CS. A far JMP (LJMP in AT&T syntax) is a common choice.
TheGameMaker90 wrote:Here, this is the link to the repo:
You're going to have to put BOOT_MAGIC somewhere outside the .text section if you want the linker to fit everything into a single sector.
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by TheGameMaker90 »

neon wrote:Hi,
My only problems with it were the use of _asm (non gcc inline assembly) and the use of bochs instead of Qemu. Not that it isn't a good emulator, I just got used to Qemu earlier on. Upon re-reading the tutorials, they are admittedly a bit outdated (like the links for Visual Studio 2005 or 2008). Do you think I could follow using Visual Studio 2017 or 19 Community Edition?
A little off topic but will address... Any later version will work. Any case, we are planning a possible advanced reboot so hopefully all feedback is addressed. (If interested, I personally prefer Virtual Box here.)
Virtualbox is nice, but I'm not sure how that would work in a Linux OSDev system running on Virtualbox (lol). I like (as you can see in my repo) to be able to test it every time I make a change, so I add Qemu to the Makefile structure. The [completed] iso could be tested in Virtualbox though.

octocontrabass:
How would I do all of that? Let's break this down:
Setting up the segment registers:
mov $0, %ax # Or xor %ax, %ax
mov %ax, %cs
mov %ax, %ds
...

That's how I have it in my boot.S. If I'm understanding you correctly, this will not work?

Decent stack:
Would it be like the one in 32-bit mode like this:
stack_bottom:
.skip 16384
stack_top:

Direction flag:
No idea what that is, lol.

I will look into GDB, thank you.

And that's what I meant with CS. Look at how I have it in my boot.S file. There's only 3 files and the makefile on the repo. I made it deliberately condensed for readability. I'll add some comments.

And I will try to do that now. I put it back above .code16
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by neon »

Hi,

boot.c appears to be compiled as 32 bit as opposed to 16 bit code:

Code: Select all

0:  66 55                   push   bp
2:  66 89 e5                mov    bp,sp
5:  66 83 ec 88             sub    sp,0xff88
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by TheGameMaker90 »

neon wrote:Hi,

boot.c appears to be compiled as 32 bit as opposed to 16 bit code:

Code: Select all

0:  66 55                   push   bp
2:  66 89 e5                mov    bp,sp
5:  66 83 ec 88             sub    sp,0xff88
How though? I used the -m16 like Octocontrabass recommended. Is there something missing in my Makefile?
And how did you get all of that out? Which part implies that it is 32-bit?
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by neon »

Hi,

The operand size override prefix byte 0x66 selects the non-default operand size. If this was 16 bit code, there would be no 0x66 prefix. boot.c starts at the second sector of your bin file, 0x7e00 which was where I pulled the bytes from. I don't use 16 bit C code so cannot address why -- but it is certainly generating 32 bit code here.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
TheGameMaker90
Member
Member
Posts: 83
Joined: Thu Jan 07, 2021 2:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by TheGameMaker90 »

I see... So how do I convert bootloader.c to 16-bit code?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Writing bootloader with includes... [Solved. Mostly...]

Post by Octocontrabass »

TheGameMaker90 wrote:That's how I have it in my boot.S. If I'm understanding you correctly, this will not work?
Correct. You need to get rid of "mov %ax, %cs" and use some other instruction (such as LJMP) to set CS.
TheGameMaker90 wrote:Decent stack:
Would it be like the one in 32-bit mode like this:
That would work, but it's probably more than you need, and it would make your binary bigger. Personally, I'd set ESP to 0x7C00. It's very important that you set all of ESP! Code generated by GCC will use 32-bit registers, so the upper bits of ESP must be zero.
TheGameMaker90 wrote:Direction flag:
No idea what that is, lol.
So... you're going to find out, right?
neon wrote:boot.c appears to be compiled as 32 bit as opposed to 16 bit code:
You're disassembling it wrong. It's 16-bit code, but it uses mostly 32-bit registers.
Post Reply