使用Go语言sys/windows库正确获取Windows句柄的技术求助
解决Go调用NtQuerySystemInformation访问句柄数组崩溃及NT_SUCCESS宏实现问题
一、访问句柄数组崩溃的原因及修复
你遇到的exit status 0xc0000374是堆损坏错误,根源在于结构体定义不符合Windows内核返回的数据格式:
Go的slice在内存中是包含指针、长度、容量的三元组,但NtQuerySystemInformation返回的SystemExtendedHandleInformation数据结构,是在NumberOfHandles字段后直接紧跟连续的SystemHandleTableEntryInfo结构体数组,没有slice的元数据。直接强转成带Handles slice的结构体,会导致slice指针指向非法内存,触发内存访问错误。
修复步骤:
- 修改结构体定义,去掉slice字段,只保留基础结构:
type SystemHandleTableEntryInfo struct { UniqueProcessId uint16 CreatorBackTraceIndex uint16 ObjectTypeIndex uint8 HandleAttributes uint8 HandleValue uint16 Object unsafe.Pointer GrantedAccess uint32 } // 去掉Handles切片,因为内核返回的内存中没有slice元数据 type SystemHandleInformation struct { NumberOfHandles uint32 }
- 手动计算内存偏移遍历句柄数组:
通过unsafe包计算每个结构体的内存地址,直接访问内核返回的连续数据:
handleInfoPtr := unsafe.Pointer(handleInfo) sysInfo := (*SystemHandleInformation)(handleInfoPtr) numHandles := sysInfo.NumberOfHandles // 计算单个句柄条目的内存大小 entrySize := unsafe.Sizeof(SystemHandleTableEntryInfo{}) // 遍历所有句柄 for i := uint32(0); i < numHandles; i++ { // 计算当前条目的内存地址:起始地址 + 头部大小 + 当前索引*条目大小 entryPtr := unsafe.Pointer( uintptr(handleInfoPtr) + unsafe.Sizeof(SystemHandleInformation{}) + uintptr(i)*entrySize, ) entry := (*SystemHandleTableEntryInfo)(entryPtr) // 安全访问字段 fmt.Printf("PID: %d, Handle值: 0x%x, 内核对象地址: 0x%x\n", entry.UniqueProcessId, entry.HandleValue, entry.Object) }
二、修正NT_SUCCESS宏的实现
Windows原生NT_SUCCESS宏的逻辑是判断状态码>=0(NT状态码中0和正数为成功,负数为错误),你的实现只覆盖了STATUS_SUCCESS和STATUS_PENDING,不够全面(比如STATUS_BUFFER_TOO_SMALL也是需要处理的成功状态)。
正确实现:
import "syscall" func NtSuccess(status error) bool { if status == nil { return true } ntStatus, ok := status.(syscall.Errno) if !ok { return false } // NT状态码是32位值,高位为0表示成功(>=0) return int32(ntStatus) >= 0 }
三、完整可运行示例
包含动态缓冲区扩容(第一次调用可能返回缓冲区不足,需根据返回的retlength重新分配内存):
package main import ( "fmt" "unsafe" "syscall" "golang.org/x/sys/windows" ) type SystemHandleTableEntryInfo struct { UniqueProcessId uint16 CreatorBackTraceIndex uint16 ObjectTypeIndex uint8 HandleAttributes uint8 HandleValue uint16 Object unsafe.Pointer GrantedAccess uint32 } type SystemHandleInformation struct { NumberOfHandles uint32 } func NtSuccess(status error) bool { if status == nil { return true } ntStatus, ok := status.(syscall.Errno) if !ok { return false } return int32(ntStatus) >= 0 } func main() { var handleInfo windows.Handle var handleInfoSize uint32 = 1024 * 1024 // 初始缓冲区1MB var retlength uint32 // 循环获取足够大的缓冲区 for { if handleInfo != 0 { windows.LocalFree(handleInfo) } allocErr := windows.LocalAlloc(windows.LPTR, handleInfoSize) handleInfo = allocErr.Handle if allocErr.Err != nil { fmt.Printf("内存分配失败: %v\n", allocErr.Err) return } status := windows.NtQuerySystemInformation( windows.SystemExtendedHandleInformation, unsafe.Pointer(handleInfo), handleInfoSize, &retlength, ) if NtSuccess(status) { break } // 缓冲区不足,扩容后重试 if status == windows.STATUS_INFO_LENGTH_MISMATCH { handleInfoSize = retlength } else { fmt.Printf("获取系统句柄信息失败: %v\n", status) windows.LocalFree(handleInfo) return } } defer windows.LocalFree(handleInfo) handleInfoPtr := unsafe.Pointer(handleInfo) sysInfo := (*SystemHandleInformation)(handleInfoPtr) numHandles := sysInfo.NumberOfHandles entrySize := unsafe.Sizeof(SystemHandleTableEntryInfo{}) fmt.Printf("系统总句柄数: %d\n", numHandles) for i := uint32(0); i < numHandles; i++ { entryPtr := unsafe.Pointer( uintptr(handleInfoPtr) + unsafe.Sizeof(SystemHandleInformation{}) + uintptr(i)*entrySize, ) entry := (*SystemHandleTableEntryInfo)(entryPtr) // 提取你需要的字段:HandleValue(进程句柄值)、Object(内核对象地址) fmt.Printf("[%d] PID: %5d | 句柄值: 0x%04x | 内核对象地址: 0x%x\n", i+1, entry.UniqueProcessId, entry.HandleValue, entry.Object) } }
说明
entry.Object就是你要的内核对象地址,entry.HandleValue是目标进程内的句柄值;- 必须处理
STATUS_INFO_LENGTH_MISMATCH错误,因为系统句柄数量可能较多,初始缓冲区大概率不够; - 确保已安装依赖库:
go get golang.org/x/sys/windows
内容的提问来源于stack exchange,提问作者Arkaprabha Chakraborty
相关产品推荐
相关产品推荐

