Home > Mobile >  Undefined reference in makefile
Undefined reference in makefile

Time:09-06

I am doing a little operating system development and wanted to restructure my source code repository like shown below. As you can see I use a makefile the build the whole thing. Now comes the strange part: In the files vga.h and vga.c there is a function putstring(size_t posx, size_t posy, const char *str) which - you guessed it - writes a string to a given positon. I know, that their code is okay, since linking worked before, when all files resided in the same directory. Now, I get the terminal output shown under the makefile and do not really know what I am doing wrong. The linker is unable to put the whole thing together.

├── boot
│   ├── boot.s
│   └── grub.cfg
├── kmain.c
├── libc
│   ├── include
│   │   └── string.h
│   └── string.c
├── libk
│   ├── font.c
│   ├── include
│   │   ├── font.h
│   │   └── vga.h
│   └── vga.c
├── linker.ld
└── makefile

I use this makefile for the build:

AS = i686-elf-as
CC = i686-elf-gcc

CFLAGS = -std=gnu99 -ffreestanding -O2 -Wall -Wextra
LFLAGS = -ffreestanding -O2 -nostdlib

BUILD_DIR = ../build
LIBC_OBJ_DIR = $(BUILD_DIR)/libc
LIBK_OBJ_DIR = $(BUILD_DIR)/libk
BOOT_OBJ_DIR = $(BUILD_DIR)/boot
ISO_DIR = $(BUILD_DIR)/iso
KERNEL_DIR = $(BUILD_DIR)

ISO = release.iso
KERNEL = $(KERNEL_DIR)/kernel.bin

# Object files

LIBC_OBJ = \
    $(LIBC_OBJ_DIR)/string.o

LIBK_OBJ = \
    $(LIBK_OBJ_DIR)/vga.o

BOOT_OBJ = \
    $(BOOT_OBJ_DIR)/boot.o

KERNEL_OBJ = $(LIBK_OBJ_DIR)/kmain.o

.PHONY: iso
iso: kernel | $(ISO_DIR)
    grub-mkrescue -o $(BUILD_DIR)/$(ISO) $(ISO_DIR)

$(ISO_DIR):
    mkdir -p $(ISO_DIR)/boot/grub
    cp boot/grub.cfg $(ISO_DIR)/boot/grub/grub.cfg
    cp $(KERNEL) $(ISO_DIR)/boot/kernel.bin

