New Loader Code using SmallerC

It has been a while since I have posted something here, but I wanted to let you know about the SmallerC compiler and a small modification to make it so that you can write your Loader code writing up to 95% of it in the C language, using unreal mode and all available memory without any special quirks or assembly code.

Alex has been working on his compiler, SmallerC (wiki), and has included the ability to use unreal mode in your DOS applications.

With this ability and his suggestion, I have made a few modifications to the startup code and now have the ability to write OS code using the SmallerC compiler.

For example, your OS will have a one or two sector boot code which will look for and load a 2nd stage loader. These two sectors will most likely be written in assembly, their only purpose is to find the loader and load it. This loader can be anywhere on the disk and of any (reasonable) size (512k limit). It is this loader file that I am talking about.

What if I told you that using SmallerC and a slight modification to the startup code, you could start your loader.c file as:

/* passed register contents */
struct REGS {
  bit32u eax;
  bit32u ebx;
  bit32u ecx;
  bit32u edx;
  bit32u esi;
  bit32u edi;
  bit32u ebp;
  bit32u eflags;
  bit16u ds;
  bit16u es;
};

/* start of our Loader.c file.  Boot.asm jumps to here. */
int main(struct REGS *boot_regs) {
  .
  .
  .
}
          

That's it. As long as your one or two sector partition/floppy boot code located your loader.sys file, loaded it to a specific location in memory, you could then use SmallerC and write this loader.sys file using almost all C and using all the 4-gig address space.

The way the compiler gets away with this is that the CS and SS segments are still 16 bit segments. This is so you can still call the BIOS and a BIOS interruption (timer interrupt for example) won't crash your loader. However, the DS and ES (as well as FS and GS) segment registers are set using a 4Gig limit, zero based, 32-bit selector, allowing a flat address space. Then, within the compiler's startup code, it finds its base address and "patches" all of the code and data to use this found address allowing for allocated data to be above the 1Meg mark. One more note is that all functions/proceedures use far calls. Therefore, you can have more than 64k of code too. (This was my downfall before I used SmallerC)

All you need to do is download the latest compiler binaries from here, choosing the development platform. For example, if you are using Windows, choose the "binw" directory. Linux? Choose "binl". Then download this modified startup library file (c0du.asm) and place in the same directory as your loader.c file.

Create a resource file as such: (resource.txt)

-unreal -Wall -o loader.sys -I . -I ./includes -stack 65500

c0du.o

loader.c

Then you can add .c files to the end of the list as you see fit.

Notice that I included the command line '-I' parameter to allow included files in the current directory and in './includes' directory. I also gave it a default stack size of just less than 64k.

Write your loader.c file, and include any others in the resource file above, and compile using:

C:\some_dir\>smlrcc @resource.txt<enter>

There is one thing that you must do in your boot code. You must parse the DOS style .EXE at the beginning of your loader.sys file and patch your CS and IP registers. Place the following code at the end of your boot.asm file. This assumes 'LOADSEG' is the 16-bit segment address of your loaded loader.sys file.

i.e.: ((LOADSEG << 4) + 0) = physical address.

; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;  Now jump to the loader.
           mov  ax,LOADSEG  ; segment to load to
           mov  ds,ax
           
           cmp  word [0],5A4Dh
           je   short is_dos_exe

           ;           
           ; give an error, since we didn't find the .exe header
           ; then halt or reboot.           
           ;           
           
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; the loader.sys file is a DOS MZ type .exe file
is_dos_exe:
           add  ax,[08h]           ; skip over .exe header
           mov  bx,ax
           add  bx,[0Eh]
           mov  ss,bx              ; ss for EXE
           mov  sp,[10h]           ; sp for EXE
           
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; set up cs:ip
           add  ax,[16h]           ; cs
           push ax
           push word [14h]         ; ip
           
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ;  The loader.sys file (c0du.asm) will save all 
           ;  registers left here (at this point) and save
           ;  them to a buffer so that main() can have access
           ;  to their values.
           ;
           ;  For example, if you restore the DL register:
           ;
           ;     mov  dl,saved_drive
           ;
           ;  the register used for the drive indicator, the
           ;  loader file will be able to access it using:
           ;
           ;    drive = boot_regs->edx & 0xFF;
           
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; "jump" to loader.sys (c0du.asm)
           retf
          

So to recap. Your boot.asm file, the partition's boot code (or if a floppy, the code at LBA 0), looks for and loads the 'loader.sys' file to LOADSEG. All register values at the end of boot.asm will be saved and passed to main() as a parameter. Therefore, except for the assembly code that every loader file must do, all of the remaining code in your 2nd stage loader can be C using SmallerC and the modified c0du.asm file shown here.

Please let me know if you have any questions at fys [at] fysnet [dot] net or post to alt.os.development.