0

I'm trying to follow OS Dev Wiki's Bare Bones tutorial. When I try to link everything together though, my linker (Cygwin Binutils) gives me the following error:

boot.o: In function `start':
boot.asm:(.text+0x6): undefined reference to `kernmain'

when I run the command

i686-pc-cygwin-gcc -T linker.ld -o myos.bin -ffreestanding -O2 -nostdlib kernel.o boot.o -lgcc

Previously, I had thought that the identifier(kernel_main()) for entry point in kernel.c had the same identifier as some other important namespace (per this other somewhat similary answer which I've already tried and didn't work).

---Edit--- Here's the source, although you can reproduce everything with the stuff on the wiki page.

boot.asm

MBALIGN equ 1 << 0
MEMINFO equ 1 << 0
FLAGS equ MBALIGN | MEMINFO
MAGIC equ 0x1BADB002                ;magic number tells cpu where to find the bootloader
CHECKSUM equ -(MAGIC + FLAGS)       ;checksum of above

section .multiboot
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

section .bss
align 16
stack_bottom:
resb 16384 ;16 kilobytes
stack_top:

section .text
global _start:function (_start.end - _start)
_start:
    mov esp, stack_top

    [extern kernmain]
    call kernmain

    cli

.hang: hlt
    jmp .hang

.end:

kernel.c

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

enum vga_color
{
    BLACK = 0,
    BLUE = 1,
    GREEN = 2,
    CYAN = 3,
    RED = 4,
    MAGENTA = 5,
    BROWN = 6,
    LIGHT_GREY = 7,
    DARK_GREY = 8,
    LIGHT_BLUE = 9,
    LIGHT_GREEN = 10,
    LIGHT_CYAN = 11,
    LIGHT_RED = 12,
    LIGHT_MAGENTA = 13,
    LIGHT_BROWN = 14,
    WHITE = 15
};

static inline uint8_t vga_entry_color(enum vga_color foreground, enum vga_color background)
{
    return foreground | background << 4;
}

static inline uint16_t vga_entry(unsigned char c, uint8_t color)
{
    return (uint16_t)c | (uint16_t)color << 8;
}

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

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

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 = vga_entry_color(WHITE, 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] = vga_entry(' ', 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] = vga_entry(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_write(const char* data, size_t size)
{
    for (size_t i = 0; i < size; i++)
        if (data[i] == '\n')
        {
            terminal_row++;
        }
        else if (data[i] == '\r')
        {
            terminal_column = 0;
        }
        else 
        {
            terminal_putchar(data[i]);
        }
}

void terminal_writestring(const char* data)
{
    terminal_write(data, strlen(data));
}

void kernmain(void)
{
    terminal_initialize();

    terminal_writestring("Hello, kernel World!\nHere again");
}

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)
    }
}

And here are the commands I ran to build it

nasm -f elf32 boot.asm -o boot.o
i686-pc-cygwin-gcc -c kernel.c -o kernel.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
i686-pc-cygwin-gcc -T linker.ld -o myos.bin -ffreestanding -O2 -nostdlib kernel.o boot.o -lgcc
mike
  • 100
  • 7
  • Questions seeking debugging help must provide the full code or a [mcve] and all commands needed to build it in the question itself. Please add your code. – fuz Jan 15 '21 at 23:58
  • You might need to swap the `kernel.o` and the `boot.o` in case your linker goes left to right. – Jester Jan 15 '21 at 23:59
  • @Jester I had already tried that out. It didn't work. – mike Jan 16 '21 at 00:02
  • 2
    Your C compiler may also automatically prepend an underscore so try using `extern _kernmain` and `call _kernmain`. If all else fails, dump the symbols from the `kernel.o` to see what the compiler declared. Or use `-S` option to gcc to get an assembly listing. – Jester Jan 16 '21 at 00:03
  • 1
    @Jester Thanks, it successfully built. Though, why would it insert an underscore? – mike Jan 16 '21 at 00:04
  • [Why do C compilers prepend underscores to external names?](https://stackoverflow.com/q/2627511/547981) – Jester Jan 16 '21 at 00:07
  • 1
    your cygwin compiler is outputting COFF objects (used in Windows and have a name mangling convention of requiring an `_`). You Cygwin compiler/linker also know how to use ELF objects and will combine them with COFF and output a COFF/PE executable. – Michael Petch Jan 16 '21 at 01:16
  • @MichaelPetch Is there any way to tell the Cygwin linker not to output a output a COFF/PE executable. – mike Jan 16 '21 at 01:29
  • 1
    Since you are creating freestanding code compile the `.c` files with the `-fno-leading-underscore` option. I highly recommend though that you consider using an ELF cross compiler rather than something that generated native Windows programs. For instance your linker script uses `.rodata` for the section name. With cygwin the section names generated are `.rdata*` . You should use .rdata with a `*` – Michael Petch Jan 16 '21 at 01:34

0 Answers0