Search code examples
gccassemblymakefilelinkergnu-assembler

Compiling x86 and c with makefile


I've been writing C for 2-3 years and recently picked up assembly but as I use Windows I've never needed to use make files before as I've just used Visual Studio. I'm trying to use Cygwin and an i686 cross compiler to compile c and assembly files, and then link them together into a binary file representing my operating system. I am new to make files so I don't know how to do this properly. This is what I've got so far for the Makefile:

CC = i686-elf-gcc
CC_FLAGS = -c -std=gnu99 -ffreestanding -O2 -Wall -Wextra -I./include
AS = i686-elf-as
LD = i686-elf-gcc -T linker.ld -o myos.bin
LD_FLAGS = -ffreestanding -O2 -nostdlib -lgcc
O_FILES = $(wildcard src/*.o)

all: $(O_FILES)
    $(LD) $(LD_FLAGS) $(O_FILES)

src/%.o: src/%.c
    $(CC) $(CC_FLAGS) -o $@ $<

src/%.o: src/%.asm
    $(AS) -o $@ $<

I'm getting an error stating that the linker can't find the entry symbol _start so obviously nothing is compiling. How would I fix this?

My src/linker.ld file that defines _start as the entry point is:

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

The src/boot.asmfile I'm using that defines my _start label is:

# Declare constants for the multiboot header.
.set ALIGN,    1<<0             # align loaded modules on page boundaries
.set MEMINFO,  1<<1             # provide memory map
.set FLAGS,    ALIGN | MEMINFO  # this is the Multiboot 'flag' field
.set MAGIC,    0x1BADB002       # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot

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

.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:

.section .text
.global _start
.type _start, @function
_start:
        mov $stack_top, %esp

        call kernel_main

        cli
1:      hlt
        jmp 1b

.size _start, . - _start

Solution

  • You will need to modify your Makefile to something like this:

    CC = i686-elf-gcc
    AS = i686-elf-as
    LD = i686-elf-gcc
    AS_FLAGS =
    LD_FLAGS = -ffreestanding -nostdlib -lgcc -Tlinker.ld
    CC_FLAGS = -c -std=gnu99 -ffreestanding -O2 -Wall -Wextra -I./include
    
    C_FILES := $(wildcard src/*.c)
    ASM_FILES := $(wildcard src/*.asm)
    O_FILES := $(C_FILES:.c=.o) $(ASM_FILES:.asm=.o)
    KERNEL_BIN := myos.bin
    
    all: $(KERNEL_BIN)
    
    clean:
            rm -f $(KERNEL_BIN) $(O_FILES)
    
    $(KERNEL_BIN): $(O_FILES)
            $(LD) $(LD_FLAGS) -o $@ $^
    
    %.o: %.c
            $(CC) $(CC_FLAGS) -o $@ $<
    
    %.o: %.asm
            $(AS) $(AS_FLAGS) -o $@ $<
    

    This Makefile is different in that we construct a list of ASM files and C files. I also cleaned up the LD_FLAGS a bit and added an extra rule to create myos.bin and to clean the object and bin files.

    In your current code you'd have the Makefile variables with this in them after expansion:

    C_FILES   = src/string.c src/tty.c src/kernel.c
    ASM_FILES = src/boot.asm
    O_FILES   = src/string.o src/tty.o src/kernel.o src/boot.o
    

    O_FILES was derived from concatenating both file lists together and replacing the .c and .asm extensions with .o. This is the list of all the objects that need to be generated from their source files.

    With GNU Assembler it is customary to use files with .s extensions (or .S if you want to use the C preprocessor) rather than .asm


    The reason the _start label wasn't found is because the assembly files were not being processed. This means that boot.asm was not becoming boot.o and thus wasn't being linked at all.