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

使用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

问题分析与修正

崩溃的核心原因有三点:

  1. 线程栈帧构造错误:x86_64架构下,clone创建的线程需要符合函数调用的栈帧规范,仅压入函数地址和0无法正确跳转执行,还需要设置线程退出的返回地址,并且保证栈16字节对齐。
  2. clone3标志缺失:创建线程必须添加CLONE_THREAD标志(标识为同一线程组的线程),同时需要CLONE_SYSVSEM保证IPC资源共享,否则会被当作独立进程处理,引发错误。
  3. 栈指针对齐问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:07:03