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
相关产品推荐
相关产品推荐

