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

使用Go语言sys/windows库正确获取Windows句柄的技术求助

解决Go调用NtQuerySystemInformation访问句柄数组崩溃及NT_SUCCESS宏实现问题

一、访问句柄数组崩溃的原因及修复

你遇到的exit status 0xc0000374是堆损坏错误,根源在于结构体定义不符合Windows内核返回的数据格式:
Go的slice在内存中是包含指针、长度、容量的三元组,但NtQuerySystemInformation返回的SystemExtendedHandleInformation数据结构,是在NumberOfHandles字段后直接紧跟连续的SystemHandleTableEntryInfo结构体数组,没有slice的元数据。直接强转成带Handles slice的结构体,会导致slice指针指向非法内存,触发内存访问错误。

修复步骤:

  1. 修改结构体定义,去掉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
}
  1. 手动计算内存偏移遍历句柄数组:
    通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:37:05