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

Golang调用内存地址执行calc.exe报fault错误,C代码正常问题排查

问题分析:Go代码调用内存中calc.exe报错的原因及解决

问题背景

将calc.exe文件内容加载到内存后,C代码可直接调用内存地址运行计算器,但Go代码执行时触发内存访问错误:unexpected fault address 0xe852455a4d,且确认内存地址有效并包含完整程序内容。

核心原因

这不是Go的Bug,而是调用约定不匹配和Go运行时环境干扰导致的:

  • 调用约定冲突:Windows PE程序(如calc.exe)的入口点采用_stdcall调用约定,而Go语言的函数调用使用自定义约定(与cdecl/stdcall均有差异)。直接将内存地址强制转换为Go的func()类型调用,会导致栈帧结构混乱,触发内存访问错误。
  • Go运行时栈管理干扰:Go的goroutine使用动态扩容的栈,而PE程序期望使用Windows系统提供的线程栈。直接在goroutine中执行PE代码,两者的栈操作逻辑冲突,引发内存异常。

解决方案

不要直接通过Go函数类型转换调用PE入口,而是使用Windows API的CreateThread来启动执行内存中的代码——CreateThread会创建符合Windows规范的线程栈,匹配PE程序的调用约定和运行环境。

修改后的Go代码

package main

import (
    "fmt"
    "io/ioutil"
    "syscall"
    "unsafe"
)

const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
    CREATE_SUSPENDED       = 0x00000004
)

var (
    kernel32                  = syscall.NewLazyDLL("kernel32.dll")
    virtualAlloc              = kernel32.NewProc("VirtualAlloc")
    createThread              = kernel32.NewProc("CreateThread")
    waitForSingleObject       = kernel32.NewProc("WaitForSingleObject")
    resumeThread              = kernel32.NewProc("ResumeThread")
)

func main() {
    filePath := "calc.exe"

    bytes, err := ioutil.ReadFile(filePath)
    if err != nil {
        fmt.Println("读取文件失败:", err)
        return
    }

    // 分配可执行内存
    memoryPtr, _, err := virtualAlloc.Call(
        0,
        uintptr(len(bytes)),
        MEM_COMMIT|MEM_RESERVE,
        PAGE_EXECUTE_READWRITE,
    )
    if memoryPtr == 0 {
        fmt.Println("内存分配失败:", err)
        return
    }
    fmt.Printf("内存地址: %x\n", memoryPtr)

    // 复制PE文件内容到内存
    copy((*[1 << 30]byte)(unsafe.Pointer(memoryPtr))[:len(bytes)], bytes)

    // 创建挂起的线程,指定入口为内存地址
    threadHandle, _, err := createThread.Call(
        0,
        0,
        memoryPtr,
        0,
        CREATE_SUSPENDED,
        0,
    )
    if threadHandle == 0 {
        fmt.Println("创建线程失败:", err)
        return
    }
    defer syscall.CloseHandle(syscall.Handle(threadHandle))

    // 恢复线程执行
    _, _, err = resumeThread.Call(threadHandle)
    if err != nil && err.Error() != "The operation completed successfully." {
        fmt.Println("恢复线程失败:", err)
        return
    }

    // 等待线程结束
    _, _, err = waitForSingleObject.Call(threadHandle, syscall.INFINITE)
    if err != nil && err.Error() != "The operation completed successfully." {
        fmt.Println("等待线程失败:", err)
        return
    }
}

补充说明

C代码能运行的原因是:C语言默认的cdecl调用约定在这个场景下刚好兼容PE入口的执行(或calc.exe的入口实现对调用约定不敏感),且C程序的线程栈是Windows系统栈,与PE程序的期望一致,没有Go运行时的干扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 17:25:55