Golang中最小支持的Windows版本:内部链接与PE头信息解析

Golang中最小支持的Windows版本:内部链接与PE头信息解析 我发现在 1.12.17 版本之后,Go 提高了最低支持的 Windows 版本。Go 二进制文件本身以及由其创建的二进制文件都停止工作,Windows 仅报告“xyz.exe 不是有效的 win32 应用程序”。

我猜这与更新日志中的这条说明有关:

Windows 内部链接的二进制文件现在指定的 Windows 版本是 Windows 7 而非 NT 4.0。这已经是 Go 的最低要求版本,但可能会影响具有向后兼容模式的系统调用的行为。这些调用现在将按文档所述的方式运行。外部链接的二进制文件(任何使用 cgo 的程序)始终指定了更新的 Windows 版本。

(摘自 https://golang.org/doc/go1.13#windows

有人能向我解释一下“内部链接”是什么意思吗? 以及二进制文件中的哪些信息包含了与版本相关的部分(是 PE 头信息吗)?

现在,照例声明一下 ;) 我知道 Windows 8.1 处于扩展支持阶段,而 Windows 7 已停止支持,任何使用它及其前身 Vista 和 XP 的人都该下地狱。但我需要更新那些仍在无法轻易更新/替换的硬件上运行的软件。


更多关于Golang中最小支持的Windows版本:内部链接与PE头信息解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

感谢您的反馈和提供的信息。

情况没有您想的那么糟 😉 我“仅仅”需要在 Windows XP 上运行这个二进制文件。如果二进制文件能提示“我需要 XP”而不是“我需要 NT 4.0”,那也可以接受。

以下是您询问的背景故事: 我销售一款软件,它从一个 POS 软件获取信息。那个 POS 软件很老旧,只能在 Windows 上运行,并且只支持 Windows XP 或更早的系统。顺便说一句:这个 POS 软件是由一家完全不同的公司开发和销售的。 使用那个 POS 软件的人大多非常友善,但他们完全不在乎使用过时、不安全的操作系统所带来的隐患。有些人甚至还在使用 Windows 2000。 目前我的软件是通过 U 盘交付的,用户插上 U 盘,点击可执行文件,然后程序会自行安装。即使这个过程对大多数用户来说也很困难,而且我不可能为了安装软件而跑遍全国。 我曾考虑将程序放在树莓派上,并通过本地网络让它访问 Windows XP 机器上的文件(是的,那些 XP 机器是联网的,有些甚至连接了互联网……)。这在技术上是可行的,但对于那些从 U 盘安装软件都觉得困难的人来说,要正确安装那个树莓派肯定会很吃力……(我认为这不可行)

更多关于Golang中最小支持的Windows版本:内部链接与PE头信息解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


mar1ged:

有人能向我解释一下“内部链接”是什么意思吗?

我不清楚在这个上下文中它确切的含义,但我推测这指的是操作系统加载和/或执行可执行文件和/或DLL的方式发生了变化。

根据这个描述,看起来Go二进制文件报告了与Windows NT 4.0的兼容性,即使它们实际上并不(或者可能不)兼容Windows NT 4.0。

mar1ged:

二进制文件中的哪些信息包含了与版本相关的部分(是PE头信息吗)?

请稍等一下,告诉我们你具体想做什么。根据我对你帖子的理解,你似乎是在尝试为比2009年发布的Windows 7更早的平台编译代码,那已经是将近11年前了。

如果你确实想在较旧的平台上编译和运行你的Go程序,那么这个变更日志告诉我,编译后的可执行文件错误地报告了与1996年的Windows NT 4.0的兼容性。也就是说,它们向操作系统报告自己遵循Windows NT 4.0的API,但实际上可能会调用一个无效的Windows API函数,最终可能导致:

  1. 应用程序崩溃(这是最好的情况)
  2. 在某个时刻调用了一个意外的函数(因为Go编译器可能会发出一个本不应该发出的Windows OS API调用,它期望API像Windows NT 4.0那样工作,但该API实际上并不遵循那个API规范)。

