3

I have a bootloader written in assembly (boot.s) and a kernel written in c (kernel.c).

I also have some other files such as: linker.ld and grub.cfg but I have no clue how to use them...

My Problem:

If i run:

gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c -lgcc
ld -melf_i386 -Tlinker.ld -nostdlib --nmagic -o kernel.elf kernel.o
objcopy -O binary kernel.elf kernel.bin

i get the Error: ld: Unrecognized emulation mode: elf_i386

PS.: Im using Windows 10 Pro 32Bit and also have VirtualBox installed (If this helps) for gcc im using cygwin.

kernel.c

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

static const uint8_t COLOR_BLACK = 0;
static const uint8_t COLOR_BLUE = 1;
static const uint8_t COLOR_GREEN = 2;
static const uint8_t COLOR_CYAN = 3;
static const uint8_t COLOR_RED = 4;
static const uint8_t COLOR_MAGENTA = 5;
static const uint8_t COLOR_BROWN = 6;
static const uint8_t COLOR_LIGHT_GREY = 7;
static const uint8_t COLOR_DARK_GREY = 8;
static const uint8_t COLOR_LIGHT_BLUE = 9;
static const uint8_t COLOR_LIGHT_GREEN = 10;
static const uint8_t COLOR_LIGHT_CYAN = 11;
static const uint8_t COLOR_LIGHT_RED = 12;
static const uint8_t COLOR_LIGHT_MAGENTA = 13;
static const uint8_t COLOR_LIGHT_BROWN = 14;
static const uint8_t COLOR_WHITE = 15;

uint8_t make_color(uint8_t fg, uint8_t bg)
{
    return fg | bg << 4;
}

uint16_t make_vgaentry(char c, uint8_t color)
{
    uint16_t c16 = c;
    uint16_t color16 = color;
    return c16 | color16 << 8;
}

size_t strlen(const char* str)
{
    size_t ret = 0;
    while ( str[ret] != 0 )
        ret++;
    return ret;
}

static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 24;

size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t* terminal_buffer;

void terminal_initialize()
{
    terminal_row = 0;
    terminal_column = 0;
    terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
    terminal_buffer = (uint16_t*) 0xB8000;
    for ( size_t y = 0; y < VGA_HEIGHT; y++ )
        for ( size_t x = 0; x < VGA_WIDTH; x++ )
        {
            const size_t index = y * VGA_WIDTH + x;
            terminal_buffer[index] = make_vgaentry(' ', terminal_color);
        }
}

void terminal_setcolor(uint8_t color)
{
    terminal_color = color;
}

void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
    const size_t index = y * VGA_WIDTH + x;
    terminal_buffer[index] = make_vgaentry(c, color);
}

void terminal_putchar(char c)
{
    terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
    if ( ++terminal_column == VGA_WIDTH )
    {
        terminal_column = 0;
        if ( ++terminal_row == VGA_HEIGHT )
        {
            terminal_row = 0;
        }
    }
}

void terminal_writestring(const char* data)
{
    size_t datalen = strlen(data);
    for ( size_t i = 0; i < datalen; i++ )
        terminal_putchar(data[i]);
}

void kmain()
{
    terminal_initialize();
    terminal_writestring("Starting mOS...\n\n");
    terminal_writestring("mOS Version alpha1 - Created by milan44\n");
}

boot.s

.set ALIGN,    1<<0
.set MEMINFO,  1<<1
.set FLAGS,    ALIGN | MEMINFO
.set MAGIC,    0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)

.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

.section .bootstrap_stack
stack_bottom:
.skip 16384
stack_top:

.section .text
.global _start
_start:
    movl $stack_top, %esp

    call kmain

    cli
hang:
    hlt
    jmp hang

linker.ld

ENTRY(_start)

