cmd/link: ld在链接cgo应用程序时消耗无限内存,

hc2pp10m  于 8个月前  发布在  Go
关注(0)|答案(8)|浏览(93)

你正在使用的Go版本是什么( go version )?

$ go version
go version go1.21.1 windows/amd64

这个问题在最新版本的发布中是否重现?

1.21.1是MSYS2中可用的最新版本。我尝试了官方的Windows二进制版本1.21.2,但它不支持cgo,所以在链接步骤之前失败。值得注意的是,我不认为在1.21.1和1.21.2之间的提交中有任何相关的内容。

你正在使用什么操作系统和处理器架构( go env )?

Windows 11, amd64
从MSYS2包中的go mingw-w64-ucrt-x86_64-go
MSYS2包中的GNU Binutils 2.41 mingw-w64-ucrt-x86_64-binutils
go env 输出

$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\maia\AppData\Local\go-build
set GOENV=C:\Users\maia\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\maia\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\maia\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:/msys64/ucrt64/lib/go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\msys64\ucrt64\lib\go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.21.1
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\Users\maia\code\snafu\go-hotplug\go.mod
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\msys64\tmp\go-build1188127562=/tmp/go-build -gno-record-gcc-switches

你做了什么?

尝试在 elemecca/go-hotplug 中构建示例程序

go build -x ./examples/announcehid

你期望看到什么?

它应该成功生成可执行文件,或者如果我的代码中有什么错误,应该立即失败。无论如何,它在进行操作时都应该消耗合理的内存。

你看到了什么?

在构建结束时的 cmd/link 步骤中, ld.exe 不断分配内存,从未完成。
在我机器上,有64 GB的RAM,它每秒大约分配2 GB,直到达到大约60 GB,然后当系统开始疯狂交换时速度会变慢。出于科学目的,我让它运行一次;Windows蓝屏时已经分配了大约215 GB。
由于问题出现在 ld.exe 中,这可能是GNU Binutils的上游错误,但我不知道关于如何工作的足够信息来确定这一点或为他们提供足够的信息以提交错误报告。

bqujaahr

bqujaahr1#

我尝试在MSYS2环境中从源代码构建go。go1.21.2masterf711892a8a上表现出与打包的go 1.21.1相同的行为。此外,为了纪念,在这个问题发生时,elemecca/go-hotplug上的当前提交是9e9ddbf41d

vcirk6k6

vcirk6k62#

I managed to reduce the test case down to just this.

package main

import (
    "fmt"
    "unsafe"
)

/*
#cgo LDFLAGS: -lcfgmgr32
#define WINVER 0x0602 // Windows 8
#define UNICODE
#include <windows.h>
#include <cfgmgr32.h>
#include <devpkey.h>

// this is missing from cfgmgr32.h in mingw-w64
CMAPI CONFIGRET CM_Get_Device_Interface_PropertyW(LPCWSTR pszDeviceInterface, const DEVPROPKEY *PropertyKey, DEVPROPTYPE *PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
*/
import "C"

func main() {
    var symbolicLink *C.WCHAR
    var propType C.DEVPROPTYPE
    var size C.ULONG
    var devInstanceId [C.MAX_DEVICE_ID_LEN + 1]C.WCHAR
    var deviceInstance C.DEVINST

    size = (C.ULONG)(len(devInstanceId) * C.sizeof_WCHAR)
    C.CM_Get_Device_Interface_PropertyW(
        symbolicLink,
        &C.DEVPKEY_Device_InstanceId,
        &propType,
        (C.PBYTE)(unsafe.Pointer(&devInstanceId[0])),
        &size,
        0,
    )

    C.CM_Locate_DevNodeW(
        &deviceInstance,
        &devInstanceId[0],
        C.CM_LOCATE_DEVNODE_NORMAL,
    )

    size = 4
    var address int32
    C.CM_Get_DevNode_PropertyW(
        deviceInstance,
        &C.DEVPKEY_Device_Address,
        &propType,
        (C.PBYTE)(unsafe.Pointer(&address)),
        &size,
        0,
    )

    fmt.Print(address)
}
nmpmafwu

nmpmafwu3#

CC @golang/compiler.

neekobn8

neekobn84#