.PHONY: kernel
kernel: libc libk kmain.c boot | $(KERNEL_DIR)
    $(CC) -c kmain.c -o $(KERNEL_OBJ) $(CFLAGS)
    $(CC) -T linker.ld -o $(KERNEL) $(LFLAGS) \
        $(wildcard $(LIBC_OBJ_DIR)/*) \
        $(wildcard $(LIBK_OBJ_DIR)/*) \
        $(KERNEL_OBJ) \
        $(wildcard $(BOOT_OBJ_DIR)/*) \
        -lgcc


$(KERNEL_DIR):
    mkdir -p $(KERNEL_DIR)

.PHONY: clean
clean:
    rm -r $(BUILD_DIR)/*

# Rules for libc

.PHONY: libc
libc: $(LIBC_OBJ)

$(LIBC_OBJ_DIR)/%.o: $(wildcard libc/*.c) | $(LIBC_OBJ_DIR)
    $(CC) -c $< -o $@ $(CFLAGS)

$(LIBC_OBJ_DIR):
    mkdir -p $(LIBC_OBJ_DIR)


# Rules for libk

.PHONY: libk
libk: $(LIBK_OBJ)

$(LIBK_OBJ_DIR)/%.o: $(wildcard libk/*.c) | $(LIBK_OBJ_DIR)
    $(CC) -c $< -o $@ $(CFLAGS)

$(LIBK_OBJ_DIR):
    mkdir -p $(LIBK_OBJ_DIR)


# Rules for boot

.PHONY: boot
boot: $(BOOT_OBJ_DIR)/boot.o

$(BOOT_OBJ_DIR)/%.o: $(wildcard boot/*.s) | $(BOOT_OBJ_DIR)
    $(AS) $< -o $@

$(BOOT_OBJ_DIR):
    mkdir -p $(BOOT_OBJ_DIR)

This is the output:

mkdir -p ../build/libc
i686-elf-gcc -c libc/string.c -o ../build/libc/string.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
mkdir -p ../build/libk
i686-elf-gcc -c libk/font.c -o ../build/libk/vga.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
mkdir -p ../build/boot
i686-elf-as boot/boot.s -o ../build/boot/boot.o
i686-elf-gcc -c kmain.c -o ../build/libk/kmain.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
i686-elf-gcc -T linker.ld -o ../build/kernel.bin -ffreestanding -O2 -nostdlib \
    ../build/libc/string.o \
    ../build/libk/vga.o \
    ../build/libk/kmain.o \
    ../build/boot/boot.o \
    -lgcc
/home/neon/tools/crc/lib/gcc/i686-elf/12.2.0/../../../../i686-elf/bin/ld: ../build/libk/kmain.o: in function `kmain':
kmain.c:(.text 0x20): undefined reference to `putstring'
collect2: error: ld returned 1 exit status
make: *** [makefile:58: kernel] Fehler 1

This is the kmain.c file, that causes the trouble. The strange thing is, importing the string.h header works just fine. You can call functions from there without any problem.

#if defined(__linux__)
#  error "This program needs to be build with a cross compiler"
#endif

#if !defined(__i386__)
#  error "You have to use a ix86-elf compiler!"
#endif

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

#include "libc/include/string.h"
#include "libk/include/vga.h"

void kmain(void) {
  // i just wanted to know, if these imports work
  uint16_t abc;
  memset16(&abc, 1, 1);

  // This does not work
  putstring(5, 2, "Hello, world!\0");
}

As you can see, the functions prototypes are here in the header vga.h.

#ifndef VGAFUNCTIONS_H
#  define VGAFUNCTIONS_H

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

#include "../../libc/include/string.h"

#define TBUFF 0xb8000
#define CHARS 80
#define LINES 25

#define vgacolor(fg, bg) ((fg) | ((bg) << 4))
#define vgachar(ch, color) ((ch) | ((color) << 8))
#define cursor(start, posx, posy) ((uint16_t*)((start)   ((posx) * CHARS * 2)   ((posx) * 2)))

extern void putchar(size_t posx, size_t posy, char ch);
extern void refresh(void);
extern void putstring(size_t posx, size_t posy, const char *str);
extern void clear(void);

#endif

This is the linker script:

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

I compiled the toolchain myself, but as I mentioned above, I know, that the build has to work, since it did before. Can anyone explain to me why the reference is broken now?

CodePudding user response:

You're looking in the wrong place. If it were the case that the header file was a problem, then the compile would fail because the declaration of the function could not be found.

But that's not what's failing. The compile of kmain.c works fine which means the compiler found the declarations of all the functions.

What's failing is the linking step. That means that all the header files are OK, but the object files that are being linked together are not correct. In that in your output you show, there is no line that compiles the vga.c file. The line that creates the vga.o file is actually compiling font.c instead:

i686-elf-gcc -c libk/font.c -o ../build/libk/vga.o ...

Obviously this is wrong: it means that the contents of vga.c are not present on your link line, just like the linker says.

This is because this rule is just not right in your makefile:

$(LIBK_OBJ_DIR)/%.o: $(wildcard libk/*.c) | $(LIBK_OBJ_DIR)
        $(CC) -c $< -o $@ $(CFLAGS)

Once make expands the function and variables, this rule becomes:

../build/libk/%.o: libk/font.c libk/vga.c | $(LIBK_OBJ_DIR)
        $(CC) -c $< -o $@ $(CFLAGS)

This tells make that it can build any file ../build/libk/%.o from BOTH source files, not EACH source file. And since the compile line uses $< which is always the FIRST source file, every time the compile runs it will compile the same source file: the first one, libk/font.c.

This rule should just be:

$(LIBK_OBJ_DIR)/%.o: libk/%.c | $(LIBK_OBJ_DIR)
        $(CC) -c $< -o $@ $(CFLAGS)

which says that the .o file is built from the corresponding .c file.

  • Related