SECTIONS
{
    . = 1M;

    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot)
        *(.text)
    }

    .rodata BLOCK(4K) : ALIGN(4K)
    {
        *(.rodata)
    }

    .data BLOCK(4K) : ALIGN(4K)
    {
        *(.data)
    }

    .bss BLOCK(4K) : ALIGN(4K)
    {
        *(COMMON)
        *(.bss)
        *(.bootstrap_stack)
    }
}
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Milan
  • 63
  • 1
  • 1
  • 9
  • For what architecture? x86? ARM? If x86, does it use legacy BIOS or 32 or 64-bit EFI? – Peter Cordes Mar 15 '18 at 09:19
  • I dont know that, wait a second i will post my code... – Milan Mar 15 '18 at 09:20
  • Related: https://stackoverflow.com/questions/34268518/creating-a-bootable-iso-image-with-custom-bootloader and https://stackoverflow.com/questions/35138651/how-can-i-create-a-bootable-cd-image-with-my-kernel for making an ISO – Peter Cordes Mar 15 '18 at 09:21
  • https://stackoverflow.com/questions/33603842/how-to-make-the-kernel-for-my-bootloader/33619597#33619597 for linking C and asm – Peter Cordes Mar 15 '18 at 09:22
  • Thanks a lot @PeterCordes but i dont understand what to do... Im a real newbie in this kinda of stuff... – Milan Mar 15 '18 at 09:30
  • You're probably going to want to set up BOCHS so you can single-step / debug your code. Unlike VirtualBox, BOCHS has a debugger built in. You might as well do that while you wait for someone to answer this. But you already have C and asm, and a linker script, so you should be all set to build a kernel binary and follow the steps in one of those other questions I linked. If not, see the last question. – Peter Cordes Mar 15 '18 at 09:36
  • @PeterCordes If i run: `gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c -lgcc` , `ld -melf_i386 -Tlinker.ld -nostdlib --nmagic -o kernel.elf kernel.o` , `objcopy -O binary kernel.elf kernel.bin` i get the Error: `ld: Unrecognized emulation mode: elf_i386` – Milan Mar 15 '18 at 09:38
  • Are you running a Windows build of gcc / binutils? You didn't put that in your question, so I assumed you had your development toolchain already sorted out. (hint, [edit] your question with important details like that.) – Peter Cordes Mar 15 '18 at 09:40
  • @PeterCordes im using cygwin and have installed gcc 6.4.0 – Milan Mar 15 '18 at 09:52
  • I wouldn't be surprised if cygwin `gcc` / `ld` don't know how to create ELF object files. @MichaelPetch probably knows, he likes these osdev questions and I think uses Windows. – Peter Cordes Mar 15 '18 at 09:58
  • @PeterCordes okay, thanks a lot for your help – Milan Mar 15 '18 at 10:09
  • You're still definitely going to want BOCHS or something equivalent to debug this once you get it to boot, if you want to try changing anything. You have source for a `multiboot` image which needs to be loaded by GRUB, which then I think runs your `_start` in 32-bit protected mode. – Peter Cordes Mar 15 '18 at 10:15
  • I have BOCHS 2.6.9 installed – Milan Mar 15 '18 at 10:28
  • 1
    @PeterCordes : Chuckle, I use Windows but not for OS Development (It is okay of course if you do it in Windows Subsystem for Linux),but yes it is possible to do it in Cygwin but it diverges from regular compilers/linkers that support ELF directly. I only know how to do it on Cygwin because I've had to help others with it. At the very least this is yet another reason why using a generic elf cross compilers is preferable. You can get away from the nuances of the host environment. Cygwin's 32-bit LD only supports i386pe (win32 PE format). Objcopy on Cygwin supports converting i386pe to elf_i386. – Michael Petch Mar 15 '18 at 22:09

1 Answers1

2

Use a Cross Compiler Tool Chain

I highly recommend you build a C cross compiler and tool chain that generates ELF objects. This breaks you from the nuances of host compilers and linkers. Default Cygwin GCC and LD have a number of differences from a generic ELF compiler and linker. The OSDev Wiki include information for building a cross compiler for Cygwin. I haven't personally built a cross compiler on Cygwin so can't say if the instructions are accurate for that environment.

Cygwin generates Windows PE32(32-bit) and PE32+ (64-bit) objects. This is why -melf_i386 doesn't work. Building an ELF cross compiler would allow you to use -melf_i386. You will need an ELF cross compiler in your case because the multiboot loaders require an ELF executable which Cywgin's GCC and LD can't generate.