在排障中,我们想知道如果你编写了一个等效的C程序(以类似的方式调用链接器(即类似的标志),ld.exe 是否仍然会消耗大量内存?如果是这样的话,那似乎是ld.exe中的一个bug。

dhxwm5r4

dhxwm5r45#

@mknyszek 这个值得一试。

看起来 go build 正在用命令行调用 link.exe,命令行内容为

C:\msys64\ucrt64\lib\go\pkg\tool\windows_amd64\link.exe -o C:\msys64\tmp\go-build323995550\b001\exe\a.out.exe -importcfg C:\msys64\tmp\go-build323995550\b001\importcfg.link -buildmode=pie -buildid=hmtQRCYZr6LXF52m_PMN/lIjeZR2XoBH9yVTDiytc/ZhhCE-YcTTorCMxaZknw/hmtQRCYZr6LXF52m_PMN -extld=gcc C:\msys64\tmp\go-build323995550\b001\_pkg_.a


link.exe 反过来又用命令行

gcc -m64 -mconsole -Wl,--tsaware -Wl,--nxcompat -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--dynamicbase -Wl,--high-entropy-va -o C:\msys64\tmp\go-build323995550\b001\exe\a.out.exe -Wl,--no-insert-timestamp C:\msys64\tmp\go-link-201239192\go.o C:\msys64\tmp\go-link-201239192\000000.o C:\msys64\tmp\go-link-201239192\000001.o C:\msys64\tmp\go-link-201239192\000002.o C:\msys64\tmp\go-link-201239192\000003.o C:\msys64\tmp\go-link-201239192\000004.o C:\msys64\tmp\go-link-201239192\000005.o C:\msys64\tmp\go-link-201239192\000006.o C:\msys64\tmp\go-link-201239192\000007.o C:\msys64\tmp\go-link-201239192\000008.o C:\msys64\tmp\go-link-201239192\000009.o -O2 -g -lcfgmgr32 -O2 -g -Wl,-T,C:\msys64\tmp\go-link-201239192\fix_debug_gdb_scripts.ld -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group -lkernel32

调用 gcc.exe
gcc.exe 用命令行

C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/collect2.exe -plugin C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/liblto_plugin.dll -plugin-opt=C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\msys64\tmp\cc6t6Uh9.res -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lpthread -plugin-opt=-pass-through=-ladvapi32 -plugin-opt=-pass-through=-lshell32 -plugin-opt=-pass-through=-luser32 -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-lkernel32 -m i386pep --subsystem console -Bdynamic -o C:\msys64\tmp\go-build323995550\b001\exe\a.out.exe C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib/crt2.o C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/crtbegin.o -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0 -LC:/msys64/ucrt64/bin/../lib/gcc -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/lib/../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../.. --tsaware --nxcompat --major-os-version=6 --minor-os-version=1 --major-subsystem-version=6 --minor-subsystem-version=1 --dynamicbase --high-entropy-va --no-insert-timestamp C:\msys64\tmp\go-link-201239192\go.o C:\msys64\tmp\go-link-201239192\000000.o C:\msys64\tmp\go-link-201239192\000001.o C:\msys64\tmp\go-link-201239192\000002.o C:\msys64\tmp\go-link-201239192\000003.o C:\msys64\tmp\go-link-201239192\000004.o C:\msys64\tmp\go-link-201239192\000005.o C:\msys64\tmp\go-link-201239192\000006.o C:\msys64\tmp\go-link-201239192\000007.o C:\msys64\tmp\go-link-201239192\000008.o C:\msys64\tmp\go-link-201239192\000009.o -lcfgmgr32 -T C:\msys64\tmp\go-link-201239192\fix_debug_gdb_scripts.ld --start-group -lmingwex -lmingw32 --end-group -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -lkernel32 -lpthread -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -lkernel32 C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib/default-manifest.o C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/crtend.o

调用 collect2.exe
最后,collect2.exe 用命令行

C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe -plugin C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/liblto_plugin.dll -plugin-opt=C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\msys64\tmp\cc6t6Uh9.res -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lpthread -plugin-opt=-pass-through=-ladvapi32 -plugin-opt=-pass-through=-lshell32 -plugin-opt=-pass-through=-luser32 -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-lkernel32 -m i386pep --subsystem console -Bdynamic -o C:\msys64\tmp\go-build323995550\b001\exe\a.out.exe C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib/crt2.o C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/crtbegin.o -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0 -LC:/msys64/ucrt64/bin/../lib/gcc -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/lib/../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../.. --tsaware --nxcompat --major-os-version=6 --minor-os-version=1 --major-subsystem-version=6 --minor-subsystem-version=1 --dynamicbase --high-entropy-va --no-insert-timestamp C:\msys64\tmp\go-link-201239192\go.o C:\msys64\tmp\go-link-201239192\000000.o C:\msys64\tmp\go-link-201239192\000001.o C:\msys64\tmp\go-link-201239192\000002.o C:\msys64\tmp\go-link-201239192\000003.o C:\msys64\tmp\go-link-201239192\000004.o C:\msys64\tmp\go-link-201239192\000005.o C:\msys64\tmp\go-link-201239192\000006.o C:\msys64\tmp\go-link-201239192\000007.o C:\msys64\tmp\go-link-201239192\000008.o C:\msys64\tmp\go-link-201239192\000009.o -lcfgmgr32 -T C:\msys64\tmp\go-link-201239192\fix_debug_gdb_scripts.ld --start-group -lmingwex -lmingw32 --end-group -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -lkernel32 -lpthread -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -lkernel32 C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib/default-manifest.o C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/crtend.o

调用 ld.exe

等效的 C 程序代码如下:

编译成目标文件时,使用 gcc 成功。

尝试使用类似于 go 使用的链接命令:

这个命令很快就失败了,输出如下:

这与我在尝试简化测试用例并删除一些必要的重复内存泄漏时从 go build 得到的结果相同,所以我怀疑如果不触发失控的内存泄漏,go 构建也会因为这些错误而失败。

我从 gcc 命令中修改的唯一内容是删除了链接脚本 -Wl,-T,C:\msys64\tmp\go-link-201239192\fix_debug_gdb_scripts.ld 并替换了对象文件列表。
考虑到这一点,我认为引发问题的原因要么是在 cgo 生成的额外 C 代码中,要么是在 go 生成的对象文件中,要么是在链接脚本 cmd/link 中使用的。当然,这并不意味着这是 GNU ld 的上游 bug,只是说 go 在某些方面是必要的才能复现它。

hfsqlsce

hfsqlsce6#

值得注意的是,我还尝试了MSYS2 MINGW64环境(包mingw-w64-x86_64-gomingw-w64-x86_64-binutils),它使用MSVCRT而不是UCRT。不幸的是,它也存在同样的问题。

cidc1ykv

cidc1ykv7#

我将问题的触发因素仅限制在由go生成并包含在导致内存泄漏的最终链接命令中的go.o文件。我只需使用gcc go.o就可以重现这个问题。
由于无法附加对象文件到这个问题,所以我将其放入了一个gist:
https://gist.github.com/Elemecca/377451208c45b80226a39456e69f2eef

jogvjijk

jogvjijk8#

看起来在某种程度上,对 DEVPKEY_* 的未定义引用与这个问题有关。
我找到了如何解决这些问题的方法,即在 devpkey.h 上方添加一个包含 initguid.h 的头文件。该头文件重新定义了 DEFINE_GUID 宏,以便在后续的头文件中使用时,它们实际上会定义 GUID 符号,而不仅仅是声明它们 extern
将此添加到 go 测试用例中可以解决问题 - 它现在可以在导致失控内存泄漏的相同环境中成功链接。我的实际应用程序也是如此。
更新后的 go 测试用例,不会触发失控内存泄漏

package main

import (
    "fmt"
    "unsafe"
)

/*
#cgo LDFLAGS: -lcfgmgr32
#define WINVER 0x0602 // Windows 8
#define UNICODE
#include <windows.h>
#include <cfgmgr32.h>
#include <initguid.h>
#include <devpkey.h>

// this is missing from cfgmgr32.h in mingw-w64
CMAPI CONFIGRET CM_Get_Device_Interface_PropertyW(LPCWSTR pszDeviceInterface, const DEVPROPKEY *PropertyKey, DEVPROPTYPE *PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
*/
import "C"

func main() {
    var symbolicLink *C.WCHAR
    var propType C.DEVPROPTYPE
    var size C.ULONG
    var devInstanceId [C.MAX_DEVICE_ID_LEN + 1]C.WCHAR
    var deviceInstance C.DEVINST

    size = (C.ULONG)(len(devInstanceId) * C.sizeof_WCHAR)
    C.CM_Get_Device_Interface_PropertyW(
        symbolicLink,
        &C.DEVPKEY_Device_InstanceId,
        &propType,
        (C.PBYTE)(unsafe.Pointer(&devInstanceId[0])),
        &size,
        0,
    )

    C.CM_Locate_DevNodeW(
        &deviceInstance,
        &devInstanceId[0],
        C.CM_LOCATE_DEVNODE_NORMAL,
    )

    size = 4
    var address int32
    C.CM_Get_DevNode_PropertyW(
        deviceInstance,
        &C.DEVPKEY_Device_Address,
        &propType,
        (C.PBYTE)(unsafe.Pointer(&address)),
        &size,
        0,
    )

    fmt.Print(address)
}

然而,问题仍然存在:即使在存在无效输入的情况下,链接器也不应该通过消耗计算机的所有 RAM 而使计算机蓝屏。

相关问题