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

sys_clone调用后的汇编代码为何影响克隆进程运行?

x86 Intel无前缀汇编线程实现故障排查与修复

问题描述

我正在用x86 Intel无前缀汇编编写编译器,实现线程时遇到两个问题:

  1. 执行第一段代码时,新线程能正常运行但会触发段错误;
  2. 如果在这段代码后添加第二段保存返回值的代码,新线程完全无法运行。

第一段触发段错误的代码

call create_thread_stack
push rax                            # Push f onto new stack, followed by thread_wrapper
pop rsi                             # New thread should start in thread_wrapper when cloned
sub rsi, 8
lea rdi, [rip + f]
push rdi
pop [rsi]
sub rsi, 8
lea rdi, [rip + thread_wrapper]
push rdi
pop [rsi]
mov rdi, -2147479808    # CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_IO
mov rax, 56             # Sys_clone
syscall

导致新线程无法运行的追加代码

sub rsp, 4
mov dword ptr [rsp], eax
mov eax, dword ptr [rsp]
add rsp, 4
mov dword ptr [rbp + -4], eax
mov rax, 0
mov rsp, rbp
pop rbp
ret

完整原始代码

.intel_syntax noprefix 
.globl main 
.section .rodata 
.text
f:                        # prints 1 and returns 0
    push rbp
    mov rbp, rsp
    mov eax, 1
    sub rsp, 4
    mov dword ptr [rsp], eax
    mov edi, dword ptr [rsp]
    add rsp, 4
    movsx rdi, edi
    call _printi
    mov eax, 0
    sub rsp, 4
    mov dword ptr [rsp], eax
    mov eax, dword ptr [rsp]
    add rsp, 4
    movsx rax, eax
    mov rsp, rbp
    pop rbp
    ret
main: 
    push rbp
    mov rbp, rsp
    sub rsp, 4
    mov dword ptr [rsp], eax
    call create_thread_stack
    push rax                            # Push f onto new stack, followed by thread_wrapper
    pop rsi                             # New thread should start in thread_wrapper when cloned
    sub rsi, 8
    lea rdi, [rip + f]
    push rdi
    pop [rsi]
    sub rsi, 8
    lea rdi, [rip + thread_wrapper]
    push rdi
    pop [rsi]
    mov rdi, -2147479808    # CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_IO
    mov rax, 56             # Sys_clone
    syscall
    sub rsp, 4
    mov dword ptr [rsp], eax
    mov eax, dword ptr [rsp]
    add rsp, 4
    mov dword ptr [rbp + -4], eax
    mov rax, 0
    mov rsp, rbp
    pop rbp
    ret
.section .rodata
    .int 2
.L._printi_str0: 
    .asciz "%d"
.text
_printi: 
    push rbp
    mov rbp, rsp
    and rsp, -16
    mov esi, edi
    lea rdi, [rip + .L._printi_str0]
    mov al, 0
    call printf@plt
    mov rdi, 0
    call fflush@plt
    mov rsp, rbp
    pop rbp
    ret
create_thread_stack:           # Creates stack for new thread
    mov rdi, 0
    mov rsi, 4194304
    mov rdx, 3
    mov r10, 290
    mov r9, 0
    mov r8, -1
    mov rax, 9
    syscall
    lea rax, [rax + 4194304]
    ret
thread_exit: 
    mov rdi, 0
    mov rax, 60
    syscall
thread_wrapper:                # Runs label below thread_wrapper on the new stack
    pop rax
    call rax
    call thread_exit

核心错误分析

  1. 对clone系统调用逻辑的误解:子线程不会自动从构造的栈地址启动,而是和父进程一样从syscall的下一条指令继续执行,需手动判断返回值引导子线程进入目标入口。
  2. clone参数传递不完整:未设置parent_tidptr、child_tidptr等参数,垃圾值可能导致异常;同时缺少CLONE_THREAD标记,子线程会被当作独立进程而非线程。
  3. 父进程提前退出:追加代码让main函数直接返回,导致整个进程终止,子线程还未执行就被杀死。
  4. 栈构造与使用不匹配:即使子线程跳转到thread_wrapper,也未切换到构造的新栈,仍使用父进程栈执行。