Had you been using 64-bit Windows 10 you would have been able to do this under Windows Subsystem for Linux (WSL) since Ubuntu's GCC and LD will generate ELF exectuables by default.


If you don't want to use a Cross Compiler

Although pushing you to have a cross compiler is the right way to do this, there is a way to make it work with Cygwin.

Cygwin GCC (like other 32-bit Windows Compilers) will prepend an _ to the non-static functions that will be of globally visible scope. That means your kmain is actually _kmain. Modify your boot.s to do call _kmain instead of call kmain. This applies to any C functions you call from assembly. Any functions you make available in assembly files to be accessed in C code will have to have an _ underscore added to them as well.

One big difference with Windows program is that the section names may be a bit different. rodata in Cygwin can be .rdata*. There may be a number of sections starting with rdata. You will have to account for this in your linker script:

ENTRY(_start)

SECTIONS
{
    . = 1M;

    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot)
        *(.text*)
    }

    .rodata BLOCK(4K) : ALIGN(4K)
    {
        *(.rodata)
        *(.rdata*)    /* IMPORTANT - Windows uses rdata */
    }

    .data BLOCK(4K) : ALIGN(4K)
    {
        *(.data)
    }

    .bss BLOCK(4K) : ALIGN(4K)
    {
        *(COMMON)
        *(.bss)
        *(.bootstrap_stack)
    }
}

This makes a big difference given that if you don't properly deal with the rdata sections they may be placed before your Multiboot header possiblly causing it to be not seen by a Multiboot loader like GRUB. So this change is very important.

Your commands to build a file usable by a Multiboot compliant bootloader (or QEMU's -kernel option) is incorrect. Since LD can't output an ELF file you will need to have OBJCOPY to convert a PE32 executable to a 32-bit ELF executable. Your OBJCOPY command does the wrong thing. You convert to a binary file. Unfortunately the way your Multiboot header is written that doesn't work.

The commands to assemble and link your code, and to produce the final kernel.elf file that can be use by a Multiboot loader could look like:

gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c
gcc -g -m32 -c -ffreestanding -o boot.o boot.s
ld -mi386pe -Tlinker.ld -nostdlib --nmagic -o kernel.pe kernel.o boot.o
objcopy -O elf32-i386 kernel.pe kernel.elf

Making a Bootable ISO/CD with Grub and your Kernel

This procedure is a bit tricky only in that Cygwin doesn't come with a grub-legacy package. To make a bootable ISO/CD image with Grub on it, you need to acquire the file stage2_eltorito. You can download a copy from this project.

You will have to run the Cygwin installer and install the package genisoimage

In the previous sections we built a file called kernel.elf. Now we have the components needed to build an ISO/CD image.

From the directory where you built kernel.elf we need to create a series of sub-directories. That can be done with:

mkdir -p iso/boot/grub

You need to copy the stage2_eltorito file and place it in iso/boot/grub directory. You will need to create the file menu.lst in iso/boot/grub as well.

iso/boot/grub/menu.lst :

default 0
timeout 0

title MyOS
# kernel ELF file.
kernel /boot/kernel.elf

The process above only has to be done once. This is enough to create a basic bootable ISO with Grub and our kernel.

Now the process of building the kernel, copying the file into the iso directory and generating the ISO/CD image can be done like this:

gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c
gcc -g -m32 -c -ffreestanding -o boot.o boot.s
ld -mi386pe -Tlinker.ld -nostdlib --nmagic -o kernel.pe kernel.o boot.o
objcopy -O elf32-i386 kernel.pe kernel.elf
cp kernel.elf iso/boot
genisoimage -R -b boot/grub/stage2_eltorito -no-emul-boot \
    -boot-load-size 4 -boot-info-table -o myos.iso iso

The genisoimage command creates an ISO/CD called myos.iso . You can change the name to whatever you please by replacing myos.iso on the genisoimage command line with the name you prefer.

The myos.iso should be bootable from most hardware, virtual machines, and emulators as a simple CD image. When run with your kernel it should appear something like:

enter image description here

The image above is what I saw when I booted the ISO/CD in QEMU with the command:

qemu-system-i386 -cdrom myos.iso

You should see similar if you run it in VirtualBox as well.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198