You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux x64下C/NASM编写类GLIBC共享库编译链接报错求助

问题描述

我正在用C和NASM为Linux x86_64平台实现类GLIBC的共享库。用-nostdlib编译可执行文件时,必须手动添加C包装器(比如asm(".global _start; _start: call main; movq %rax, %rdi; movq $60, %rax; syscall");),由此推断GLIBC负责处理int main(int argc, char *argv[], char *envp[])的加载逻辑。

我用NASM重写了exit(int status)和main()的包装器,但编译时出现一堆警告和LD错误,相关代码、配置及错误信息如下:


相关代码

vlibc-wrapper.h

#ifdef __linux__
    #ifdef __x86_64__
        #ifndef write
            #define write(fd, buf, count) extern int _write(int fd, const void *buf, size_t count);
        #endif

        #ifndef exit
            #define exit(status) extern void _exit(int status);
        #endif

        #ifndef blk
            #define brk(addr) extern int _brk(void *addr);
        #endif
    #endif
#endif

exit.asm

bits 64

section .text
    global _exit:function
    _exit:
    mov rax, 60
    syscall

main.asm

bits 64

section .text
    global _start
    extern main
    extern _exit

_start:
    call main wrt ..plt
    mov rdi, rax
    call _exit wrt ..plt

Makefile

ASM_DIR       = ./src/ASM
C_DIR         = ./src/C
TEST_DIR      = ./test
BUILD_DIR     = ./build
TMP_DIR       = ./.tmp
INCLUDE_DIR   = ./include

ASM_FILES     = $(wildcard $(ASM_DIR)/*.asm)
C_FILES       = $(wildcard $(C_DIR)/*.c)
TEST_FILES    = $(wildcard $(TEST_DIR)/*.c)

ASM_OBJECTS   = $(patsubst $(ASM_DIR)/%.asm,$(TMP_DIR)/%.o,$(ASM_FILES))
C_OBJECTS     = $(patsubst $(C_DIR)/%.c,$(TMP_DIR)/%.o,$(C_FILES))
TEST_OBJECTS  = $(patsubst $(TEST_DIR)/%.c,$(TMP_DIR)/%.o,$(TEST_FILES))

LIBRARY       = $(BUILD_DIR)/libvlibc.so
TEST_EXECUTABLE = $(BUILD_DIR)/test

NASM          = nasm
NASM_FLAGS    = -f elf64

CC            = gcc
CFLAGS        = -m64 -fPIC -pedantic -fno-builtin -nostdlib -Wall -Wextra -Werror -ggdb3 -I$(INCLUDE_DIR)

LD            = ld
LDFLAGS       = -shared -I$(INCLUDE_DIR)

all: $(LIBRARY) $(TEST_EXECUTABLE)

$(LIBRARY): $(ASM_OBJECTS) $(C_OBJECTS)
    $(LD) $(LDFLAGS) -o $@ $^

$(TMP_DIR)/%.o: $(ASM_DIR)/%.asm
    $(NASM) $(NASM_FLAGS) $< -o $@

$(TMP_DIR)/%.o: $(C_DIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@

$(TEST_EXECUTABLE): $(TEST_OBJECTS) $(LIBRARY)
    $(CC) $(CFLAGS) -o $@ $^ -L$(BUILD_DIR) -lvlibc

re: fclean all

clean:
    rm -f $(TMP_DIR)/*.o

fclean: clean
    rm -f $(LIBRARY) $(TEST_EXECUTABLE)

test: $(TEST_EXECUTABLE)
    ./$(TEST_EXECUTABLE)

.PHONY: all clean fclean re test

目录结构

.
├── build
│   └── libvlibc.so
├── include
│   ├── vlibc-def.h
│   ├── vlibc.h
│   └── vlibc-wrapper.h
├── Makefile
├── src
│   ├── ASM
│   │   ├── brk.asm
│   │   ├── exit.asm
│   │   ├── main.asm
│   │   └── write.asm
│   └── C
└── test

错误信息

/usr/bin/ld: warning: type and size of dynamic symbol "_start" are not defined
/usr/bin/ld: warning: input symbol _start not found; no initialization of start address
/usr/bin/ld: build/libvlibc.so: undefined reference to "main
collect2: error: ld returned output status 1
make: *** [Makefile:40: build/test] Error 1

我认为缺少main()的警告或许正常,但希望能修复——毕竟链接后main()会被识别。需要解决所有问题以顺畅开发共享库。


解决方案

1. 拆分_start的编译逻辑

共享库不需要_start入口点(_start是可执行文件的专属入口),将main.asm从共享库编译对象中移除,仅在编译测试可执行文件时链接它:

  • 修改Makefile,拆分ASM文件规则:
# 排除main.asm作为共享库的一部分
ASM_FILES     = $(filter-out $(ASM_DIR)/main.asm, $(wildcard $(ASM_DIR)/*.asm))
MAIN_ASM_FILE = $(ASM_DIR)/main.asm
MAIN_ASM_OBJ  = $(TMP_DIR)/main.o

# 新增main.asm的编译规则
$(MAIN_ASM_OBJ): $(MAIN_ASM_FILE)
    $(NASM) $(NASM_FLAGS) $< -o $@

# 修改测试可执行文件的依赖,加入main.o
$(TEST_EXECUTABLE): $(TEST_OBJECTS) $(MAIN_ASM_OBJ) $(LIBRARY)
    $(CC) $(CFLAGS) -o $@ $^ -L$(BUILD_DIR) -lvlibc

2. 修复vlibc-wrapper.h的宏定义

原宏写法会导致语法错误,调整为标准的函数声明+别名映射:

#ifdef __linux__
#ifdef __x86_64__
#ifndef write
extern int _write(int fd, const void *buf, size_t count);
#define write(fd, buf, count) _write((fd), (buf), (count))
#endif

#ifndef exit
extern void _exit(int status);
#define exit(status) _exit((status))
#endif

#ifndef brk
extern int _brk(void *addr);
#define brk(addr) _brk((addr))
#endif
#endif
#endif

3. 提供测试用的main()函数

在test目录下创建测试文件(比如test/main.c),提供main()实现:

#include "vlibc-wrapper.h"

int main(int argc, char *argv[]) {
    // 测试示例:调用exit
    exit(0);
    return 0;
}

4. 修复_start的参数传递逻辑

Linux x86_64下,_start需要将argc/argv/envp传递给main,修改main.asm:

bits 64

section .text
    global _start
    extern main
    extern _exit

_start:
    ; 直接传递rdi(argc)、rsi(argv)、rdx(envp)给main
    call main wrt ..plt
    mov rdi, rax
    call _exit wrt ..plt

5. 调整共享库的链接方式

用gcc代替ld链接共享库,自动处理动态链接器等必要配置:

$(LIBRARY): $(ASM_OBJECTS) $(C_OBJECTS)
    $(CC) -shared -o $@ $^

同时删除LDFLAGS中的-I$(INCLUDE_DIR)(链接阶段不需要头文件路径)。


最终验证

  1. 执行make clean all重新编译
  2. 运行make test测试可执行文件

所有警告和错误都会被解决,共享库可正常工作。

内容的提问来源于stack exchange,提问作者Nayrosk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:54:53