23

I am a newbie in writing bootloaders. I have written a helloworld bootloader in asm, and I am now trying to write one in C. I have written a helloworld bootloader in C, but I cannot compile it.

This is my code. What am I doing wrong? Why won't it compile?

void print_char();
int main(void){
char *MSG = "Hello World!";
int i;

__asm__(
    "mov %0, %%SI;"
    :
    :"g"(MSG)
);
for(i=0;i<12;i++){
    __asm__(
        "mov %0, %%AL;"
        :
        :"g"(MSG[i])
    );
    print_char();
}

return 0;
}

void print_char(){
__asm__(
    "mov $0X0E, %AH;"
    "mov $0x00, %BH;"
    "mov $0x04, %BL;"
    "int $0x10"
);
}
TylerH
  • 20,799
  • 66
  • 75
  • 101

5 Answers5

18

Let me assume a lot of things here: you want to run your bootloader on an x86 system, you have the gcc toolchain set up on a *nix box.

There are some points to be taken into account when writing a bootloader:

  1. the 510 byte limit for a VBR, even lesser for MBR due to partition table (if your system needs one)
  2. real mode - 16 bit registers and seg:off addressing
  3. bootloader must be flat binary that must be linked to run at physical address 7c00h
  4. no external 'library' references (duh!)

now if you want gcc to output such a binary, you need to play some tricks with it.

  1. gcc by default splits out 32bit code. To have gcc output code that would run in real mode, add __asm__(".code16gcc\n") at the top of each C file.
  2. gcc outputs compiled objects in ELF. We need a bin that is statically linked at 7c00h. Create a file linker.ld with following contents

    ENTRY(main);
    SECTIONS
    {    
        . = 0x7C00;    
        .text : AT(0x7C00)
        {
            _text = .;
            *(.text);
            _text_end = .;
        }
        .data :
        {
            _data = .;
            *(.bss);
            *(.bss*);
            *(.data);
            *(.rodata*);
            *(COMMON)
            _data_end = .;
        }    
        .sig : AT(0x7DFE)    
        {        
            SHORT(0xaa55);
        }    
        /DISCARD/ :
        {
            *(.note*);
            *(.iplt*);
            *(.igot*);
            *(.rel*);
            *(.comment);
            /* add any unwanted sections spewed out by your version of gcc and flags here */    
        }
    }
    
  3. write your bootloader code in bootloader.c and build the bootloader

    $ gcc -c -g -Os -march=i686 -ffreestanding -Wall -Werror -I. -o bootloader.o bootloader.c
    $ ld -static -Tlinker.ld -nostdlib --nmagic -o bootloader.elf bootloader.o
    $ objcopy -O binary bootloader.elf bootloader.bin
    
  4. Since you already have built boot loaders with ASM, I guess the rest is obvious to you.

- taken from my blog: http://dc0d32.blogspot.in/2010/06/real-mode-in-c-with-gcc-writing.html

dc0d32
  • 181
  • 2
  • 5
8

A bootloader is written in ASM.

When compiling C code (or C++, or whatever), a compiler will 'transform' your human readable code into machine code. So you can't be sure about the result.

When a PC boots, the BIOS will execute code from a specific address. That code needs to be executable, directly.

That's why you'll use assembly. It's the only way to have un-altered code, that will be run as written, by the processor.

If you want to code in C, you'll still have to code an ASM bootloader, which will be in charge to load properly the machine code generated by the compiler you use.

You need to understand that each compiler will generate different machine codes, that may need pre-processing before execution.

The BIOS won't let you pre-process your machine code. The PC boot is just a jump to a memory location, meaning the machine code located at this location will be directly executed.

Macmade
  • 52,708
  • 13
  • 106
  • 123
  • 1
    I would call the whole thing the bootloader, the entry point, leading into the first call to a C function is assembler yes, but the bulk of the bootloader can be (and often is) written in C. (better to write real assembler for this startup code than messing around with inline assembler). – old_timer Aug 16 '11 at 14:47
  • Thank you for your reply. but i want ot know how can i create .bin file from c code so that i can boot it. i saw the compile instructions on this site http://dc0d32.blogspot.com/2010/06/real-mode-in-c-with-gcc-writing.html but it gives me error on ld command i.e linker.ld file not found. Pleas help me –  Aug 16 '11 at 14:57
  • 3
    @Dnyanesh: your bootloader MUST NOT be linked to any dynamic library. Not even linked to stdc. – jweyrich Aug 16 '11 at 15:37
  • 1
    This depends on which stage of the bootloader you are talking about. The mbr part is typically ASM, but the parts after not. Most OSes have a multistage bootloader nowadays. – Marco van de Voort Aug 17 '11 at 12:40
1

Since you are using GCC, you should read the info pages about the different "target environments". You most probably want to use the -ffreestanding flag. Also I had to use -fno-stack-protector flags to avoid some ugly magic of the compiler.

Then, you will get linker errors saying that memset and the like are not found. So you should implement your own version of these and link them in.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
0

I tried this a few years ago -- options may have changed.

You have to run gcc with -ffreestanding (don't link) and then link using ld with the flags -static, -nostdlib

Foo Bah
  • 25,660
  • 5
  • 55
  • 79
  • And have a look at the relevant FreeBSD code, except from the bootloader, also CSU, the ELF startup code provides nice examples how to do lowlevel C-asm mixes. – Marco van de Voort Aug 17 '11 at 12:41
-3

As far as I know, you cannot write bootloader in C. That is because, C needs you to work in a 32-bit protected mode while in bootloader some portions are in 16-bit mode.

  • 4
    There are C compilers that will generate 16-bit code. Openwatcom, SmallerC, Bruce's C compiler. There is a GCC ia16 port that can produce 16-bit code. GCC itself can produce C code that will run in real mode but requires a 386+ to run. – Michael Petch Jan 31 '20 at 20:20