近来鲜有闲暇,长一点的文章,写着写着便搁置了。那就写点轻松的话题,聊聊这两天遇到的坑吧
问题起因
最近在开发一个编辑器组件的时候,需要调用nm,从静态库中查找符号所在的.o
代码很公式话,这里就不贴了
C# 创建Process,设置 Process.StartInfo:
UseShellExecute = false
FileName = <Path to llvm-nm>
因为要读取输出,还设置了 RedirectStandardOutput = true
从 StandardOutput 上读到了 null,什么输出也没有,子进程便退出了。
检查了 ExitCode 不为0,代表有错误。
Step 1. 缩小问题范围
把同样的代码,贴到VS工程运行没有问题 √
同一份代码,控制台用mono执行也没有问题 √
通过 Process 启动 llvm-nm 失败的情况只出现 Unity3D 下,它到底哪里不一样导致了调用失败?
Step 2. 检查 stderr
既然遇到了错误退出,作为一个严谨的控制台程序 StandardError 总会有些输出吧。重定向了StandardError 得到的信息 Bad file descriptor,错误的文件描述符。
立即闪现的念头是llvm-nm是否依赖于调用其他文件所以导致失败? 经过测试排除了这种可能,所以决定还是编译调试版的nm跟一下流程吧。
Step 3. 调试LLVM-NM
![ea81033bfaf67a65c63ccd82d0a22f68.png](https://i-blog.csdnimg.cn/blog_migrate/6ea2d2f492dc168b727586c396c6c7df.jpeg)
进程一启动遍退出了,sys::ChangeStdinToBinary() 失败了, 那就看下它的代码实现。
Windows的实现是这样的:
![343400439dbff94cfc7f945b00cd5b38.png](https://i-blog.csdnimg.cn/blog_migrate/a251ee12156b1f0ffff65c5fc142cc19.jpeg)
_setmode 失败了,失败的原始是当前的stdin是无效的。于是问题便成为了stdin为什么是无效的?
Step 4. 查阅Mono代码
启动进程的代码定位到 mcs/class/System/System.Diagnostics/Process.cs
函数 bool StartWithCreateProcess (ProcessStartInfostartInfo) 其中关于stdin的设置是这样的:
![18a1dc33d6bb7b2214d09197cd72efed.png](https://i-blog.csdnimg.cn/blog_migrate/b78cfece0ae49e14cf7279787f8ed653.jpeg)
如果启用stdin重定向,就创建管道,设置给子进程。
如果没有启动,则使用当前的 MonoIO.ConsoleInput。
那么 MonoIO.ConsoleInput 的值是怎么来的呢?
MonoIO.ConsoleInput 只有get方法,并且是internal call,
mono/metadata/icall-def.h 中定义了:
ICALL(MONOIO_28, "get_ConsoleInput", ves_icall_System_IO_MonoIO_get_ConsoleInput)
![39f8fd83b1530e3568de22563c84761b.png](https://i-blog.csdnimg.cn/blog_migrate/c5c11f7aae6d2ce9d79c838607870ad1.jpeg)
![76844be427120f08f37e81f4a414a365.png](https://i-blog.csdnimg.cn/blog_migrate/af46e8d0abc8b1608c2916c28721c32c.jpeg)
最终调用了 Windows的API GetStdHandle ,于是进一步确认:在 Unity 中通过P/Invoke调用 GetStdHandle 得到了返回 0,即 NULL。文档中如下写道:
If an application does not have associated standard handles, such as a service running on an interactive desktop, and has not redirected them, the return value is NULL.
到这里,问题的起因就查清了。
总结
Unity 没有关联 stdin, stdout, stderr,它创建的子进程也受此影响,导致llvm-nm设置stdin的转化模式失败,进而引发错误退出。
解决方案
知道了原因,问题就很好解决了,在Unity中启动 llvm-nm 时启用 stdin 重定向,规避stdin setmode失败。