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

C# .NET 10 Linux环境下调用forkpty后子进程触发SIGSEGV的问题排查求助

在Kubuntu 25.10上用C# .NET 10调用forkpty时子进程触发SIGSEGV的问题

背景与问题概述

我正在Kubuntu 25.10系统里用C# .NET 10开发一款终端模拟器,过程中需要调用openpty API获取PTY主/从文件描述符对,接着通过fork创建子进程,把这些描述符复制到STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,最后执行目标程序。

摸索的时候我发现forkpty函数能自动完成大部分相关工作,子进程只要在forkpty返回后执行exec就行。但在C#里尝试这一操作时,子进程会在forkpty返回与exec调用之间触发SIGSEGV段错误。

已尝试的预热方案

我曾推测可能是P/Invoke调用初始为存根,存根解析需要的操作超出了浅度fork的范畴,因此在forkpty前先调用execvp进行预热(传入空文件参数使其快速返回错误),但仍会立即触发SIGSEGV。

通过strace输出可以看到,forkpty已完成替换标准输入、输出、错误文件描述符等操作,且我确信SIGSEGV并非发生在forkpty内部(这一点也得到了回溯信息的佐证)。此外,该错误也并非由execvp调用触发:若完全注释掉forkpty调用,直接让父进程执行exec,程序可正常替换并运行bash(仅PTY仍处于规范模式)。

测试代码

var term = new termios() { ... /* 一系列标志位 */ };
var win = new winsize() { ... /* 80x25字符,640x400像素 */ };
string filePath = "/usr/bin/bash";
nint fileNamePtr = Marshal.StringToHGlobalAnsi(filePath);
nint argvPtr = Marshal.AllocHGlobal(2 * nint.Size);
Marshal.WriteIntPtr(argvPtr, 0, Marshal.StringToHGlobalAnsi(Path.GetFileName(filePath)));
Marshal.WriteIntPtr(argvPtr, nint.Size, 0);
nint emptyString = argvPtr + nint.Size; // 已在此地址写入0
int result = execvp(emptyString, 0);
Console.WriteLine("Warm-up result: {0}", result);
sleep(0); // 同样用于预热
int childPID = forkpty( out int masterFD, name: null, ref term, ref win);
if (childPID == 0) {
    // 验证子进程代码是否运行——SIGSEGV在sleep返回前就已触发
    //sleep(15);
    execvp(fileNamePtr, argvPtr);
}
Console.WriteLine("Child PID is: {0}", childPID);

运行时现象

  • 输出Warm-up result: -1,表明execvp预热调用成功(因传入空文件参数而失败);
  • 父进程输出Child PID is: 12345,但strace显示子进程在该输出前已接收SIGSEGV,且段错误发生在进入if分支代码之前;
  • 为排除execvp引发异常的可能,我尝试添加sleep,但子进程在fork返回后立即触发SIGSEGV。

调试困境

我已捕获核心转储,但由于捕获时执行的是JIT输出的代码,转储信息实用性不高。将核心转储加载到GDB后,回溯仅为一系列不存在的内存地址,且回溯中未出现forkpty或execvp。我尝试配置COMPlus迷你转储,也尝试将/proc/self/coredump_filter设为0xff以让系统生成包含完整信息的转储(文件大小翻倍),但GDB仍无法识别代码。

我通过sharplab.io获取了x64目标的汇编输出,仅能看到forkpty代码处的一个调用地址,随后是execvp的参数设置和调用。我理解这些调用指向按需解析的蹦床,且首次调用后蹦床会替换为直接调用目标函数,但我无法验证这一认知是否正确。基于这一JIT解析策略,我尝试在fork前对P/Invoke端点进行预热调用,但并未解决问题。

补充:通过sharplab.io获取的x64目标汇编代码:

int childPID = forkpty( out int masterFD, name: null, ref term, ref win);
L0285: lea r8, [rbp-0x30] ; 从本地变量加载参数2
L0289: lea rcx, [rbp-0x60] ; 从本地变量加载参数0
L028d: lea r9, [rbp-0x40] ; 从本地变量加载参数3
L0291: xor edx, edx ; 参数1 == NULL
L0293: call 0x00007ffe91060030 ; 调用forkpty的P/Invoke蹦床
L0298: mov [rbp-0xe8], eax
L029e: mov eax, [rbp-0xe8] ; 我不清楚此行存在的原因
L02a4: mov [rbp-0x54], eax ; 保存返回值的内容

疑问求助

请问可能的原因是什么?我该如何进一步调试?我可以整理测试代码并上传至代码仓库,若有需要请告知。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 06:38:46