Linux下最直接方式是遍历/proc/self/fd目录并用readlink()获取路径,需跳过非数字项及readlink失败的fd;Windows无等价机制,须调用未公开NTAPI且需调试权限,稳定性差;跨平台无通用解,推荐按需埋点或排他性探测。

Linux下用/proc/self/fd读取句柄最直接
在Linux上,/proc/self/fd是当前进程打开文件描述符的符号链接目录,每个数字子项对应一个fd号,指向实际打开的文件路径或设备。这是最轻量、无需额外权限的方式。
- 直接
opendir("/proc/self/fd")遍历目录项,对每个数字名称调用readlink()获取目标路径 - 注意跳过非数字项(如
.、..),并检查readlink()返回值是否为负——部分fd(如管道、socket、eventfd)可能无法解析出可读路径,会返回EINVAL或ENOENT - 不要依赖
readdir()顺序,fd编号不保证连续;有些fd可能已被close()但尚未被内核回收(极短暂窗口),readlink()会失败,需忽略ENOENT - 示例关键片段:
DIR* dir = opendir("/proc/self/fd"); struct dirent* ent; while ((ent = readdir(dir)) != nullptr) { if (isdigit(ent->d_name[0])) { char path[256]; ssize_t len = readlink(("/proc/self/fd/" + std::string(ent->d_name)).c_str(), path, sizeof(path)-1); if (len > 0) path[len] = '\0'; } }
Windows没有等价的/proc机制,得用NtQuerySystemInformation
Windows不暴露类似/proc的用户态接口,必须调用未公开的NTAPI:通过NtQuerySystemInformation传入SystemHandleInformation类,遍历当前进程所有句柄。这需要SeDebugPrivilege权限,且函数名不在ntdll.dll导出表中,得手动GetProcAddress。
- 先用
OpenProcess以PROCESS_QUERY_INFORMATION打开自身,再调用NtQuerySystemInformation——但注意:该API返回的是**全系统句柄表**,需过滤UniqueProcessId字段匹配当前GetCurrentProcessId() - 句柄类型(
ObjectTypeNumber)需查ntdll.dll中硬编码的类型数组(如File、Event、Section),不能靠字符串比较 - 即使拿到
HandleValue,也无法直接获取文件路径——对File类型句柄,还需用NtQueryObject配合ObjectNameInformation,且多数情况下仅返回设备路径(如\Device\HarddiskVolume1\foo.txt),需进一步转换为盘符路径 - 此方式稳定性差:微软不承诺
SystemHandleInformation结构体兼容性,Win11已部分失效,建议仅用于调试工具,生产环境慎用
跨平台方案只能妥协:只统计数量或按需探测
没有真正跨平台的“获取所有打开文件路径”方案。POSIX标准不定义此类接口,C++标准库更不提供。所谓跨平台库(如Boost.Process)也只封装fork/exec,不解决句柄枚举问题。
- 若只需句柄数量,可用
getrlimit(RLIMIT_NOFILE, &rlim)查上限,再暴力尝试fcntl(fd, F_GETFD)判断是否有效——但效率低,且无法区分“打开”和“已关闭但未回收” - 若目标是检测某特定文件是否被打开,应改用
flock()或open(..., O_EXCL)做排他性探测,而非枚举全部句柄 - 日志/诊断场景下,建议在
open()、fopen()、socket()等调用点自行埋点记录,比事后枚举可靠得多
容易被忽略的细节:/dev/stdin等伪路径和socket地址
/proc/self/fd/0通常指向/dev/pts/1或/dev/tty,但有时会是socket:[12345]或pipe:[67890]——这些不是文件路径,而是内核对象标识符。直接当文件路径处理会导致误判。
立即学习“C++免费学习笔记(深入)”;
-
socket:[...]表示网络或本地socket,可用getsockopt(fd, SOL_SOCKET, SO_DOMAIN, ...)进一步查AF类型,但无法还原监听地址 -
anon_inode:[eventfd]、inotify等内核匿名对象,readlink()返回的字符串纯属内核内部标记,无用户态意义 - 对
stat()结果要谨慎:某些fd指向/proc/self/fd/X本身(循环链接),stat()可能触发重入或返回错误
真实环境里,绝大多数需求其实只需要知道“某个文件是否被自己打开”,而不是完整列表。强行枚举不仅慢,还容易因race condition漏项或误报。把精力放在设计可追踪的资源生命周期,比逆向挖掘内核状态更可持续。


