如果你想要一个能在Windows NT 4.0上稳定运行的程序,我建议你联系Go团队,让他们确认哪个最新的Go版本与你目标平台兼容。

如果你真的在寻求23年多以前的Windows NT 4.0兼容性,我建议升级你的代码以适配更新的平台。你提到了硬件兼容性,这与操作系统兼容性不同。我想不出一个很好的理由(诚然,我不是计算机科学专家)来试图让你的Go代码保持与Windows NT 4.0的兼容。如果你受限于旧硬件,你仍然可以将操作系统切换到基于Linux的系统,这些系统仍然可以在i386兼容的硬件上运行。

如果这个回答没有解决你的问题,请告诉我们背景信息,以便我们判断为什么报告Windows 7+兼容性会是一个问题。

在Go语言中,内部链接指的是完全使用Go工具链(如go build)编译生成的二进制文件,不依赖任何外部C库(即未使用cgo)。这种链接方式生成的二进制文件是纯Go的,不包含C运行时依赖。

外部链接则指使用了cgo(通过import "C")或链接了外部C库的Go程序,这类程序会依赖系统的C运行时库(如msvcrt.dllucrtbase.dll)。

关于版本信息,确实存储在PE头信息中。具体来说,是PE文件头的Optional Header中的MajorSubsystemVersionMinorSubsystemVersion字段,它们定义了运行该二进制文件所需的最低Windows子系统版本。

在Go 1.13之前,内部链接的二进制文件将这些字段设置为4.0(对应Windows NT 4.0)。从Go 1.13开始,内部链接的二进制文件将这些字段设置为6.1(对应Windows 7)。这会导致更早的Windows版本(如Windows XP或Vista)无法运行这些二进制文件。

以下是一个示例代码,展示如何读取Go生成的Windows PE文件的子系统版本信息:

package main

import (
    "debug/pe"
    "fmt"
    "log"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        log.Fatal("请提供PE文件路径")
    }

    file, err := pe.Open(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    switch hdr := file.OptionalHeader.(type) {
    case *pe.OptionalHeader32:
        fmt.Printf("子系统版本: %d.%d\n", 
            hdr.MajorSubsystemVersion, 
            hdr.MinorSubsystemVersion)
    case *pe.OptionalHeader64:
        fmt.Printf("子系统版本: %d.%d\n", 
            hdr.MajorSubsystemVersion, 
            hdr.MinorSubsystemVersion)
    default:
        log.Fatal("不支持的PE格式")
    }
}

要验证不同Go版本生成的二进制文件,可以编译上述程序并检查不同Go版本编译的exe文件:

# 使用Go 1.12编译
go1.12 build -o test_1.12.exe main.go

# 使用Go 1.13+编译  
go1.13 build -o test_1.13.exe main.go

# 检查子系统版本
go run pe_check.go test_1.12.exe
go run pe_check.go test_1.13.exe

对于需要向后兼容的情况,可以考虑以下方案:

  1. 使用旧版Go工具链:继续使用Go 1.12或更早版本编译
  2. 交叉编译指定版本:使用Go 1.13+但手动设置子系统版本:
// 在构建时使用ldflags设置子系统版本
// go build -ldflags="-X main.version=1.0 -H windowsgui -linkmode=internal -extldflags=\"-Wl,--major-subsystem-version=5 -Wl,--minor-subsystem-version=1\""
  1. 使用外部链接:通过cgo强制使用外部链接,但这会增加对C运行时的依赖:
// 添加空的cgo导入
import "C"

func main() {
    // 程序代码
}
  1. 使用PE编辑器:编译后使用工具(如rcedit)修改已生成二进制文件的PE头信息。

对于无法更新硬件的环境,最可靠的方案是使用Go 1.12或更早版本进行编译,或者使用上述方法修改PE头中的子系统版本字段。

回到顶部