重构OS源码后Makefile出现putstring未定义引用问题求助
在小型操作系统开发中,重构源码目录后使用Makefile编译出现putstring函数未定义引用的链接错误。此前所有文件在同一目录时编译正常,当前目录分为boot、libc、libk等模块,kmain.c调用putstring(size_t posx, size_t posy, const char *str)时触发错误。
目录结构
├── 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
编译错误输出
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
从编译日志能看到关键错误:i686-elf-gcc -c libk/font.c -o ../build/libk/vga.o —— Makefile错误地将libk/font.c编译成了vga.o,而实际包含putstring实现的libk/vga.c完全没被编译。
问题出在libk的编译规则:
$(LIBK_OBJ_DIR)/%.o: $(wildcard libk/*.c) | $(LIBK_OBJ_DIR) $(CC) -c $< -o $@ $(CFLAGS)
这里用$(wildcard libk/*.c)作为依赖,会展开为libk/font.c libk/vga.c,而$<代表第一个依赖文件(即libk/font.c)。当Make尝试生成vga.o时,会用font.c编译,直接跳过vga.c,导致putstring的实现没有被包含到目标文件中。
修改Makefile中libc和libk的编译规则,让每个.c文件对应生成同名的.o文件,替换原来的wildcard批量匹配逻辑。
修正后的libc规则
# Rules for libc .PHONY: libc libc: $(LIBC_OBJ) $(LIBC_OBJ_DIR)/%.o: libc/%.c | $(LIBC_OBJ_DIR) $(CC) -c $< -o $@ $(CFLAGS) $(LIBC_OBJ_DIR): mkdir -p $(LIBC_OBJ_DIR)
修正后的libk规则
首先更新LIBK_OBJ变量,加入font.o的依赖:
LIBK_OBJ = \ $(LIBK_OBJ_DIR)/vga.o \ $(LIBK_OBJ_DIR)/font.o
再修改编译规则:
# Rules for libk .PHONY: libk libk: $(LIBK_OBJ) $(LIBK_OBJ_DIR)/%.o: libk/%.c | $(LIBK_OBJ_DIR) $(CC) -c $< -o $@ $(CFLAGS) $(LIBK_OBJ_DIR): mkdir -p $(LIBK_OBJ_DIR)
更通用的优化方案
可以自动收集libk下的所有.c文件并生成对应.o文件,避免手动维护依赖:
# 自动收集libk下的所有.c文件 LIBK_SRC := $(wildcard libk/*.c) LIBK_OBJ := $(patsubst libk/%.c, $(LIBK_OBJ_DIR)/%.o, $(LIBK_SRC)) .PHONY: libk libk: $(LIBK_OBJ) $(LIBK_OBJ_DIR)/%.o: libk/%.c | $(LIBK_OBJ_DIR) $(CC) -c $< -o $@ $(CFLAGS)
后续新增.c文件时,无需手动修改Makefile,会自动编译对应的.o文件。
重新执行make kernel,编译日志会正确显示:
i686-elf-gcc -c libk/vga.c -o ../build/libk/vga.o ... i686-elf-gcc -c libk/font.c -o ../build/libk/font.o ...
此时vga.o中包含了putstring的实现,链接时不会再出现未定义引用错误。
内容的提问来源于stack exchange,提问作者mju

