7

I try to create a simple bootloader which print "hello world".

I can do it when I call a function which only print "hello world", but when I call a function to print a specific string, nothing is happening.

For it, I use two files. The first one is boot.ld and the second is boot.cpp (it also work in C with boot.c).

Firstly, I create the floppy disk from my terminal:

dd if=/dev/zero of=floppy.img bs=512 count=2880

Secondly, I compile the code (boot.cpp and boot.ld):

gcc -c -g -Os -m64 -ffreestanding -Wall -Werror boot.cpp -o boot.o

ld -static -Tboot.ld -nostdlib --nmagic -o boot.elf boot.o

objcopy -O binary boot.elf boot.bin

Lastly, I add boot.bin into floppy.img:

dd if=boot.bin of=floppy.img

Now we just need to add the floppy from the storage panel of VirtualBox and launch our Virtual Machine.

FloppyImage

The source code

from: http://www.codeproject.com/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Part

boot.ld

ENTRY(main);
SECTIONS
{
    . = 0x7C00;
    .text : AT(0x7C00)
    {
        *(.text);
    }
    .sig : AT(0x7DFE)
    {
        SHORT(0xaa55);
    }
}

boot.cpp (or boot.c)

void cout();

void main()
{
    cout();
}

void cout()
{
    __asm__ __volatile__("movb $'h' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'e' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'l' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'l' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'o' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $' ' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'w' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'o' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'r' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'l' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");

    __asm__ __volatile__("movb $'d' , %al\n");
    __asm__ __volatile__("movb $0x0e, %ah\n");
    __asm__ __volatile__("int  $0x10\n");
}

Output:

TestBootLoader1

The bugged source code

boot.cpp (or boot.c)

void cout(const char* str);

void main()
{
    cout("hello world");
}

void cout(const char* str)
{
    while(*str)
    {
        __asm__ __volatile__ ("int $0x10" : : "a"(0x0e00 | *str), "b"(0x0007));
        ++str;
    }
}

Output:

TestBootLoader2

Why the output is empty?

What is wrong in my function?

I have forget something?

Thanks for your help.

34
  • Try checking disassembly, may be it will shed a light to the problem. Commented Oct 4, 2015 at 7:43
  • Nop, I can compile it properly... It didn't print any error message :/ Commented Oct 4, 2015 at 7:46
  • 1
    If you want to disassemble the final bin file you create, I recommend installing the ndisasm package and then you can run this to disassemble it in 16 bits with ndisasm -b16 boot.bin Commented Oct 4, 2015 at 8:58
  • 1
    Here is the problem. I am doing this at 3am and I missed the obvious. I kept talking about 16bit experimental code generation. At the top of your file get ride of the __asm line with .code16 and try replacing it with asm(".code16gcc") . As well in your main your function has no where to return to. So after the call to cout hlt the processor with __asm__ __volatile__("cli\n") followed by __asm__ __volatile__("hlt\n") Commented Oct 4, 2015 at 9:50
  • 1
    You can do it in assembly but with the modifications I suggested I actually got it running here with "C". That is assuming your GCC supports .code16gcc Commented Oct 4, 2015 at 9:51

2 Answers 2

2

On my cross-compiler (i686-elf-gcc (GCC) 4.9.2), the later code produces the following (dis)assembly:

    boot.o:     file format elf32-i386


Disassembly of section .text:

00000000 <cout>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   53                      push   %ebx
   4:   bb 07 00 00 00          mov    $0x7,%ebx
   9:   8b 55 08                mov    0x8(%ebp),%edx
   c:   0f be 02                movsbl (%edx),%eax
   f:   84 c0                   test   %al,%al
  11:   74 08                   je     1b <cout+0x1b>
  13:   80 cc 0e                or     $0xe,%ah
  16:   cd 10                   int    $0x10
  18:   42                      inc    %edx
  19:   eb f1                   jmp    c <cout+0xc>
  1b:   5b                      pop    %ebx
  1c:   5d                      pop    %ebp
  1d:   c3                      ret

I'm very interested in whether you're using GCC (a non-16-bit-compatible compiler) with 16-bit stuff (BIOS interrupts). If you're going to do 16-bit code, do it in full assembly! GCC will simply mess it up, because it's generating 32-bit code that will be run on 16-bit mode. If you want to go directly to C/C++, then what you want to write is probably not a bootloader, but a kernel. In such a (common) case, read the unquestionable sacred ritual to initiate you into OSDev. The fact that your first example works is just luck, and any minimal change may break everything, even leading to the mythical horrifying triple fault, nightmares of kernel panics themselves.

