OSDev.org

The Place to Start for Operating System Developers
It is currently Tue Apr 16, 2024 1:15 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: Issue with bootloader
PostPosted: Sat May 02, 2020 7:51 am 
Offline
Member
Member

Joined: Tue Feb 18, 2020 3:29 pm
Posts: 1071
Hello,
I am currently working on a bootloader written in C and Assembly that is designed for floppies. As I am using PE, it being built under Visual Studio. I was wondering what the best way to make a floppy driver for a bootloader would be. If it is to drop down to real mode, how would I link that into 32 bit PE application?

_________________
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sat May 02, 2020 8:23 am 
Online
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4594
Location: Chichester, UK
Why not do the bits involving reads from the floppy before you enter Protected Mode? That way you can use BIOS calls.


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sat May 02, 2020 2:58 pm 
Offline
Member
Member
User avatar

Joined: Sat Nov 22, 2014 6:33 pm
Posts: 934
Location: USA
Your question is a little vague.

I think @iansjack took it as you have already created a boot loader which has loaded another stage/kernel file and you want to continue to read from the floppy.

I took your question as you are writing the actual boot sector using Visual Studio and wondering how to do it.

Which one of us is correct?

If the former is correct, then are you looking to write a driver for the floppy drive to read and write from/to the disk? As @iansjack stated, you would be better off reading all you need to read using the BIOS before moving to protected mode. However, writing a driver for the floppy drive can be interesting and enjoyable to do. I have done it.

If the latter is correct, you need to forget about Visual Studio and C and stick with assembly to write the boot loader. The later stage loader and kernel can be written in Visual Studio as a PE file, but the (1st stage) boot loader must be assembly.

Ben
- http://www.fysnet.net/media_storage_devices.htm


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sat May 02, 2020 3:28 pm 
Offline
Member
Member

Joined: Tue Feb 18, 2020 3:29 pm
Posts: 1071
I have decided to load all files before entering protected mode. Thank you for your help

_________________
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Thu May 07, 2020 11:07 pm 
Offline
Member
Member
User avatar

Joined: Sun Feb 18, 2007 7:28 pm
Posts: 1564
Hi,

Interestingly, our boot loader was written originally in MSVC. What we did was implement a separate function, ioServices, that drops the system down to real mode, calls the BIOS, and returns back to protected mode. Higher level code can then call it at any time to call BIOS interrupt services. We provided this function some time ago but never announced it publicly. It can be seen here. The idea was inspired off of the old bios.h geninterrupt call. It is assembled & linked as any ordinary object file. It is important to note that NBOOT_IMAGE_BASE and NBOOT_IMAGE_SEGMENT is dependent on the in-memory address of the boot loader. Of course, the boot loader and any buffer must be accessible from real mode (boot loader must be linked >= 64k base address.)

For completeness, here is our disk read method for floppy drives which demonstrates using this.
Code:
PRIVATE unsigned int DiskReadSectors (IN unsigned int cylNumber, IN unsigned int sectorNum, IN unsigned int  headNum,
             IN unsigned int  driveNum, OUT unsigned char* buffer) {

   INTR     in;
   INTR     out;

   if ( buffer == NULL )
      return 0;

   /* int 0x13 function 2. */
   _ah (in.eax) = 2;
   _al (in.eax) = 1;   /* numSectors */
   _cl (in.ecx) = (unsigned char) sectorNum;
   _ch (in.ecx) = (unsigned char) cylNumber;
   _dh (in.edx) = (unsigned char) headNum;
   _dl (in.edx) = (uint8_t) driveNum;
   in.es        = SEG    ( (uint32_t) buffer);
   _bx (in.ebx) = OFFSET ( (uint32_t) buffer);

   /* call BIOS. */
   io_services (0x13, &in, &out);

   /* status code is in AH.  */
   return _ah (out.eax);
}

_________________
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Fri May 08, 2020 5:23 pm 
Offline
Member
Member
User avatar

Joined: Sat Nov 22, 2014 6:33 pm
Posts: 934
Location: USA
Hi neon,

I use a very similar technique in my boot code. (Here is an older version) It is called from unreal mode, allowing the caller to have memory, stack, and code to be anywhere in the 4gig address space. The call, however, requires all memory used by the service to be accessible by a real mode BIOS.

An alternative to your technique, I use the same space for the 'in' registers as I do for the 'out' registers. I don't see any reason to preserve the 'in' state.

Also, I don't worry about calling it after the loader transfers control to the kernel. Once the kernel takes control, the loader is no longer a part of the system. However, I see an advantage to your technique.

In theory, since I use unreal mode through out my loader, I wouldn't need something like this. A real mode BIOS should work just fine in unreal mode. However, with a technique like the one I use, my loader, its stack, and any memory buffer needed, can be anywhere in the 4 gig space, using all of the 32-bits of a register, and still call a BIOS service call without worries.

Another note, my code uses self modifying code just like yours:
Code:
        mov   ebx, .call_service - NBOOT_IMAGE_BASE
        mov   byte [ ebx + 1 ], dl
    .
    .
    .
.call_service:
        int   0

