Call C code from bootloader

I am trying to write a bootloader. I would like to compile some C code so that the loader can load it into memory and go there.

I have two questions:

  • Is the calling convention the same as on x86? Namely, the arguments in the stack are from left to right.
  • How can I create the source binary using gcc?
+4
source share
5 answers

You can create simple binaries using the gcc linker using the linker script. the key is the OUTPUT_FORMAT directive (binary):

//======================================== FILE: linker.ld //======================================== OUTPUT_FORMAT(binary) SECTIONS { .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } } //======================================== 

I called the linker in the makefile as follows (whereas linker.ld is the linker script):

 //======================================== ld -T linker.ld loaderEntry.o loaderMain.o -o EOSLOAD.BIN -L$(lib) -lsys16 //======================================== 

I compiled the object code with

 //======================================== gcc -nostdinc -nostdlib -ffreestanding -c <code files> -o theObjectCode.o //======================================== 

to get rid of standard libraries that don't work in 16-bit mode.

for the MBR loader for handshake and loader, I used the following assembly code loaderMain.S gcc (loaderMain.o should be the first file passed to the linker, which is located at offset 0x0000, as you can see above). I used the -code16gcc directive to generate 16-bit code. However, the generated code will probably not work on older x86 machines, as I used incompatible ones (% esp, $ ebp, leave, etc.) that are available only for newer models.

 //======================================== FILE: loaderEntry.S //======================================== .text .code16gcc // the entry point at 0x9000:0x0000 // this is where I did a far call to by the MBR .globl loaderMain // loader C entry function name declaration push %cs // initialize data segments with same value as code segment pop %ax // (I managed only tiny model so far ...) movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs movw %ax, %ss // initialize stack segment with same value as code segment movl $0xffff, %esp // initialize stack pointers with 0xffff (usage of extended (dword) offsets does not work, so we're stuck in tiny model) movl %esp, %ebp call loaderMain // call C entry function cli // halt the machine for the case the main function dares to return hlt //======================================== 

assembly code calls the character that was defined in the CMain.c language file loader. in order to generate code compatible with 16-bit code, you must declare the use of the 16-bit command installed before the first line of code in each C file you use. This can only be done with the AFAIK built-in command:

  asm(".code16gcc\n"); // use 16bit real mode code set /* ... some C code .. */ // ... and here is the C entry code ... // void loaderMain() { uint cmdlen = 0; bool terminate = false; print(NL); print(NL); print("*** EOS LOADER has taken over control. ***\r\n\r\n"); print("Enter commands on the command line below.\r\n"); print("Command are executed by pressing the <ENTER> key.\r\n"); print("The command \'help\' shows a list of all EOS LOADER commands.\r\n"); print("HAVE FUN!\r\n"); print(NL); while (!terminate) { print("EOS:>"); cmdlen = readLine(); buffer[cmdlen] = '\0'; print(NL); terminate = command(); } shutdown(); } 

So far I have managed to write simple C code - so far I have failed with C ++ code, and I have managed to create only a small memory model (this means that CS, SS, DS and ES are all the same). gcc uses only offsets as pointer addresses, so it seems difficult to overcome the timy memory model problem without additional assembler code. (Although I heard that some people dealt with this issue in gcc)

The calling convention is that the last argument is first pushed onto the stack, and it seems that all values โ€‹โ€‹are dword aligned. An example assembly code that can be invoked in .code16gcc C code is located below:

 //====================== .text .code16gcc .globl kbdread // declares a global symbol so that the function can be called from C .type kbdread, @function // declares the symbol as a function kbdread: // the entry point label which has to the same as the symbol // this is the conventional stack frame for function entry pushl %ebp movl %esp, %ebp // memory space for local variables would be allocated by decrementing the stack pointer accordingly // the parameter arguments are being addressed by the base pointer which points to the same address while bein within the function pushw %ds // I'm paranoid, I know... pushw %es pushw %fs pushl %eax pushl %ebx pushl %ecx pushl %edx pushl %esi pushl %edi xorl %eax, %eax // calls the keyboard interrupt in order to read char code and scan code int $0x16 xorl %edi, %edi movl 8(%ebp), %edi // moves the pointer to the memory location in which the char code will be stored into EDI movb %al, (%edi) // moves the char code from AL to the memory location to which EDI points xorl %edi, %edi // paranoid again (but who knows how well the bios handles extended registers??).. movl 12(%ebp), %edi // moves the pointer to the memory location in which the scan code will be stored into EDI movb %ah, (%edi) // moves the scan code from AH to the memory location to which EDI points popl %edi // restoring the values from stack.. popl %esi popl %edx popl %ecx popl %ebx popl %eax popw %fs popw %es popw %ds leave // .. and the conventional end frame for functions. ret // be aware that you are responsible to restore the stack when you have declared local variables on the stack ponter. // the leave instruction is a convenience method to do that. but it is part of not early X86 instruction set (as well as extended registers) // so be careful which instruftion you actually use if you have to stay compatible with older computer models. //===================== 

btw The declaration of the C function header is as follows:

 //===================== void kbdread(char* pc, (unsigned char)* psc); //===================== 

Hope this was helpful somehow. Greetings.

+5
source

From your previous questions here, I assume that you want to create a bootloader for a modern x86 machine (i.e. 386 or later).

In real mode, the size of the operand and the default address is 16 bits. Unfortunately, GCC cannot generate 16-bit x86 assembler code. However, by placing the .code16gcc directive at the top of each file, you can tell as use command prefixes that will override the address and size of the operand. These prefixes are described in more detail in section 3.3.5 of the Intel 64 Software Development Guide and IA-32 Volume Volume.

More information on .code16gcc can be found here . Note that this guide dates back to 2003, and .code16gcc no longer experimental or at least stable enough to use Linux.

Since gcc does not know that, as in the case of a code call, it will remain unchanged. Here is the ld script that can be used to create the bootloader.

+4
source

8086 IS x86. 8088/86 used different models, small, medium, large, huge. And depending on the model, you could / could get the difference in the stack. The huge / large return address is the segment and offset, where the small return address is just an offset (for example, changing the entire stack setting). Karl already mentioned the width of the stack.

Compile and parse a few simple examples, and this should be obvious. If gcc doesn't fulfill a non-flat target, maybe try djgpp. or watcom or borland (free).

+2
source

First, 8086 - x86.

Secondly, the calling convention is specific to the compiler used and any of its functions that can change it (for example, you can often specify things like cdecl , stdcall , fastcall , etc.). Which compiler are you using?

Third, gcc does not compile code into 16-bit x86 instructions.

As suggested by @dwelch, use Open Watcom C / C ++ or the ancient Borland / Turbo C / C ++, which are free and can compile 16-bit code.

Here's how all this can be done, 1 , 2 .

+1
source
  • The calling convention is similar to IA32, except that everything is inserted into 16-bit words, not 32.
  • You probably can't directly create a flat binary with GCC. Does GCC support another 16-bit Intel processors? You can use objcopy to get the source binary data from your linked object. You will probably want to look into the linker scripts .
0
source

Source: https://habr.com/ru/post/1401252/


All Articles