Anyway, you're better off writing directly to VGA DMA memory than using BIOS calls (you need to get to protected mode first, and setup the VGA hardware and modes (GRUB does this for you, but you're creating a bootloader, aren't you?)):

void PrintString(const char *str) {
    uint16_t *vga = (uint16_t*)0xB8000;

    for(; *str != '\0'; str++, vga++)
        *vga = ((uint16_t)0x07 << 8) | *str; // Light grey on a black background, nice!
}

BTW, you may find the OSDev community, wiki, andforums very useful. And, as shown in the comments, you should be using .code16 for real mode code, and your linked article already shows its age.

Sign up to request clarification or add additional context in comments.

4 Comments

"GCC will simply mess it up, because it thinks it's generating 32-bit code." this part is not true, GCC generates 32-bit code, period. GAS will add 32bit-prefixes with the .code16 setting, so this 32-bit code is runnable in real mode.
@FelixPalmen: I've already addressed this issue with you in the comments of the OP. Edit: That part of my answer was actually a bit misleading/misunderstandable, so I changed it.
@FelixPalmen Same thing in other words: "it magically works for simple code, but don't be surprised if if happens that your code jumps to the wrong place, an condition is true instead of false, or you have an undetected arithmetic overflow where it shouldn't happen"
@DanielAlder maybe it's not mature enough but I have satisfying results with clang and the -m16 option .. no BIOS interrupt call was ever a problem. The only problem I encountered was some messing up at the assembly stage, which I could circumvent using GAS for that. But it was just for a simple .COM DOS executable, so not directly comparable to the attempt of writing a bootloader.
2

Thanks to @MichaelPetch for this answer.

Source code:

boot.ld

ENTRY(main);
SECTIONS
{
    . = 0x7C00;
    .text : AT(0x7C00)
    {
        *(.text);
    }
    .sig : AT(0x7DFE)
    {
        SHORT(0xaa55);
    }
}

boot.cpp

Also here: http://pastebin.com/6NV3UMjE

asm(".code16gcc");
__asm__("jmpl $0x0000, $main\n");

void cout(const char* str);

void main()
{
    __asm__ __volatile__ ("xor %ax, %ax\n");
    __asm__ __volatile__ ("mov %ax, %ds\n");
    cout("Hello World");
    __asm__ __volatile__("cli\n");
    __asm__ __volatile__("hlt\n");
}

void cout(const char* str)
{
    while(*str)
    {
        __asm__ __volatile__("int $0x10" : : "a"(0x0e00 | *str), "b"(0x0007));
        ++str;
    }
}

Compile:

gcc -c -g -O0 -m32 -ffreestanding -Wall -Werror boot.cpp -o boot.o

ld -melf_i386 -static -Tboot.ld -nostdlib --nmagic -o boot.elf boot.o

objcopy -O binary boot.elf boot.bin

dd if=boot.bin of=floppy.img conv=notrunc

Output:

Output

8 Comments

This an ugly fragile hack, and will cause tremendous problems in the future. Please read the OSDev Wiki and understand what does that solution that was gaven to you implies before proceeding. This is all to prevent you from getting in the same almost-undebuggable issues people have already came with while doing this sort of things.
Ok, let me take a coffee before and I start to read. Have you an idea of a specific article than I need to read for understand why this code is an ugly hack?
(BTW: Sorry if I offended you/the proposer. That wasn't my intention). What happens is that .code16gcc is a (known to be fragile) experimental way to allow GCC code (which assumes protected mode and overall 32-bit stuff). Even the buddies from GNU already said they'ld never support 16-bit systems (sorry, couldn't found a reference to this, but I read it somewhere on gnu.org). Continuation
Continuation. Based from that, you can get perfectly deep and obscure bugs in your code that may actually be compiler/assembler bugs. It's simply not a feasible option for a bootloader. Once of the links I gave you even mentions that .code16gcc will work, as long as no memory is referenced by the code. Your best alternative is to use a GRUB-style strategy, that is, create two bootloaders. The first-stage bootloader is a small stub in the MBR that load the second-stage bootloader Continuation.
Continuation. The second-stage bootloader can be actually pretty big, and you'll be allowed to use protected mode and even long mode! Such a bootloader can actually present you with multiple O.Ses to boot to in the screen, and even support loadable modules (GRUB does, for example). If necessary, you may return to real mode (but only for necessary reasons, not for convience!), such as to allow a mechanism known as chain-loading. Tell me if you have any other doubts! :).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.