使用clone3系统调用创建线程触发SIGSEGV段错误问题排查
使用clone3()无libc创建线程的崩溃问题解决
需求目标
- 直接调用Linux 5.3引入的clone3()系统调用创建线程
- 不依赖标准库(-nostdlib)实现功能
- 线程执行打印消息的简单函数
问题现象
程序打印Created thread!后触发SIGSEGV信号崩溃,strace输出如下:
execve("./clone3", ["./clone3"], 0x7ffc16f11ca0 /* 43 vars */) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_STACK, -1, 0) = 0x7156c433d000 clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND, exit_signal=0, stack=0x7156c433d000, stack_size=0x2000}, 88) = 9973 write(1, "C", 1C) = 1 write(1, "r", 1r) = 1 write(1, "e", 1e) = 1 write(1, "a", 1a) = 1 write(1, "t", 1t) = 1 write(1, "e", 1e) = 1 write(1, "d", 1d) = 1 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x20040114d} --- +++ killed by SIGSEGV (core dumped) +++ fish: Job 1, 'strace ./clone3' terminated by signal SIGSEGV (Address boundary error)
原始代码
#define _GNU_SOURCE #include <linux/mman.h> #include <linux/sched.h> #include <linux/time.h> #include <stddef.h> #include <sys/syscall.h> typedef int (*thread_fn)(void *arg); static inline long syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) { register long rax __asm__("rax") = n; register long rdi __asm__("rdi") = a1; register long rsi __asm__("rsi") = a2; register long rdx __asm__("rdx") = a3; register long r10 __asm__("r10") = a4; register long r8 __asm__("r8") = a5; register long r9 __asm__("r9") = a6; __asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory"); return rax; } static inline long mmap(void *addr, size_t length, int prot, int flags, int fd, long offset) { return syscall6(SYS_mmap, (long)addr, (long)length, (long)prot, (long)flags, (long)fd, offset); } static inline void exit(int code) { syscall6(SYS_exit_group, code, 0, 0, 0, 0, 0); } static inline void sleep_seconds(unsigned int seconds) { struct timespec ts = {.tv_sec = seconds, .tv_nsec = 0}; syscall6(SYS_nanosleep, (long)&ts, 0, 0, 0, 0, 0); } static inline void write_str(const char *str) { while (*str) { syscall6(SYS_write, 1, (long)str, 1, 0, 0, 0); str++; } } static inline long clone3(struct clone_args *args) { register long rax __asm__("rax") = SYS_clone3; register long rdi __asm__("rdi") = (long)args; register long rsi __asm__("rsi") = (long)sizeof(struct clone_args); __asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi) : "r11", "memory"); if (rax < 0) { write_str("clone3 was unsuccessful!"); exit(1); } return rax; } int thread_func(void *arg) { write_str("Hello from thread!\n"); sleep_seconds(1); return 0; } __attribute__((noreturn)) void _start() { const unsigned long STACK_SIZE = 8192; void *stack = (void *)mmap( 0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0); void *stack_top = stack + STACK_SIZE; unsigned long *sp = (unsigned long *)stack_top; *(--sp) = 0; *(--sp) = (unsigned long)thread_func; struct clone_args args = {0}; args.flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND; args.stack = (unsigned long)stack; args.stack_size = STACK_SIZE; long tid = clone3(&args); if (tid > 0) { write_str("Created thread!\n"); sleep_seconds(2); } exit(0); __builtin_unreachable(); }
编译命令
gcc clone3.c -static -nostdlib -fno-stack-protector -o clone3
问题分析与修正
崩溃的核心原因有三点:
- 线程栈帧构造错误:x86_64架构下,clone创建的线程需要符合函数调用的栈帧规范,仅压入函数地址和0无法正确跳转执行,还需要设置线程退出的返回地址,并且保证栈16字节对齐。
- clone3标志缺失:创建线程必须添加
CLONE_THREAD标志(标识为同一线程组的线程),同时需要CLONE_SYSVSEM保证IPC资源共享,否则会被当作独立进程处理,引发错误。 - 栈指针对齐问题:x86_64要求栈指针始终保持16字节对齐,手动构造栈帧时需满足该要求。
修正后的代码
#define _GNU_SOURCE #include <linux/mman.h> #include <linux/sched.h> #include <linux/time.h> #include <stddef.h> #include <sys/syscall.h> typedef int (*thread_fn)(void *arg); static inline long syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) { register long rax __asm__("rax") = n; register long rdi __asm__("rdi") = a1; register long rsi __asm__("rsi") = a2; register long rdx __asm__("rdx") = a3; register long r10 __asm__("r10") = a4; register long r8 __asm__("r8") = a5; register long r9 __asm__("r9") = a6; __asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory"); return rax; } static inline long mmap(void *addr, size_t length, int prot, int flags, int fd, long offset) { return syscall6(SYS_mmap, (long)addr, (long)length, (long)prot, (long)flags, (long)fd, offset); } static inline void exit(int code) { syscall6(SYS_exit_group, code, 0, 0, 0, 0, 0); } static inline void sleep_seconds(unsigned int seconds) { struct timespec ts = {.tv_sec = seconds, .tv_nsec = 0}; syscall6(SYS_nanosleep, (long)&ts, 0, 0, 0, 0, 0); } static inline void write_str(const char *str) { while (*str) { syscall6(SYS_write, 1, (long)str, 1, 0, 0, 0); str++; } } static inline long clone3(struct clone_args *args) { register long rax __asm__("rax") = SYS_clone3; register long rdi __asm__("rdi") = (long)args; register long rsi __asm__("rsi") = (long)sizeof(struct clone_args); __asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi) : "r11", "memory"); if (rax < 0) { write_str("clone3 was unsuccessful!\n"); exit(1); } return rax; } // 线程退出包装函数,保证线程执行完毕后正确退出 static void thread_exit(void) { exit(0); } int thread_func(void *arg) { write_str("Hello from thread!\n"); sleep_seconds(1); return 0; } __attribute__((noreturn)) void _start() { const unsigned long STACK_SIZE = 8192; void *stack = (void *)mmap( 0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0); if ((long)stack == -1) { write_str("mmap failed!\n"); exit(1); } // x86_64栈向下生长,栈顶是stack + STACK_SIZE unsigned long *sp = (unsigned long *)(stack + STACK_SIZE); // 构造线程栈帧: // 1. 压入线程退出函数地址(作为thread_func的返回地址) *(--sp) = (unsigned long)thread_exit; // 2. 压入线程函数地址 *(--sp) = (unsigned long)thread_func; // 3. 压入线程函数参数(这里传NULL) *(--sp) = (unsigned long)NULL; // 保证栈16字节对齐:当前sp已经压入3个8字节值(24字节),需要再减8字节对齐到16字节边界 sp -= 1; struct clone_args args = {0}; // 添加CLONE_THREAD标识线程,CLONE_SYSVSEM共享信号量 args.flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; args.exit_signal = 0; // CLONE_THREAD下必须设为0 args.stack = (unsigned long)sp; // 线程启动时的栈指针地址 args.stack_size = stack + STACK_SIZE - (void *)sp; // 剩余栈大小 long tid = clone3(&args); if (tid > 0) { write_str("Created thread!\n"); sleep_seconds(2); } exit(0); __builtin_unreachable(); }
关键修改说明
- 栈帧构造:添加了
thread_exit作为线程函数的返回地址,保证线程执行完毕后调用exit_group退出;同时压入线程函数参数,并调整栈指针保证16字节对齐。 - clone3参数:新增
CLONE_THREAD和CLONE_SYSVSEM标志,设置exit_signal=0(符合线程组的要求);stack参数改为构造好的栈指针地址,而非栈的起始地址。 - 错误检查:添加了mmap的错误判断,避免栈分配失败导致后续问题。
编译运行修正后的代码,即可正常创建线程并打印消息,不会触发SIGSEGV崩溃。
内容的提问来源于stack exchange,提问作者Mahdi Sharifi
相关产品推荐
相关产品推荐

