22

Is it possible to create a bootloader in C or C++ without using some type of Assembler (and preferably without using __asm)? I'm writing an Operating System and would like it to be completely written in C and C++.

Joshua Vega
  • 598
  • 1
  • 8
  • 22
  • My first reaction was "no" because you need a 16-bit compiler for the early part of the boot, but I guess if you have that, there isn't too much stopping you (assuming you can use intrinsics, and that you have some tool to convert your executables into raw format)... curious myself actually. – user541686 Jul 24 '11 at 17:41
  • It might be possible in UEFI as for plain old bios, it is simply not possible. – xeon111 Jul 24 '11 at 17:47
  • If it's going to be an open source one, I would love to follow something like this. – McKayla Jul 24 '11 at 17:49
  • @xeon111, UEFI is just an interface specification. An implementation still needs to be able to run C code, and that normally means (at least) setting up a stack pointer for the C runtime to make sense. – Carl Norum Jul 24 '11 at 17:50
  • 3
    @Mehrdad - there's no way in C to write code outside the context of a function, which is going to be pretty important, even if you do have access to intrinsics. How would you set up the original stack pointer? Pretty much every function call is going to start with a `push` instruction, so there's a big Catch-22 there. Intrinsics and inline assembly are a big pain for portability too - I'd much prefer to see a flat assembly entry point for a bootloader. – Carl Norum Jul 24 '11 at 17:59
  • @Carl: You can use `__declspec(naked)` or something similar. That's actually a trivial problem to solve. – user541686 Jul 24 '11 at 18:09
  • @Mehrdad @Carl - That just omits the boilerplate `asm` code a compiler will insert into a function call for cdecl, etc. function call ... but doing something like that will not actually *create* a stack. For instance, how on x86 in 16-bit real-mode would you substitute `movs $0x7C00, %ss` in order to setup the stack segment to the location of the master-boot-record? I guess you could work with a flat memory model, but even then, you're going to have to allocate some stack memory somewhere, and point the `%sp` register to that static memory location. That again has to be done using `asm`. – Jason Jul 24 '11 at 19:05
  • @Jason: You can use [`__setReg`](http://msdn.microsoft.com/en-us/library/75y8125t.aspx), AFAIK. No assembly, unless your compiler doesn't support it (which is quite possible). – user541686 Jul 24 '11 at 19:24
  • 1
    @Mehrdad - if you use magic compiler features to write code that looks like C but isn't, that doesn't really count. It's certainly quite possible by adding features to the compiler to generate special behaviour. That doesn't make your code C, though. Jason is still right about setting up the stack pointer - how are you going to solve that problem without assembly? Use compiler intrinsics? That's still cheating... – Carl Norum Jul 24 '11 at 23:21
  • possible duplicate of [A boot loader in C++](http://stackoverflow.com/questions/3273430/a-boot-loader-in-c) – Matthew Slattery Jul 24 '11 at 23:41
  • @Matthew: while it's very related, I wouldn't say it's the same, as it asks about C++ exclusively and the answers focus a lot on the peculiarities of C++, as opposed to the general "no ASM" problem. – Joachim Sauer Jul 25 '11 at 06:35
  • 2
    Since you haven't specified a target processor, this isn't answerable. – bmargulies Jul 25 '11 at 22:11
  • Since you haven't specified a target processor, the answer is "Yes." – Robᵩ Oct 18 '13 at 15:20

4 Answers4

24

That's pretty system dependent. In most cases, the answer is going to be no - you will need to write some custom assembly to set up the C runtime before you start running your C code. There are some exceptions, however. The ARM Cortex-M0, for example, can run C code straight out of reset.

Presumably, though, you're not using an M0, so you're going to need to write some assembly. Again, it's system/chip dependent, but you might be able to get away with something as simple as:

reset_vector:
    mov  sp, SOME_KNOWN_GOOD_STACK_ADDRESS
    call c_entry_point

which simply initializes the stack pointer and calls your C program's entry point. Of course, this simple a setup depends on your chip having a reset vector/vector table that supports it, RAM (or something like RAM) being initialized before the reset vector gets called, and so on. There tend to be a lot of "gotchas" in early system initialization.

Prepare to get pretty friendly with your compiler, assembler, and linker documentation - generating a flat binary that you can flash down as a first stage bootloader is often a big pain in and of itself.

Good luck!

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • 1
    Nice answer. I would pay (reputation) to see a full code example written out that calls a C function starting with a naked boot (e.g. in qemu). – Kerrek SB Jul 24 '11 at 18:04
  • @Carl: No, you do **NOT** need assembler to set up a stack pointer. You can create a naked function with no prologue and epilogue, and do things by hand. -1 since that's simply not correct (will fix if that's fixed). – user541686 Jul 24 '11 at 18:10
  • According to the gcc-documentation, *"Use this attribute on the ARM, AVR, MCORE, RX and SPU ports to indicate that the specified function does not need prologue/epilogue sequences generated by the compiler. It is up to the programmer to provide these sequences. The only statements that can be safely included in naked functions are asm statements that do not have operands. All other statements, including declarations of local variables, if statements, and so forth, should be avoided.* – Jason Jul 24 '11 at 18:54
  • *Naked functions should be used to implement the body of an assembly function, while allowing the compiler to construct the requisite function declaration for the assembler.*" ... So it seems that a naked function will not suffice for setting up a stack ... you're still going to need some `asm` to setup your initial stack. – Jason Jul 24 '11 at 19:00
  • @kerrek So what exactly do you want it to do? no rep needed btw, – Captain Obvlious Jul 24 '11 at 19:14
  • @Jason: Ah, whoops. `__setReg` doesn't work on x86, I didn't know that. I'd +1 this if it's edited sometime, sorry. – user541686 Jul 24 '11 at 20:09
  • BTW, I would add a simple loop like `return_error: jmp return_error` right after the `call` command ... otherwise if something goes wrong, you may end up returning from your `c_entry_point` function, at which point the instruction pointer will go wild over memory, most likely causing a triple-fault which will be very hard to debug. – Jason Jul 24 '11 at 21:40
  • @Chet: Well, some code that I could compile with gcc/as/ld and put as the boot image of qemu, say, which would print out a message of my choosing, for example. That'd be a nice start. Executing a C function `init()` or so would be even better. I suppose a `printf` function would be quite challenging to provide. I'm just very curious as to what the initial stage of the boot process looks like and how one would set up a sensible execution environment! – Kerrek SB Jul 24 '11 at 21:54
  • 2
    @Mehrdad, but that's not standard C, then. If you have special compiler features that let you fake it out, that's a different story. I stand by my answer. – Carl Norum Jul 24 '11 at 23:19
  • @Kerrek ... Here are some good details you can follow, as well as a very early bootloader for Linux v 0.01: http://en.wikibooks.org/wiki/X86_Assembly/Bootloaders#GNU_GRUB – Jason Jul 26 '11 at 02:31
  • @Jason: Thanks a lot, very interesting! I've also found [this tutorial on OSDev](http://wiki.osdev.org/Real_mode_assembly_bare_bones) thanks to Mat's post. – Kerrek SB Jul 26 '11 at 02:51
6

Assuming this is for x86, you can probably get something running in 16bit mode if you have the right compiler options and manage to get the layout of your bootsector correct with the right linker magic.

But you won't get far with plain C (or C++): you'll need to mask interrupts real fast, and there is no C function for that. Assembly is required.

This is probably the same for most of the other architectures: C and C++ simply don't have those features built in (some compiler extensions might help you though).

A great resource for what you are attempting to do: OSDev.

Mat
  • 202,337
  • 40
  • 393
  • 406
3

Assuming x86 protected mode:

I believe the answer is No because you need to do something like this to switch to protected mode:

    lgdt[GDTR]
    jmp CODESEL:FLUSH
FLUSH:
    ...

I don't think there's a way to do the jmp instruction in pure C/C++, though I could be wrong. (I'm by no means an expert here; I'm just referencing a boot loader that I made some while ago.)

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Who said he wanted a modern, protected mode OS? (Or that he is on x86 for that matter?) – Mat Jul 24 '11 at 17:45
  • 1
    I just need something simple. It's gonna be a DOS-like system for a course I'm taking for freshman high school. – Joshua Vega Jul 24 '11 at 17:47
  • 3
    @Mat: Idk... it's because when I hear hoofbeats, I think horses, not zebras. ... **Freshman high school**? *impressed* – user541686 Jul 24 '11 at 17:47
  • 1
    @Mehrdad, Yeah I'm homeschooled and I decided to take Computer Science and Operating System Theory or whatever it's called. – Joshua Vega Jul 24 '11 at 17:52
  • @Josh: +1, I'm in college and taking that course, AFAIK, isn't even *required*! – user541686 Jul 24 '11 at 18:11
3

No, it's not possible with "pure" C, at least on x86 ... In addition to the fact that an x86 machine will boot into 16-bit real-mode (requiring a compiler to generate 16-bit and not 32-bit code), you will need the ability to mask interrupts, setup segment registers, load code into memory from a hardware device (i.e., disk), setup a stack, access I/O ports, etc., all of which on the x86 and other platforms require access to the CPU's registers and/or specific assembly commands.

Secondly, for C++, should you decide to define any classes, you will need to have some type of manually configured and run "constructors" in order to setup memory so that your initial classes can actually exist somewhere in memory... you also won't be able to throw any exceptions. Essentially any C++ specific features you will try to use will be useless, as these higher-level data-abstractions require the proper support from the OS run-time itself, which have to be setup using a combination of assembly and C-code.

Jason
  • 31,834
  • 7
  • 59
  • 78