One problem that I know is that you have to make sure there is enough of a code stream between the self modifying code and the self modified code so that the processor hasn't already loaded that code into the cache. Granted on some systems, the processor will see this and compensate. Older machines may not. However, I think you have plenty of space between the two.

I wanted to use a serializing instruction, such as CPUID, to guarentee this, but this assumed I had checked that the CPUID instruction was first available before a call like this. The chicken and the egg scenario here is that I use this "wrapper" to call the BIOS to print a message to the screen. If a CPUID instruction is not found, I would want to print an error message stating so. However, to print the error message, a CPUID instruction would have to be available to do so. Chicken/egg...

Anyway, good point. I think a good loader needs a BIOS wrapper like this.

Ben


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Fri May 08, 2020 6:45 pm 
Offline
Member
Member
User avatar

Joined: Sun Feb 18, 2007 7:28 pm
Posts: 1564
Hi,

The core boot loader program running in 32 bit protected mode or 64 bit long mode as opposed to unreal mode was a design choice to facilitate supporting BIOS and UEFI 32 and 64 bit builds; only calls to the IO services require memory accessible from real mode. Two memory pools are used by the allocator to facilitate buffers for real mode but it may be copied anywhere in the address space.

We did consider the CPU caching the instructions ahead before it gets to it. We were really trying to find a method to avoid self modifying code but alas after research in existing solutions this might be the only way without compiler support. I don't know if there is a more full-proof way for this, so am just waiting for it to potentially fail in which case we know we need to add something more.

The basic idea is the same though -- abstracting BIOS calls behind a single function. The details of how its done I believe is heavily dependent on design goals -- Unreal mode can be a good option for sure. For the chicken/egg scenario, perhaps a beep would work to indicate some kind of fatal error? Having the loader output debug records to the serial port for any attached debugger is also very useful here -- we don't use BIOS for that for obvious reasons.

_________________
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Fri May 08, 2020 10:33 pm 
Offline
Member
Member

Joined: Mon Feb 02, 2015 7:11 pm
Posts: 898
I wrote some code to call the BIOS from protected mode. It is self modifying code in that it modifies the "int xx" instruction to call the BIOS function I want. But not only is there a lot of code between the modification and the invocation, the processor also has to switch from protected mode to real mode. Surely this will flush the instruction pipeline. It would be rather surprising if the CPU was speculating real mode instructions while in protected mode.

There are also other ways beside using the CPUID instruction to flush the CPU pipeline.

_________________
https://github.com/kiznit/rainbow-os


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Fri May 08, 2020 11:15 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1604
BenLunt wrote:
I wanted to use a serializing instruction, such as CPUID, to guarentee this, but this assumed I had checked that the CPUID instruction was first available before a call like this. The chicken and the egg scenario here is that I use this "wrapper" to call the BIOS to print a message to the screen. If a CPUID instruction is not found, I would want to print an error message stating so. However, to print the error message, a CPUID instruction would have to be available to do so. Chicken/egg...
I remember reading in the Linux source code once that on CPUs without CPUID support, a conditional jump will flush the instruction queue. Therefore you only need to test for CPUID availability and do a conditional jump past the CPUID to flush the I-queue there. And on machines with CPUID support the jump would not happen, but then the CPUID instruction would synchronize things.

Alternatively, mark the CPUID instruction with a symbol and add a #UD handler that recognizes when IP == that symbol, and then returns to the point after it.

Honestly though, why don't you just put a bunch of if-then-else chains in there? I mean, with interrupts 0x10 and 0x13, you already have covered most of what people ever want to use BIOS for, right? Alternatively, use macros to get two different functions that are almost identical, but end up calling int 0x10 in one case, and int 0x13 in the other. That sidesteps the whole self-modifying code issue altogether.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sat May 09, 2020 2:22 am 
Offline
Member
Member

Joined: Mon Feb 02, 2015 7:11 pm
Posts: 898
I am fascinated by this self-modifying code issue. There is no issue. Just modify the int instruction at the beginning of your function that calls the bios. There is so much to do, including setuping and switching to real mode, that by the time you get to the int instruction, there are no concerns.

_________________
https://github.com/kiznit/rainbow-os


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sat May 09, 2020 3:18 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5134
Modern x86 CPUs handle self-modifying code automatically, as long as the code is being modified and executed at the same linear address. (A serializing instruction is required if writing and executing will use different linear addresses.)

Old x86 CPUs require a taken conditional jump or an unconditional jump to flush the prefetch queue.

Intel and AMD have various recommendations for self-modifying code, but both agree that an unconditional jump will satisfy the requirements of all CPUs, old and new. Adding a single "jmp short $+2" instruction after the write is enough.

kzinti wrote:
There is so much to do, including setuping and switching to real mode, that by the time you get to the int instruction, there are no concerns.

You're right, switching from protected to real mode involves an unconditional jump and a serializing instruction.


Top
 Profile  
 
 Post subject: Re: Issue with bootloader
PostPosted: Sun May 10, 2020 9:06 am 
Offline
Member
Member

Joined: Tue Feb 18, 2020 3:29 pm
Posts: 1071
Thank you for your help! I have currently paused this project in favor of NexNix, a Unix like operating system, but I will definitely use your suggestion when I make it again.

_________________
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 20 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group