修复步骤

1. 正确传递clone参数并区分父子线程

clone系统调用的x86_64参数顺序为:flags(rdi)、child_stack(rsi)、parent_tidptr(rdx)、child_tidptr(r10)、tls(r8)。需补充参数并添加CLONE_THREAD标记,同时在syscall后判断返回值,引导子线程进入目标入口。

2. 让父进程等待子线程完成

使用waitpid系统调用(rax=61),避免父进程提前退出导致子线程被终止。

3. 修正栈构造逻辑

确保子线程使用新栈时,栈顶内容符合thread_wrapper的执行预期:栈顶放置f的地址,让thread_wrapper弹出后调用。

修复后的完整代码

.intel_syntax noprefix 
.globl main 
.section .rodata 
.text
f:                        # prints 1 and returns 0
    push rbp
    mov rbp, rsp
    mov eax, 1
    sub rsp, 4
    mov dword ptr [rsp], eax
    mov edi, dword ptr [rsp]
    add rsp, 4
    movsx rdi, edi
    call _printi
    mov eax, 0
    sub rsp, 4
    mov dword ptr [rsp], eax
    mov eax, dword ptr [rsp]
    add rsp, 4
    movsx rax, eax
    mov rsp, rbp
    pop rbp
    ret
main: 
    push rbp
    mov rbp, rsp
    sub rsp, 4
    mov dword ptr [rsp], eax
    call create_thread_stack
    # 构造新栈:栈顶放置f的地址,供thread_wrapper弹出调用
    mov rsi, rax
    sub rsi, 8
    lea rdi, [rip + f]
    mov [rsi], rdi

    # 设置clone参数
    mov rdi, -2147479808 | 0x10000  # 加上CLONE_THREAD标记
    mov rsi, rsi                    # 子线程栈指针
    mov rdx, 0                      # parent_tidptr设为0
    mov r10, 0                      # child_tidptr设为0
    mov r8, 0                       # tls设为0
    mov rax, 56                     # Sys_clone
    syscall

    # 区分父子线程
    cmp eax, 0
    je child_thread_entry

    # 父进程逻辑:保存子线程PID并等待其结束
    mov dword ptr [rbp-4], eax
    mov rdi, eax                    # 子线程PID
    mov rsi, 0                      # status指针设为0
    mov rdx, 0                      # options设为0
    mov rax, 61                     # waitpid syscall
    syscall

    # 父进程退出
    mov rax, 0
    mov rsp, rbp
    pop rbp
    ret

child_thread_entry:
    # 子线程跳转到thread_wrapper执行
    jmp thread_wrapper

.section .rodata
    .int 2
.L._printi_str0: 
    .asciz "%d"
.text
_printi: 
    push rbp
    mov rbp, rsp
    and rsp, -16
    mov esi, edi
    lea rdi, [rip + .L._printi_str0]
    mov al, 0
    call printf@plt
    mov rdi, 0
    call fflush@plt
    mov rsp, rbp
    pop rbp
    ret
create_thread_stack:           # Creates stack for new thread
    mov rdi, 0
    mov rsi, 4194304
    mov rdx, 3                     # PROT_READ | PROT_WRITE
    mov r10, 290                   # MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN
    mov r9, 0
    mov r8, -1
    mov rax, 9                     # mmap syscall
    syscall
    lea rax, [rax + 4194304]       # 返回栈顶地址(栈向下增长)
    ret
thread_exit: 
    mov rdi, 0
    mov rax, 231                   # 使用exit_group系统调用,更适合线程退出
    syscall
thread_wrapper:                # Runs label below thread_wrapper on the new stack
    pop rax
    call rax
    call thread_exit

额外优化点

线程退出建议使用exit_group系统调用(rax=231),而非exit,能确保整个线程组的资源被正确清理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 06:04:54