Go: cmd/go: -buildmode=c-shared 应该在 Windows 上工作

创建于 2015-06-04  ·  216评论  ·  资料来源: golang/go

FrozenDueToAge OS-Windows

最有用的评论

可能大多数 Windows 用户都在寻找一种生成 dll 的方法:

--buildmode=c-shared

要么

--buildmode=dll

所有216条评论

更具体一点会有所帮助:不同的系统支持并且总是支持不同的 -buildmode 选项。 您对哪些 -buildmode 选项特别感兴趣?

可能大多数 Windows 用户都在寻找一种生成 dll 的方法:

--buildmode=c-shared

要么

--buildmode=dll

我只需要-buildmode=c-shared来为其他 C/C++ 用户生成 DLL。
如果 Go 可以生成 DLL,我可以和 C++ 说再见。

PS:也希望在 Go1.5 中修复https://github.com/golang/go/issues/9510

我也希望看到这种情况发生。 目前有人在研究这个问题吗? 任何可以审查的 CL?

不幸的是,我不知道有人在工作
现在就这个问题。

我也需要这个。 如果我知道涉及多少工作,我可能愿意贡献一个实现。 我知道 Windows DLL 与 ELF 风格的共享库有很大的不同,所以如果这是一项艰巨的任务,我将无法承诺。

有什么方法可以查明会涉及多少/什么样的工作?

@nadiasvertex主要是使 cmd/link/internal/ld/lb.go 中的函数 hostlink 在 Windows 上做正确的事情。 在 Darwin 上,它使用 -dynamiclib 调用 C 链接器。 在 GNU/Linux 上,它使用 -Wl,-Bsymbolic -Wl,-z,relro -shared -Wl,-z,nodelete 调用 C 链接器。

基本上,如果您生成将 Windows 对象文件转换为 Windows DLL 的命令列表,请更改 hostlink 以调用这些命令。

也就是说,Windows 可能需要知道可从 DLL 外部调用的符号列表。 它曾经需要知道这一点,但我已经有很多年没有看过它了。 如果您需要该符号列表,可以通过遍历符号表并查找具有 CgoExportStatic 标志的 Cgoexport 字段来获得它。

伟大的! 我会看看它。 我怀疑挑战主要在于知道
链接器在 Windows 上的位置。

2015 年 12 月 7 日星期一上午 10:58 Ian Lance Taylor [email protected]
写道:

@nadiasvertex https://github.com/nadiasvertex主要是做
cmd/link/internal/ld/lb.go 中的函数 hostlink 做正确的事
视窗。 在 Darwin 上,它使用 -dynamiclib 调用 C 链接器。 在 GNU/Linux 上
使用 -Wl,-Bsymbolic -Wl,-z,relro -shared 调用 C 链接器
-Wl,-z,节点删除。

基本上,如果您生成将打开 Windows 的命令列表
将目标文件转换为 Windows DLL,更改 hostlink 以调用这些命令。

也就是说,Windows 可能需要知道符号列表
可从 DLL 外部调用。 它曾经需要知道,但是
自从我看它以来已经很多年了。 如果您需要该列表
符号,它可以通过遍历符号表并寻找
具有 CgoExportStatic 标志的 Cgoexport 字段。


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -162566183。

@ianlancetaylor我开始对此进行更多研究,结果发现我对架构的一些基本部分有一些疑问。 例如:

  1. 我看到在 Windows 上进行构建似乎需要一个 gcc-ish 编译器,比如 mingw。 因此,我假设进行 C 存档构建或共享库构建将需要相同的东西(而不是 MSVC。)
  2. 到目前为止,我已经启用了使用 c-shared 进行 go build 的命令,并且我得到“_rt0_amd64_windows_lib:未定义”,这似乎是在 lib.go/libinit 中设置的。 我不完全确定在这里寻找什么,所以一些指针会有所帮助。

如果有帮助,当前的跟踪输出如下所示:

WORK=C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698
mkdir -p $WORK\命令行参数_obj\
mkdir -p $WORK\命令行参数_obj\exe\
cd Z:\projects\test\src\calc
CGO_LDFLAGS="-g" "-O2" "z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行参数_obj\" -importpath 命令行参数"-exportheader=C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_install.h" -- -I" C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" calc.go
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -print-libgcc-file-name
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.o" -c "C:\Users\CHRIST~1\ AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.o" -c "C:\Users\CHRIST~1\ AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.o" -c "C:\Users\ CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行-arguments_obj_cgo_.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go -build642482698\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.o" -g -O2
"z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -dynpackage main -dynimport "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_.o" -dynout "C:\Users\CHRIST~1\AppData\Local\Temp\go- build642482698\命令行参数_obj_cgo_import.go"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行-arguments_obj_all.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go -build642482698\command-line-arguments_obj\calc.cgo2.o" -g -O2 -Wl,-r -nostdlib -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group C:/TDM -GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/libgcc.a
"z:\projects\go\pkg\tool\windows_amd64\compile.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments.a" -trimpath "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698" -p main -buildid af4f2da53bc903b4d17a55032a8fca5f579d7452 -D _/Z_/projects/test/src/calc -I "C:\Users\CHRIST~1 \AppData\Local\Temp\go-build642482698" -pack "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_gotypes.go" "C:\Users\CHRIST~1 \AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo1.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_import.go "
pack r "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments.a" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\命令行参数_obj_all.o" #内部
光盘。
"z:\projects\go\pkg\tool\windows_amd64\link.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\exe\a. out.exe" -L "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698" -extld=gcc -buildmode=c-shared -buildid=af4f2da53bc903b4d17a55032a8fca5f579d7452 -v "C:\Users\CHRIST~ 1\AppData\Local\Temp\go-build642482698\command-line-arguments.a"
# 命令行参数
标题 = -H11 -T0x401000 -D0x0 -R0x1000
在 $WORK/runtime.a 中搜索 runtime.a
在 z:\projects\go/pkg/windows_amd64/runtime.a 中搜索 runtime.a
0.00 死码
0.01 pclntab=154410 字节,funcdata 共 33488 字节
0.01 数据
0.01 重定位
0.02 重定位
_rt0_amd64_windows_lib.ptr:_rt0_amd64_windows_lib:未定义
0.02 组合
0.02 码块
0.03 数据块
0.03 符号
0.03 矮人
0.03 标头
0.03 对称大小 = 0
0.03 对称大小 = 0
_rt0_amd64_windows_lib.ptr:未定义:_rt0_amd64_windows_lib

我认为我们不需要任何人都需要 GCC,但显然我们确实需要一些其他工具链。 如果有人想添加对 MSVC 的支持,我认为那会很棒。 无论如何,我认为我们确实希望使用 GCC 来假设 -buildmode=c-shared 成为可能。

_rt0_amd64_windows_lib应该在 runtime/rt0_windows_amd64.s 中定义,并且应该在 runtime/rt0_linux_amd64.s 中看起来类似于_rt0_amd64_linux_lib 。 我承认我完全忘记了这部分工作。 您必须调整 argc/argv 处理以适合 Windows,并且您必须实现 newosproc0 的 Windows 版本。

您可能还需要担心线程本地存储在 DLL 中的工作方式(我不知道这种事情在 Windows 上是如何工作的)

@nadiasvertex主要是使 cmd/link/internal/ld/lb.go 中的函数 hostlink 在 Windows 上做正确的事情。 在 Darwin 上,它使用 -dynamiclib 调用 C 链接器。 在 GNU/Linux 上,它使用 -Wl,-Bsymbolic -Wl,-z,relro -shared -Wl,-z,nodelete 调用 C 链接器。

如果您只想构建一个不包含任何 C 代码的 DLL,则不必使用外部链接器。 我相信你可以修改 Go 链接器来产生你想要的东西。 Go 链接器在创建 Windows 可执行文件时就是这样做的。 我看不出创建 DLL 会有什么不同。

您还必须处理每个 Windows DLL 处理的问题。 您必须具有 DLL 中所需的最小函数集。 您必须处理在不同线程上调用的 DLL 导出函数。 你必须处理异常。

您可能还需要担心线程本地存储在 DLL 中的工作方式(我不知道这种事情在 Windows 上是如何工作的)

现在 Go windows 可执行文件中线程本地存储的工作方式有什么问题?

亚历克斯

让我说清楚,我假设 go 已经处理了其中的大部分
问题。 我当然可以为现有服务提供一些粘合剂。 然而,
如果需要编写全新的运行时支持,我将需要很多
更多的方向。

在 Windows 上,dll 入口点有一些约定。 然而,
这些通常是 C 库工件。 如果 go 有特殊需求,我可能会
能够满足那些,给出一些指示。

我个人的需要是获取 go 代码,通过 C ABI 公开其中的一些代码,然后
将用另一种语言编写的可执行文件与其链接。 代码需要
在 Windows、Linux 和 Mac 上工作。 Android、iOS 和 Windows Mobile 是
加,但不急待处理。 Go 完美地满足了其中的大部分。 一世
我对任何有助于我实现这一目标的解决方案感兴趣。 我会
更喜欢强大的集成解决方案。 但是,如果是不合理的
新贡献者的大任务,那么我很乐意知道
变通方法。

2015 年 12 月 7 日星期一晚上 7:27,Alex Brainman [email protected]写道:

@nadiasvertex https://github.com/nadiasvertex主要是做
cmd/link/internal/ld/lb.go 中的函数 hostlink 做正确的事
视窗。 在 Darwin 上,它使用 -dynamiclib 调用 C 链接器。 在 GNU/Linux 上
使用 -Wl,-Bsymbolic -Wl,-z,relro -shared 调用 C 链接器
-Wl,-z,节点删除。

如果你只想构建一个不包含任何 C 代码的 DLL,你
不必使用外部链接器。 我相信您可以将 Go 链接器修改为
生产你想要的。 Go 链接器在创建窗口时就是这样做的
可执行文件。 我看不出创建 DLL 会有什么不同。

您还必须处理每个 Windows DLL 处理的问题。 你
必须具有 DLL 中所需的一组最小函数。 你必须处理
您的 DLL 导出函数在不同线程上调用。 你必须处理
有例外。

您可能还需要担心线程本地存储在 DLL 中的工作方式
(我不知道这种东西在 Windows 上是如何工作的)

Go windows 中线程本地存储的工作方式有什么问题
现在可执行文件?

亚历克斯


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -162714478。

如果您只想构建一个不包含任何 C 代码的 DLL,则不必使用外部链接器。 我相信你可以修改 Go 链接器来产生你想要的东西。 Go 链接器在创建 Windows 可执行文件时就是这样做的。 我看不出创建 DLL 会有什么不同。

如果您没有可用的 C 工具链,那么想要构建共享库/DLL 是不寻常的。 在 Unix 上,我们决定简单地依赖它,而不是花时间教 Go 链接器如何生成共享库。 在 Windows 上,与 ELF 相比,DLL 与可执行文件的差异更大,我建议遵循相同的策略。 因为在一般情况下,我们必须在生成共享库时使用外部链接,所以我认为总是需要它并不是什么坏事。

在 Windows 上,dll 入口点有一些约定。 然而,
这些通常是 C 库工件。 ...

这不是真的。 Windows 上有多种非 C 编译器允许您构建 DLL。

我个人的需要是获取 go 代码,通过 C ABI 公开其中的一些代码,然后
将用另一种语言编写的可执行文件与其链接。

您不必为此使用 DLL。 您还可以将您的 Go 代码作为最终可执行文件的一部分。 但是,您需要具体说明用于构建该可执行文件的工具。 那会是gcc吗? 如果是,那么 Ian 建议的是你的路径。 我对gcc不太了解,所以我不熟悉需要什么。

您也可以尝试使用 gcc 创建 DLL。 同样,这也是 Ian 建议的路径。

如果您不想依赖 gcc 来构建程序,那么您将不得不构建 windows DLL 作为 Go 链接器的一部分。 代码位于 $GOROOT/src/cmd/link/internal/ld/pe.go 中。 当前代码生成 Windows PE 可执行文件(除其他外)。 您可以修改它以输出 Windows DLL(无论 DLL 需要什么)。 我对 pe.go 很熟悉,但我从来没有从头构建过 DLL。 但乐于助人。

您应该首先尝试 gcc 方法,因为它已经在一些非 Windows 操作系统上实现。 所以,也许,这很容易。

如果您没有可用的 C 工具链,那么想要构建共享库/DLL 是不寻常的。 ...

也许我误解了你,但我不同意。 我看不出构建 Windows DLL 与构建 Windows 可执行文件有何不同。 当然,对于 cgo,我们需要 gcc,但除此之外。

在 Unix 上,我们决定简单地依赖它,而不是花时间教 Go 链接器如何生成共享库。

很公平。

在 Windows 上,与 ELF 相比,DLL 与可执行文件的差异更大,我建议遵循相同的策略。 因为在一般情况下,我们必须在生成共享库时使用外部链接,所以我认为总是需要它并不是什么坏事。

在 Windows 上不需要 gcc 有一些优点。 Go 开箱即用。 当事情破裂时,您将拥有所有源代码; 源代码是 Go。 您可以在任何其他操作系统上构建 Go Windows 可执行文件 - 您可以使用 Plan9 计算机构建 Go Windows 可执行文件。

亚历克斯

@alexbrainman这个问题专门关于 -buildmode=c-shared。 使用 -buildmode=c-shared 的唯一原因是构建一个可以链接到用 C 编写的程序的 DLL。我的想法是,这样做的人可能也在用 C 编写程序,因此有一个 C 编译器。 但我显然不是 Windows 开发人员,所以我可能错了。

这个问题专门针对 -buildmode=c-shared。 使用 -buildmode=c-shared 的唯一原因是构建一个可以链接到用 C 编写的程序的 DLL。我的想法是,这样做的人可能也在用 C 编写程序,因此有一个 C 编译器。 但我显然不是 Windows 开发人员,所以我可能错了。

很公平。 类似的,我不熟悉 -buildmode 提供的内容。 我应该使用什么 -buildmode 标志来构建 Windows DLL? 可以从任何 Windows 可执行文件或其他 DLL 调用的 DLL。 这些其他(可执行文件和 DLL)可以用 C 语言编写,但不一定非要这样——它们可以用 Go 语言编写。 我们(我们构建的 Go 可执行文件)一直使用系统 DLL(由 Microsoft 生产)。 你看到 Go 提供了像这样构建 DLL 的方法吗?

亚历克斯

目的是编写一个插件包,可用于打开使用 -buildmode=plugin 构建的 Go 共享库。 但这还没有实现(参见 https://golang.org/s/execmodes 以及更多关于 -buildmode 的信息)。

在 Windows 上,它可能已经可以使用 -buildmode=c-shared 并从 Go 程序打开 DLL。 缺点是您只能使用具有 C 风格接口的函数,并且您将有两个不同的 Go 堆和垃圾收集器——一个在主程序中,一个在 DLL 中。 也就是说,-buildmode=c-shared 旨在为您提供一个完整的 DLL,该 DLL 可以由任何语言编写的程序打开,因此它包含 Go 运行时的完整副本。

谢谢你的解释,伊恩。

亚历克斯

您可能还需要担心线程本地存储在 DLL 中的工作方式(我不知道这种事情在 Windows 上是如何工作的)

现在 Go windows 可执行文件中线程本地存储的工作方式有什么问题?

没什么,但它的工作原理是假设 g 存在于 $fs 的固定偏移量中,并且当您处于将要加载到另一个进程中的动态库中时,您不能假设固定偏移量,因为该进程中的某些东西或另一个动态库可能已经在使用该偏移量。 或者至少,这是你在 ELF 系统上遇到的那种问题——就像我说的,我不知道 windows 在这里是如何工作的。 但我怀疑你需要改变该地区的一些东西。

FTR,在 Windows 上,mingw 不支持原生
线程本地存储(模拟 __thread 关键字。)

也就是说,Go 使用 TIB 中的 ArbitraryUserPointer 字段
作为 TLS 插槽(https://golang.org/cl/5519054),这意味着
如果您将 Go DLL 加载到另一个也使用
ArbitraryUserPointer,那么可能会出现问题。

是的。 我们必须找到不同的方法来找到 TLS 插槽。

亚历克斯

为了明确我的动机和目标:

我想构建一个 .dll,因为我不知道哪些代码最终会消耗我负责的部分。 在某些情况下,静态库就足够了,但在其他情况下则不然。 例如,JNI 和 C# 的 PInvoke 需要一个 .dll 文件。 如果事实证明通过 -buildmode=c-shared 制作 go .dll 是一个比我可以承担的更大的项目,我希望退回到通过 -buildmode=c-archive 创建一个静态库,然后编写一些 C 函数调用 Go 代码,并且本身是 DLL 导出。

我知道 cgo 已经允许我注册回调,它当然允许我调用 C 代码,这几乎可以做任何事情。 因此,我想 TLS 问题已经解决了。 我知道 .dll 给这种情况增加了一些额外的皱纹,但我不得不想象这仅限于设置代码。

关于@mwhudson ,我不明白你在描述什么问题。 如果您说 $fs 段寄存器用作基础,我不确定您为什么认为其他进程在做什么很重要。 每个进程隔离整个寄存器集内容。 代码本身存在于它所在的位置,并在 dll 初始化时通过 GOT 和 PLT 表进行更新。 我承认我已经很长时间没有详细处理这些问题了,所以我肯定在这里遗漏了一些重要的东西。

但是,考虑到这一切都适用于非 Windows 平台,并且考虑到 Windows 对生成原生 go 代码的支持非常好,我不得不想象这些问题中的绝大多数都是已知的并且已经得到解决。 我对具体的指导以及 go 工具链的已知限制非常感兴趣。 我对通用的“thar be dragons”评论不是很感兴趣,因为如果这真的是微不足道的,它已经完成了。:-)

我已经到了尝试实现 newosproc0 的地步。 不幸的是,实现这个功能似乎需要深入了解 Go 在 Windows 上的线程创建机制。 这里有任何指示吗? 我查看了 Linux 版本,但它调用了 Windows 甚至不存在的函数。

我不是 Windows 专家,但在 os1_windows.go 中查看 newosproc,我怀疑 newosproc0 可能只是它的副本。 在 Unix 上 newosproc 和 newosproc0 之间的区别在于 newosproc0 负责安全地分配堆栈,但在 Windows 上 newosproc 无论如何都会忽略 stk 参数。

我注意到 c-shared 和 c-archive 非常相似。 我为初始实现切换到 c-archive 模式以降低复杂性。 当这可行时,我将回到 c-shared 模式。

我创建了一个小项目:

//calc.go
包主

导入“C”

//导出总和
func Sum(x, y int) int {
返回 x+y
}

功能主要(){

}

//test_driver.c

包括“test.h”

int main(int argc, char **argv) {
返回总和(5, 10);
}

在 Linux 上,这似乎工作得很好:

// 命令
csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ go build-buildmode=c-archive -o test.a calc.go
csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ gcc -c test_driver.c -o test_driver.o
csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ gcc -o test test_driver.o test.a -lpthread
csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ ./test
csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ echo $?
15

在 Windows 上,我通过构建的 Go 部分没有错误,但我从 C 链接器获得未定义的引用:

csnelson@nix-us2ua34705wc :~/meps/projects/test/src/calc$ gcc -o test test_driver.o test.a
test.a(000000.o): 在函数Sum': C:/Users/CHRIST~1/AppData/Local/Temp/go-build344693770/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to _cgoexp_f070ceaf1261_Sum'
C:/Users/CHRIST~1/AppData/Local/Temp/go-build344693770/command-line-arguments/_obj/_cgo_export.c:19: 未定义引用crosscall2' test.a(000000.o):calc.cgo2.c:(.rdata$.refptr._cgoexp_f070ceaf1261_Sum[.refptr._cgoexp_f070ceaf1261_Sum]+0x0): undefined reference to _cgoexp_f070ceaf1261_Sum'
collect2.exe:错误:ld 返回 1 退出状态

test.a 存档的内容:
$ ar -t test.a
粘性物
000000.o
000001.o

$ nm test.a
000000.o:
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 d .data
00000000000000f2 N .debug_abbrev
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000030 N .debug_aranges
0000000000000000 N .debug_frame
00000000000002ba N .debug_info
0000000000000000 N .debug_info
0000000000000000 N .debug_line
00000000000000ad N .debug_line
0000000000000000 N .debug_loc
0000000000000000 p .pdata
0000000000000000 r .rdata$.refptr._cgoexp_f070ceaf1261_Sum
0000000000000000 r .rdata$zzz
0000000000000020 r .rdata$zzz
0000000000000000 R .refptr._cgoexp_f070ceaf1261_Sum
0000000000000000 吨 .text
0000000000000040 t .text
0000000000000000 r .xdata
U _cgo_wait_runtime_init_done
U _cgoexp_f070ceaf1261_Sum
你交叉呼叫2
0000000000000000 T 总和

000001.o:
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000130 N .debug_abbrev
0000000000000032 N .debug_abbrev
00000000000002ac N .debug_abbrev
0000000000000019 N .debug_abbrev
0000000000000000 N .debug_abbrev
0000000000000447 N .debug_abbrev
00000000000000d0 N .debug_aranges
00000000000000a0 N .debug_aranges
0000000000000070 N .debug_aranges
0000000000000020 N .debug_aranges
0000000000000000 N .debug_aranges
0000000000000040 N .debug_aranges
0000000000000000 N .debug_frame
0000000000000068 N .debug_frame
00000000000000f8 N .debug_frame
0000000000000a19 N .debug_info
000000000000018b N .debug_info
00000000000005ae N .debug_info
00000000000002e2 N .debug_info
0000000000000000 N .debug_info
0000000000000ea2 N .debug_info
0000000000000298 N .debug_line
00000000000000e9 N .debug_line
000000000000003a N .debug_line
000000000000001d N .debug_line
00000000000001bd N .debug_line
0000000000000000 N .debug_line
0000000000000000 N .debug_loc
0000000000000072 N .debug_loc
0000000000000211 N .debug_loc
000000000000000c N .debug_str
0000000000000000 N .debug_str
0000000000000048 p .pdata
0000000000000024 p .pdata
0000000000000000 p .pdata
0000000000000000 r .rdata
0000000000000060 r .rdata
0000000000000030 r .rdata
0000000000000080 r .rdata$zzz
0000000000000020 r .rdata$zzz
0000000000000040 r .rdata$zzz
0000000000000000 r .rdata$zzz
0000000000000060 r .rdata$zzz
0000000000000110 t .text
0000000000000050 t .text
0000000000000000 吨 .text
00000000000001f0 t .text
0000000000000000 吨 .text
0000000000000000 吨 .text
0000000000000000 r .xdata
0000000000000010 r .xdata
0000000000000028 r .xdata
U __imp____iob_func
U __imp__beginthread
U __imp__errno
00000000000001a0 T _cgo_sys_thread_start
0000000000000030 T _cgo_wait_runtime_init_done
中止
00000000000001f0 T crosscall_amd64
U fprintf
你免费
U fwrite
u malloc
0000000000000110 t 螺纹入口
0000000000000090 T x_cgo_free
0000000000000180 T x_cgo_init
0000000000000050 T x_cgo_malloc
0000000000000040 T x_cgo_notify_runtime_init_done
0000000000000000 T x_cgo_sys_thread_create
00000000000000a0 T x_cgo_thread_start
nm: go.o: 文件格式无法识别

@ianlancetaylor

使用 -buildmode=c-shared 的唯一原因是构建一个可以链接到用 C 编写的程序的 DLL。我的想法是,这样做的人可能也在用 C 编写程序,因此有一个 C 编译器。 但我显然不是 Windows 开发人员,所以我可能错了。

在这里我不敢苟同:在 Windows 上,DLL 经常被用作提供插件的机制。 在这种情况下,您从其他人编写的一些第三方应用程序开始(通常是在专有许可下,因此没有源代码;但开源也一样,参见例如 Notepad++ ),您想要扩展带插件。 如果应用程序支持插件,它通常会描述一个插件DLL必须符合的接口,以及如何在应用程序中注册DLL(例如,您必须放置DLL的特定目录)。 所以,真的看不到 C 工具链; 用Delphi 或 C#编写 DLL 是很正常的。

即使您正在为插件构建 DLL,插件接口通常也是
指定的
作为 C 兼容接口(COM 更难)。

在没有 cgo 的情况下,如何从 Go DLL 提供 C 兼容接口?

如果你cgo创建接口,那么gcc是必然的,我看不出来
任何
需要 gcc 构建 DLL 的问题。

请注意,Ian 的观点不是您只能使用 C 编译器创建 DLL,
就是要创建一个 Go DLL (-buildmode=c-shared),需要 GCC。

@nadiasvertex test.a 文件有问题。 您显示的输出看起来好像根本没有指定-buildmode=c-archive 。 使用go build -x查看 Go 命令正在执行的命令。 确保-buildmode=c-archive被传递给链接器。 使用go build -ldflags=-v将 -v 传递给链接器; 确保它正在做正确的事情来生成存档,我认为这意味着调用ar命令。

为了明确我的动机和目标:

将所有功能构建为单个 Go 可执行文件的一部分并将其导出为 RPC(通过 TCP 或类似方式)怎么样?

为什么你认为其他进程在做什么很重要

@mwhudson在这里谈论“trad 本地存储”,而不是关于进程视图。 Go 运行时需要一些“特定于线程”的内存块——您可以从任何线程读取和写入该内存,如果您从不同线程查看内存内容,内存内容看起来会有所不同,但如果您从同一个线程查看,内存内容会相同。 正如@minux提到的,Windows 提供了一些神奇的内存块(TIB),您可以在该块中的特定偏移处存储一个指针,并且该指针在不同的线程上会有所不同。 Go 运行时为此特定目的使用该插槽,但我们也调用一些外部代码(系统 DLL 和 cgo)。 对于 Go 来说幸运的是,没有其他代码使用该插槽。 但是,例如,如果我们创建一个 Go DLL,它有自己的运行时使用相同的插槽,那么从 Go 可执行文件调用这个 DLL 将是致命的。

在没有 cgo 的情况下,如何从 Go DLL 提供 C 兼容接口?

这实际上取决于您将用于_consume_ DLL 的工具。 对于 gcc,您将提供一组 .obj、.lib 和 .h 文件。 对于 Delphi,您提供小的 Pascal 源文件。 对于 C#,你提供了小的 C# 文件(我对 C# 不太了解,所以我在这里可能是错的)。 对于 Go,您可以提供小的 Go 文件(类似于 $GOPATH/src/syscall/zsyscall_windows.go)。

您可以像 Microsoft 一样记录您的界面。 例如, https ://msdn.microsoft.com/en-us/library/windows/desktop/aa363858 (v=vs.85).aspx

我没有看到任何需要 gcc 来构建 DLL 的问题。

你失去了我上面提到的所有好东西。 恕我直言,这对普通 Windows 用户来说太难了。 这意味着您将它们排除在构建 DLL 之外。

亚历克斯

为了明确我的动机和目标:

将所有功能构建为单个 Go 可执行文件的一部分怎么样?
并将其导出为 RPC(通过 TCP 或类似方式)?

这将是一个很难出售的选择。 这可能是工作量的三倍,并且有
性能挑战。 特别是在处理大字符串时
转移,这是这个图书馆要做的大部分工作。

为什么你认为其他进程在做什么

很重要

但是,例如,如果我们创建一个带有自己的运行时的 Go DLL,它使用
相同的插槽,然后从 Go 可执行文件调用此 DLL 将是致命的。

我不明白这怎么可能是真的。 当前的执行模式规范
每个进程只需要一份 go runtime 副本。 除非我
误读,链接时必须合并运行时的所有副本
(静态或动态。)

除非我看错了,否则在链接时(静态或动态)必须合并运行时的所有副本。

如果只有一份运行时副本,那么它肯定会正确协调自己。 但我还不会担心这个。 您需要首先构建您的 DLL。

亚历克斯

@ianlancetaylor :这是带有详细标志的构建的输出:

WORK=C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846
mkdir -p $WORK\命令行参数_obj\
mkdir -p $WORK\命令行参数_obj\exe\
cd Z:\projects\test\src\calc
CGO_LDFLAGS="-g" "-O2" "z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行参数_obj\" -importpath 命令行参数"-exportheader=C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_install.h" -- -I" C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" calc.go
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.o" -c "C:\Users\CHRIST~1\ AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.o" -c "C:\Users\CHRIST~1\ AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.o" -c "C:\Users\ CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行-arguments_obj_cgo_.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go -build079329846\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.o" -g -O2
"z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -dynpackage main -dynimport "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_.o" -dynout "C:\Users\CHRIST~1\AppData\Local\Temp\go- build079329846\命令行参数_obj_cgo_import.go"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行-arguments_obj_all.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go -build079329846\command-line-arguments_obj\calc.cgo2.o" -g -O2 -Wl,-r -nostdlib -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group
"z:\projects\go\pkg\tool\windows_amd64\compile.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments.a" -trimpath "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846" -p main -buildid 5b68efefee46667e4a728bc7de39d8436fd9e03f -D _/Z_/projects/test/src/calc -I "C:\Users\CHRIST~1 \AppData\Local\Temp\go-build079329846" -pack "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_gotypes.go" "C:\Users\CHRIST~1 \AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo1.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_import.go "
pack r "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments.a" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\命令行参数_obj_all.o" #内部
光盘。
"z:\projects\go\pkg\tool\windows_amd64\link.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\exe\a. out.a" -L "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846" -extld=gcc -buildmode=c-archive -buildid=5b68efefee46667e4a728bc7de39d8436fd9e03f -v "C:\Users\CHRIST~ 1\AppData\Local\Temp\go-build079329846\command-line-arguments.a"
# 命令行参数
标题 = -H11 -T0x401000 -D0x0 -R0x1000
在 $WORK/runtime.a 中搜索 runtime.a
在 z:\projects\go/pkg/windows_amd64/runtime.a 中搜索 runtime.a
0.00 死码
0.03 pclntab=171685 字节,funcdata 共 25240 字节
0.03 数据
0.03 重定位
0.03 重定位
0.03 组合
0.03 码块
0.05 数据块
0.05 符号
0.05 矮
0.05 标头
0.05 对称尺寸 = 0
0.05 对称尺寸 = 0
存档:ar -q -c -s $WORK\command-line-arguments_obj\exe\a.out.a C:\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/go.o C :\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/000000.o C:\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/000001.o
0.11 CPU 时间
25353 个符号
7280个活体数据
cp $WORK\命令行参数_obj_cgo_install.h test.h
cp $WORK\command-line-arguments_obj\exe\a.out.a test.a

这一切看起来都是正确的。 但是我不明白为什么你的 nm 程序不能识别 go.o 文件的格式。 您或其他人将不得不深入研究链接器以了解它是如何创建 go.o 文件并找出 nm 不理解它的原因。

事实证明,go.o 文件创建得非常好。 如果我使用我选择的文件夹将 -tmpdir 传递给 link.exe,则生成的存档格式正确。 换一种说法:

go build -x -work -buildmode=c-archive -ldflags="-v -tmpdir ./tmpo" -o test.a calc.go

结果是一个完全有效的 test.a,我可以链接到:

gcc -o test test_driver.o test.a -lws2_32 -lntdll

我猜有一个竞争条件,外部链接器没有完成它的工作,但是临时文件夹被从它下面拉出来。

此可执行文件在运行 runtime.sdtcall1 后出现段错误。

(gdb) 运行
启动程序:Z:\projects\test\src\calc\test.exe
[新线程 3224.0x14c]
[新线程 3224.0xb20]
[新线程 3224.0x38c]

断点3,0x0000000000422760 in runtime.stdcall1()
2: x/3i $pc
=> 0x422760: 低于 $0x10,%rsp
0x422764: 移动 %gs:0x28,%rbx
0x42276d: 移动 0x0(%rbx),%rcx

(gdb) 信息寄存器
拉克斯 0x2a 42
RBX 0x4c41d8 4997592
回复 0x2 2
rdx 0x24fdf0 2424304
rsi 0x5 5
rdi 0xdb1540 14357824
rbp 0x4018c0 0x4018c0 main._cgoexpwrap_f070ceaf1261_Sum
回复 0x24fc78 0x24fc78
r8 0x18 24
r9 0xdb15b0 14357936
r10 0x0 0
r11 0x286 646
r12 0x1 1
r13 0x8 8
r14 0x0 0
r15 0x0 0
撕裂 0x422760 0x422760 runtime.stdcall1
eflags 0x246 [PF ZF IF]
CS 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
GS 0x0 0

(gdb) c
继续。

程序收到信号 SIGSEGV,分段错误。
0x000000000042276d 在 runtime.stdcall1 ()
2: x/3i $pc
=> 0x42276d: 移动 0x0(%rbx),%rcx
0x422774: 移动 0x30(%rcx),%rbx
0x422778: movq $0x1,0x328(%rbx)
(gdb) 信息寄存器
拉克斯 0x2a 42
RBX 0x0 0
回复 0x2 2
rdx 0x24fdf0 2424304
rsi 0x5 5
rdi 0xdb1540 14357824
rbp 0x4018c0 0x4018c0 main._cgoexpwrap_f070ceaf1261_Sum
回复 0x24fc68 0x24fc68
r8 0x18 24
r9 0xdb15b0 14357936
r10 0x0 0
r11 0x286 646
r12 0x1 1
r13 0x8 8
r14 0x0 0
r15 0x0 0
撕裂 0x42276d 0x42276d runtime.stdcall1+13
eflags 0x10202 [中频射频]
CS 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
GS 0x0 0

再跟踪一下,结果发现我发现了这个:

//gcc_libinit_windows.c
空白
_cgo_wait_runtime_init_done() {
// TODO(spetrovic): 实现这个方法。
}

我不知道这是否重要,因为它还没有为 openbsd 或 linux_ppc 实现。 我可以看到 Linux 的“默认”实现使用 pthread 互斥锁。 我可能可以为 Windows/gcc 实现类似的东西。 事实上,它可能不需要改变太多,如果有的话。

我逐字复制代码,程序不再出现段错误。 相反,它永远挂在 _cgo_wait_runtime_init_done() 中,这可能表明 x_cgo_notify_runtime_init_done() 永远不会被调用。

任何指示从这里去哪里?

x_cgo_notify_runtime_init_done 由 runtime/proc.go 中的 main 函数以名称 _cgo_notify_runtime_init_done 调用。

可能没有调用函数 main 。 这通常是因为链接器将 INITENTRY(rt0 符号)放在了 INITARRAY 部分(请参阅 cmd/link/internal/ld/data.go 中的 addinitarrdata)。 然而,虽然这适用于 ELF,但它可能对 PE 没有任何作用。 您需要弄清楚如何安排在程序启动时调用该符号。

我想你会合成一个调用 rt0 的 DllMain 函数
功能。

@ianlancetaylor我用静态构造函数构建了一个 C++ 文件,看起来该函数的地址已放入 Windows 上名为“.ctors”的部分中。 此外https://gcc.gnu.org/onlinedocs/gccint/Initialization.html似乎表明这是此信息的正确位置。

C++ .o 文件如下所示:

objdump -h
const.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000080  0000000000000000  0000000000000000  0000021c  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .bss          00000010  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC
  3 .text$_ZN4testC1Ev 00000010  0000000000000000  0000000000000000  0000029c  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, LINK_ONCE_DISCARD (COMDAT _ZN4testC1Ev 4)
  4 .xdata$_ZN4testC1Ev 00000008  0000000000000000  0000000000000000  000002ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD
  5 .pdata$_ZN4testC1Ev 0000000c  0000000000000000  0000000000000000  000002b4  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA, LINK_ONCE_DISCARD
  6 .text$_ZN4testD1Ev 00000010  0000000000000000  0000000000000000  000002c0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, LINK_ONCE_DISCARD (COMDAT _ZN4testD1Ev 8)
  7 .xdata$_ZN4testD1Ev 00000008  0000000000000000  0000000000000000  000002d0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD
  8 .pdata$_ZN4testD1Ev 0000000c  0000000000000000  0000000000000000  000002d8  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA, LINK_ONCE_DISCARD
  9 .xdata        00000024  0000000000000000  0000000000000000  000002e4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .pdata        00000024  0000000000000000  0000000000000000  00000308  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 11 .ctors        00000008  0000000000000000  0000000000000000  0000032c  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 12 .rdata$zzz    00000020  0000000000000000  0000000000000000  00000334  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

但是 go.o 文件看起来像这样:

tmpo/go.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000c1200  0000000000001000  0000000000001000  00000600  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE, DATA
  1 .data         00001200  00000000000c3000  00000000000c3000  000c1800  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  2 .bss          0001f4c0  00000000000c5000  00000000000c5000  00000000  2**5
                  ALLOC

在 Linux 上它看起来像这样:

tmpo/go.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00052650  0000000000000000  0000000000000000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .rodata       00040dd3  0000000000000000  0000000000000000  00053660  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  2 .typelink     000009f0  0000000000000000  0000000000000000  00094438  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  3 .gosymtab     00000000  0000000000000000  0000000000000000  00094e28  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  4 .gopclntab    00028a6b  0000000000000000  0000000000000000  00094e40  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .note.go.buildid 00000038  0000000000000000  0000000000000000  000bd8c0  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .noptrdata    000003b0  0000000000000000  0000000000000000  000be000  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  7 .init_array   00000008  0000000000000000  0000000000000000  000be3b0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  8 .data         000013b0  0000000000000000  0000000000000000  000be3c0  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  9 .bss          00023878  0000000000000000  0000000000000000  000bf780  2**5
                  ALLOC
 10 .noptrbss     00004b40  0000000000000000  0000000000000000  000e3000  2**5
                  ALLOC
 11 .tbss         00000008  0000000000000000  0000000000000000  000be000  2**3
                  ALLOC, THREAD_LOCAL
 12 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000000  2**0
                  CONTENTS, READONLY
 13 .debug_abbrev 000000ff  0000000000000000  0000000000000000  000d0bff  2**0
                  CONTENTS, READONLY, DEBUGGING
 14 .debug_line   0000a793  0000000000000000  0000000000000000  000d0cfe  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 15 .debug_frame  0000a56c  0000000000000000  0000000000000000  000db491  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 16 .debug_info   00027273  0000000000000000  0000000000000000  000e59fd  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 17 .debug_pubnames 00009a03  0000000000000000  0000000000000000  0010cc70  2**0
                  CONTENTS, READONLY, DEBUGGING
 18 .debug_pubtypes 00004c30  0000000000000000  0000000000000000  00116673  2**0
                  CONTENTS, READONLY, DEBUGGING
 19 .debug_aranges 00000030  0000000000000000  0000000000000000  0011b2a3  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 20 .debug_gdb_scripts 0000002a  0000000000000000  0000000000000000  0011b2d3  2**0
                  CONTENTS, READONLY, DEBUGGING

作为一项实验,我更改了 link/internal/ld/data.go 中的第 1334 行,以创建一个名为“.ctors”的部分,而不是“.init_array”。 但是,“.ctors”部分永远不会出现。 我在 data.go 中添加了一个 Diag() 调用,以确保确实生成了 initarray,而且确实如此。

在这一点上,我不确定为什么 .ctor 部分没有出现。 我通读了 doelf() 函数和 dope() 函数,但我显然遗漏了一些东西。

我在链接/内部/ld/pe.go 中找到了 Asmbpe()。 这实际上为 .text、.data 和 .bss 创建了 PE 部分。 我试图为“.ctors”添加一个部分,但我实际上不确定在通话中应该做什么。

在调用中放入一些硬编码的值确实会在 go.o 文件中产生一个名为 .ctors 的大而空的部分! 所以现在我想我只需要将 initarray 的内容写入这个区域。 我不确定如何获取该数据,也不确定如何正确调整该部分的大小。

我在链接/内部/ld/pe.go 中找到了 Asmbpe()。 这实际上为 .text、.data 和 .bss 创建了 PE 部分。 我试图为“.ctors”添加一个部分,但我实际上不确定在通话中应该做什么。

是的,您在 Asmbpe 中编写 pe 部分。 例如,您可以查看 addimports 函数以了解它是如何完成的。 基本上,您可以在其中编写您想要的任何内容,并调用 addpesection,以便稍后将您的部分参考写入部分表中。 我从未听说过“.ctors”部分,所以我不知道该放什么。

在调用中放入一些硬编码的值确实会在 go.o 文件中产生一个名为 .ctors 的大而空的部分! 所以现在我想我只需要将 initarray 的内容写入这个区域。 我不确定如何获取该数据,也不确定如何正确调整该部分的大小。

您使用 addpesection 来指定您的部分的大小。 将所有数据实际写入 te 文件是您的责任。

亚历克斯

也许有人可以为我澄清一些困惑。

在文件 cmd/link/internal/ld/data.go 中有一个完整的代码块,它创建了一个名为 .init_array 的部分,然后将值写入该部分。 我添加了一些诊断信息,显示此代码也在 Windows 上被调用。

我的问题是,名为“.init_array”的部分在 data.go 中创建后会去哪里? 一个相关的问题是:如何访问写入那里的数据?

另外,我在 gcc 中发现了关于 .init_array 与 .ctors 的非常有趣的讨论: https ://gcc.gnu.org/bugzilla/show_bug.cgi?id=46770

2015 年 12 月 11 日星期五上午 5:09,Christopher Nelson <
通知@github.com> 写道:

也许有人可以为我澄清一些困惑。

在文件 cmd/link/internal/ld/data.go 中有一个完整的代码块
它创建了一个名为 .init_array 的部分,然后将值写入
这个部分。 我添加了一些诊断信息,表明此代码被调用
在 Windows 上也是如此。

我的问题是,名为“.init_array”的部分在它之后去哪里
在 data.go 中创建?

好吧,在 ELF 上,它被写入文件的一个名为
“.init_array”,它被放入 DT_INIT_ARRAY 动态标签中。 我不是
确定在 PE 上会发生什么。

一个相关的问题是:我如何访问数据
写在那里?

这是一个错误的问题。 要么这种方法是正确的,它
应该写入文件,否则不正确,我们需要做
Windows上的其他东西。 我们不应该做的一件事是写一个
.init_array 部分,然后阅读它并将其更改为其他内容。 我们
最初应该是正确的。

另外,我在 .init_array 与 .ctors 中发现了这个非常有趣的讨论

gcc: https ://gcc.gnu.org/bugzilla/show_bug.cgi?id=46770

是的,但请注意,错误报告的上下文是其中的系统
动态链接器实现 DT_INIT_ARRAY,一个 ELF 特定的概念。 如果
Windows 没有类似 .init_array 的东西,我们需要做一些事情
在 Windows 上有所不同。

伊恩

2015 年 12 月 11 日星期五上午 8:40 伊恩·兰斯·泰勒[email protected]
写道:

2015 年 12 月 11 日星期五上午 5:09,Christopher Nelson <
通知@github.com> 写道:

也许有人可以为我澄清一些困惑。

在文件 cmd/link/internal/ld/data.go 中有一个完整的代码块
它创建了一个名为 .init_array 的部分,然后将值写入
这个部分。 我添加了一些诊断程序,表明此代码是

在 Windows 上也是如此。

我的问题是,名为“.init_array”的部分在哪里

在 data.go 中创建?

好吧,在 ELF 上,它被写入文件的一个名为
“.init_array”,它被放入 DT_INIT_ARRAY 动态标签中。 我不是
确定在 PE 上会发生什么。

是的,我明白这一点。 我的困惑源于我没有看到的事实
data.go 中任何特定于平台的代码。 我理解数据“应该”
并最终以某种方式进入 ELF 的 .init_array 部分。 一世
想在 Windows 上对 PE 执行相同的步骤,除了重定向
数据到 .ctors 部分。

换句话说,我在这里看到了代码:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1333
这在 Windows 和 Linux 上被调用,但我不明白这是怎么回事
实际上被 ELF 作家捡到了。 它肯定不会被选中
由 PE 作家提出。

我还看到:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1015
这也在 Windows 上被调用,似乎生成了我想要的数据
写入 Windows PE 文件中的 .ctors 部分。 然而,再次
我实际上并不了解数据的“去向”,或者如何返回
在 Asmbpe() 中将其写入,以便我可以将其写入 .ctors 部分。

我浏览了 ELF writer,我看到它添加了一个部分名称
称为.init_array,但我不清楚它实际上是如何得到的
连接到 data.go 中同名的部分。

运行一些实验后,似乎 gcc 在 Windows 上独占
对这种事情使用 .ctors 数据部分。 没有出现
成为一个 .init_array 概念,坦率地说,对于静态库,我认为这是
完全与编译器运行时相关。 对于 gcc,.ctors 部分似乎
就像它会工作一样。 对于 MSVC,可能还有其他一些机制,但那是
目前对我来说并不重要。 如果有人读到这里知道
更好,我绝对想听听。

我做了一些简单的 C 代码,如下所示:

$ cat const_2.c
void do_this_first() __attribute__((constructor));

void do_this_first() {
  for(int i=0; i<100; ++i) {}
}

在 Linux 上编译它看起来像这样:

$ objdump -h const_2_u.o

const_2_u.o:     file format elf64-x86-64

Sections:

Idx Name          Size      VMA               LMA               File off Algn

  0 .text         0000001a  0000000000000000  0000000000000000  00000040 2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

  1 .data         00000000  0000000000000000  0000000000000000  0000005a 2**0
                  CONTENTS, ALLOC, LOAD, DATA

  2 .bss          00000000  0000000000000000  0000000000000000  0000005a 2**0
                  ALLOC

  3 .init_array   00000008  0000000000000000  0000000000000000  00000060 2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA

  4 .comment      0000002e  0000000000000000  0000000000000000  00000068 2**0
                  CONTENTS, READONLY

  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000096 2**0
                  CONTENTS, READONLY

  6 .eh_frame     00000038  0000000000000000  0000000000000000  00000098 2**3

$ objdump -s const_2_u.o

const_2_u.o:     file format elf64-x86-64

Contents of section .text:

 0000 554889e5 c745fc00 000000eb 048345fc  UH...E........E.
 0010 01837dfc 637ef690 5dc3               ..}.c~..].

Contents of section .init_array:
 0000 00000000 00000000                    ........

Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520352e  .GCC: (Ubuntu 5.
 0010 322e312d 32327562 756e7475 32292035  2.1-22ubuntu2) 5
 0020 2e322e31 20323031 35313031 3000      .2.1 20151010.

Contents of section .eh_frame:

 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 1a000000 00410e10 8602430d  .........A....C.
 0030 06550c07 08000000                    .U......

在 Windows 上,它看起来像这样:

$ objdump -h const_2.o

const_2.o:     file format pe-x86-64

Sections:

Idx Name          Size      VMA               LMA               File off Algn

  0 .text         00000030  0000000000000000  0000000000000000  0000012c 2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

  1 .data         00000000  0000000000000000  0000000000000000  00000000 2**4
                  ALLOC, LOAD, DATA

  2 .bss          00000000  0000000000000000  0000000000000000  00000000 2**4
                  ALLOC

  3 .xdata        0000000c  0000000000000000  0000000000000000  0000015c 2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  4 .pdata        0000000c  0000000000000000  0000000000000000  00000168 2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

  5 .ctors        00000008  0000000000000000  0000000000000000  00000174 2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA

  6 .rdata$zzz    00000020  0000000000000000  0000000000000000  0000017c 2**4

$ objdump -s const_2.o

const_2.o:     file format pe-x86-64

Contents of section .text:

 0000 554889e5 4883ec10 c745fc00 000000eb  UH..H....E......
 0010 048345fc 01837dfc 637ef690 4883c410  ..E...}.c~..H...
 0020 5dc39090 90909090 90909090 90909090  ]...............

Contents of section .xdata:

 0000 01080305 08120403 01500000           .........P..

Contents of section .pdata:

 0000 00000000 22000000 00000000           ....".......

Contents of section .ctors:

 0000 00000000 00000000                    ........

Contents of section .rdata$zzz:

 0000 4743433a 20287464 6d36342d 31292035  GCC: (tdm64-1) 5
 0010 2e312e30 00000000 00000000 00000000  .1.0............

这可能不是决定性的,但它强烈地向我表明,在这个
相当简单的情况,如果我在 ELF 上获取 .init_array 中的数据,并且
将其写入 PE 上的 .ctors,它可能会起作用。

换句话说,我在这里看到了代码:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1333
这在 Windows 和 Linux 上被调用,但我不明白这是怎么回事
实际上被 ELF 作家捡到了。 它肯定不会被选中
由 PE 作家提出。

这令人困惑。

首先是命名混乱。 PE 文件有“节”(如果需要详细信息,请搜索 pecoff.doc),而 Go 链接器指的是包含“节”的“段”。 PE“节”对应于 Go 链接器“段”。 就段而言,Go 链接器生成“文本”、“数据”和“矮...”段列表。 这些包含每个平台所需的最少代码 + 数据 + debug_info。 所有附加位均由特定于平台的代码编写。

例如,当 Asmbpe 启动“text”时,“data”和“dwarf...”段已经在磁盘上。 所有 PE 编写代码所做的就是记录(addpesection)这些位置的位置,因此可以在构建 PE 文件时将它们写入“pe 节表”。 Asmbpe 还写入此文件所需的任何其他 PE 部分 - 这次不仅通过调用 addpesection 还通过实际写入文件内容。 例如,在 addimports 中,它编写了一个特殊的 PE 部分,Windows 程序加载器使用它来查找 Go 程序在运行时将使用的所有系统 DLL。

我怀疑这里发生了什么,是有人在 Go 链接器中创建了新的“段”而没有告诉 PE 作家。 所以你需要做的就是在 Asmbpe 的某个地方添加适当的 addpesection。 或者将编写代码复制到 pe.go 中,如果您认为它更正确或更清晰等等。 调用 addpesection 时,您可以将其命名为“.ctors”或任何您想要的名称。 我不熟悉“.ctors”,所以我不知道是否需要它或者它是否会对您有所帮助。

亚历克斯

@alexbrainman@ianlancetaylor非常感谢您提供的所有帮助。 我现在处于一个令人沮丧的地方,所以也许你可以帮助我更多。

我很确定我知道 _what_ 确切需要写入 PE 文件,至少对于 gcc:

  1. 在 Windows 上,gcc 期望在名为 .ctors 的部分中找到以 0 开头并以 0xffffffff 终止的函数指针列表。 (https://gcc.gnu.org/onlinedocs/gccint/Initialization.html)不管是ELF还是PE,都是一样的。
  2. 链接器会将 .o 文件中这些列表的所有内容组合到一个名为CTOR_LIST的数组中。
  3. glibc 运行时与链接器协作生成一个名为 __do_global_ctors 的函数,该函数将遍历此列表并调用其上的所有函数。

我现在遇到的问题是:

  1. 我不确定如何找出应该写在这里的_rt0_amd64_windows_lib函数的地址。
  2. 我不确定是否需要在某处写一个 reloc 条目以防符号被重新定位。
  3. 我似乎无法弄清楚如何将二进制数据实际写入该部分。

很抱歉我很痛苦,但我觉得我真的很接近 -buildmode=c-archive 工作,我似乎无法获得最后的作品。

我找到了 Cntxt.AllSyms,我可以找到“_rt0_amd64_windows_lib”的符号记录,但是符号记录中有很多数据,显然不是地址(或地址)。

我还看到了 Vputl、Lputl 函数。 但是,当我调用它们时,结果数据似乎没有出现在文件中。

再次感谢你的帮助。

我找到了 Cntxt.AllSyms,我可以找到“_rt0_amd64_windows_lib”的符号记录,但是符号记录中有很多数据,显然不是地址(或地址)。

我认为 Value 包含函数的地址(但我可能是错的)。 但该地址是一个“绝对”地址。 我不认为绝对地址对你有用。 也许您需要从包含该函数的 PE 部分的开头偏移。 也许您需要为这些提供搬迁。 我会摆弄 gcc 编译的对应目标文件,看看需要什么。 我已经为类似目的构建了 github.com/alexbrainman/goissue10776/pedump。 它可能不如 objdump 好,但它是用 Go 编写的,我可以用它轻松地做我喜欢的事情。 它还可以帮助您了解 PE 文件的结构。

我似乎无法弄清楚如何将二进制数据实际写入该部分

以 addimports 为例。 Vputl 和 Lputl 以及许多其他的当然可以工作。 IO 被缓冲,因此您不会看到它们立即写入文件。 我还建议你检查你在文件中的位置——你可能写在文件中间的某个地方,而不是结尾。 我不确定你在做什么。 如果您向我们展示您的更改,也许有人可以帮助您。

亚历克斯

谢谢,这很有帮助。 我现在有数据显示在 .ctors 部分。 我认为这不是正确的数据,但我越来越接近了。

我已经分叉了 go repo 并进行了更改。 到目前为止所有工作的提交是:
https://github.com/nadiasvertex/go/commit/6a98514598645aa03b21c54f1ea0a18e413ce5fb

我现在关注的特定功能是:
https://github.com/nadiasvertex/go/blob/master/src/cmd/link/internal/ld/pe.go#L1089

根据objdump:

$ objdump -S ./tmpo/go.o | grep "_rt0"                                                                                             
000000000004f430 <_rt0_amd64_windows_lib>:

我想要的符号地址是 0x4f430。 这与 gcc 所做的相对应:

$ objdump -S const_2.o
0000000000000012 <do_this_first>:

$ objdump -s const_2.o
Contents of section .ctors:
 0000 12000000 00000000                    ........   

但是,这不是我实际得到的数据:

$ objdump -s -j .ctors tmpo/go.o 
Contents of section .ctors:
 e5000 30e40400 00000000 00000000 00000000  0...............

或者,如果我不减去 Segtext.Vaddr:

Contents of section .ctors:
 e5000 30f44400 00000000 00000000 00000000  0.D.............

我还注意到我可以在这里创建的最小节大小是 512 字节,但 gcc 似乎将节大小设置为实际写入数据的长度。 在这种情况下,gcc 使 .ctors 长 8 个字节,但 go 使它长 512 个字节。 我想知道当平台链接器在最后合并 .o 文件的 .ctors 部分时,这是否会混淆平台链接器。

海合会:

$ objdump -h const_2.o

const_2.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000040  0000000000000000  0000000000000000  0000012c  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC
  3 .xdata        00000024  0000000000000000  0000000000000000  0000016c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .pdata        00000030  0000000000000000  0000000000000000  00000190  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .ctors        00000008  0000000000000000  0000000000000000  000001c0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  6 .rdata$zzz    00000020  0000000000000000  0000000000000000  000001c8  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

走:

$ objdump -h tmpo/go.o
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000c1200  0000000000001000  0000000000001000  00000600  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE, DATA
  1 .data         00001200  00000000000c3000  00000000000c3000  000c1800  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  2 .bss          0001f4c0  00000000000c5000  00000000000c5000  00000000  2**5
                  ALLOC
  3 .ctors        00000200  00000000000e5000  00000000000e5000  000c2a00  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

我还注意到我可以在这里创建的最小节大小是 512 字节,但 gcc 似乎将节大小设置为实际写入数据的长度。 在这种情况下,gcc 使 .ctors 长 8 个字节,但 go 使它长 512 个字节。 我想知道当平台链接器在最后合并 .o 文件的 .ctors 部分时,这是否会混淆平台链接器。

是的,这可能会破坏你的东西。

Go PE 链接器仅用于生成可执行文件。 Windows 可执行文件必须将部分填充到 512 字节(也许其他一些大小也可以),否则 Windows 程序加载器无法执行它们。

最近 minux 实现了代码以允许使用 cgo 进行外部链接器。 该代码生成目标文件(供 gcc 链接器使用)而不是可执行文件。 目标文件的目标文件部分以相同的方式填充,但仅存在 2 个部分:.text 和 .data。 gcc 对此并不抱怨。

在尝试解决问题 #10776(参见 cl 13571)时,我发现不能填充矮部分,否则 gcc 会抱怨。 您可以将代码更改为不填充 .ctors 部分,看看会发生什么。 您可以从 cl 13571 中捏出我的一些代码。我认为不应该填充目标文件中的所有部分,但它不容易更改 - 当 PE 链接器知道它是在构建可执行文件还是目标文件时,对齐已经被设置。 我们需要为此重新排列代码,但我没有时间摆弄它。

我还建议您检查您的 C 目标文件是否有任何重定位。 您可能也必须创建它们。

亚历克斯

我已经取得了一些进步。 在调用 addpesection 后,我手动终止了这一节的填充,这会在稍后的节标题中产生正确的值。

事实证明,我需要为 .ctors 部分发出一个重定位条目(这很有意义。)我无法重用 peemitreloc 代码,因为它想要遍历整个符号链,而我只想写一个符号。

无论如何,我查看了 MS 的 PE 规范,我认为我拥有我需要的大部分信息。 但是,我似乎无法获得正确的 SymbolTableIndex,它是“符号表的从零开始的索引。这个符号给出了要用于重定位的地址。如果指定的符号有节存储类,那么符号的地址就是
同名的第一部分。”

这里的任何指示都会非常有帮助。

另请注意,gcc 似乎会在 .ctors 部分生成重定位,而不是 .text 部分:

const_2.o:     file format pe-x86-64

RELOCATION RECORDS FOR [.ctors]:
OFFSET           TYPE              VALUE 
0000000000000000 R_X86_64_64       .text

最新代码为: https ://github.com/nadiasvertex/go/commit/f0894cbfb254c762a59fc5c4510c5004dfcc76a5

如果您有*LSyms ,那么您要查找的符号索引是s.Dynid 。 但如果该值为负数,则没有分配符号索引,并且出现了问题。

针对 .text 节的 reloc 实际上是针对名为.text的符号的 reloc,该符号定义在名为.text的节中的偏移量 0 处。

我似乎无法获得正确的 SymbolTableIndex ...

每个 PE 部分都有重定位表。 每个搬迁记录是:

type Reloc struct {
    VirtualAddress   uint32
    SymbolTableIndex uint32
    Type             uint16
}

VirtualAddress 在这个重定位表所属的PE段中偏移了数据需要调整的地方。 类型决定如何调整它。 SymbolTableIndex 指向一个“符号”,其中包含外部链接器产生重定位值所需的所有信息。 SymbolTableIndex 是 PE“符号表”的索引。 您可以在 pecoff.doc 中了解它,但它存在于所有 PE 部分完成并由 PointerToSymbolTable 指向的某个地方。 您应该能够使用 objdump 查看 C obj 文件的 PE 符号表并执行类似操作。 您可以查看 pe.go 中的 addpesymtable 以了解我们如何编写符号表。 也许 Ian 是正确的,它指向整个 .text 部分,但无论如何您都必须在符号表中写入对应的条目。

亚历克斯

一位同事帮助我取得了额外的进展。 他添加了一些逻辑来调整现有的重定位代码以使用 .ctors 部分。 现在看来,我们在正确的位置拥有正确的数据:

$ objdump -r -j .ctors tmpo\go.o 

tmpo\go.o:     file format pe-x86-64

RELOCATION RECORDS FOR [.ctors]:
OFFSET           TYPE              VALUE 
0000000000000000 R_X86_64_64       _rt0_amd64_windows_lib-0x000000000004f430

这看起来与 gcc 输出的内容略有不同,因为重定位的值是“.text”。 但是,如果我了解@ianlancetaylor ,那么该值只是一个名称,并不重要。 这正是我们要重定位的符号,地址正确(0x4f430)。

出于某种原因,如果我将 Go 生成的目标文件链接到 .a 文件中,则 .ctors 部分不会被拾取。 但是,如果我使用 gcc 将 3 个 Go 文件与 C 驱动程序链接,则 .ctors 数据确实会被拾取:

$ gdb --silent  <dump_ctorlist.txt
(gdb) Reading symbols from test.exe...done.
(gdb) 
0x4cb5f0 <___CTOR_LIST__>:      0xffffffffffffffff      0x000000000089ee40
0x4cb600 <___CTOR_LIST__+16>:   0x00000000004cb5e0      0x0000000000000000
0x4cb610 <___DTOR_LIST__>:      0xffffffffffffffff      0x0000000000000000
0x4cb620:       Cannot access memory at address 0x4cb620

美中不足的是,重定位发生错误。 0x000000000089ee40 是应该调用 _rt0_amd64_windows_lib 的条目(0x00000000004cb5e0 = .text 部分中的 register_frame_ctor,gcc 初始化代码的一部分)。

然而地址是错误的。 GDB 报告:

0x44fa10 <_rt0_amd64_windows_lib>

所以我希望该列表中的条目是 0x44fa10。

我猜我们指定的重定位错误。 代码在这里: https ://github.com/nadiasvertex/go/commit/39b73bf8ee24f869ba15ec29c221b1d69dac7478

任何指导都会有所帮助。

它可能会或可能不会帮助您,但是:我曾经还必须实现一些与重定位相关的代码,用于为 Win32 for Go 构建“.rsrc”部分(嵌入图标等)的内容(该工具构建一个 . o/.syso 文件)。 不幸的是,我已经不记得计算的含义了(不确定我在编写这些东西时是否真的理解它们),但如果它可以以任何方式帮助你,相关代码似乎在这里:
https://github.com/akavel/rsrc/blob/ba14da1f827188454a4591717fff29999010887f/coff/coff.go#L386-L387

快速解释l.371-393 中的 Walk 块如何工作:它顺序地和递归地 (DFS) 遍历Coff 结构(表示 Coff 输出文件的“草图”/模板),并提供一个表示“当前Coff 结构中的路径”(例如“/Dir/Dirs[1]”表示 coff.Dir.Dirs[1])到闭包,而“offset”包含该字段在最终输出文件中的字节位置(它通过 l.392 中的freezeCommon2 调用而前进)。 闭包更新最终文件中必须根据此偏移量填充的各个字段的内容。

不确定这是否对您有帮助; 抱歉,如果是后者,但以防万一。

我和我的同事已经明白,_rt0_amd64_windows_lib 初始化程序是从链接到 Go 生成的静态库的可执行文件中正确调用的。 这很令人兴奋。

现在的问题是可执行文件段错误。 我将简要解释我认为它发生在哪里,也许可以提供一些指导,说明什么是“正确”的事情。

首先,我知道我们必须以某种方式传递命令行参数。 在 Windows 上,这似乎非常复杂,所以我什至还没有处理它。 当我们进入初始化例程时,保存来自 Windows 内核的信息的寄存器已被覆盖。

所以目前我正在初始化我们将这些东西拉回0x0的内存位置。 也许这很糟糕,但如果我将它们设置为空,运行时中的代码似乎应该跳过处理 args。

运行时进入 x_cgo_sys_thread_create(),它接收 _rt0_amd64_windows_lib_go 作为生成的函数。 pthread_create 被赋予此值并返回成功。

走出去,我们很好地退出了 __do_global_ctors() 函数。 与此同时,初始化的线程开始运行。 通过跳转到地址 0x00000000ffffffff,该线程很快就结束了段错误。 显然这是错误的。

有什么建议可以让我缩小范围为什么它会进入杂草?

首先,我知道我们必须以某种方式传递命令行参数。 在 Windows 上,这似乎非常复杂,

在 Windows 上,命令行参数由内核处理。 您的可执行文件不需要执行任何特定操作,只需在需要时检索它们(使用适当的 Windows API)。 Go 运行时确实在 goenvs 函数中。 这对于 Go(纯和 cgo)可执行文件都是正确的。 我不知道 Go DLL 是做什么的。

有什么建议可以让我缩小范围为什么它会进入杂草?

我不知道 Go DLL 中会发生什么。 也许其他人会有所帮助。

亚历克斯

@alexbrainman需要明确的是,这不是 .dll。 这是一个静态库。 目前我们可以生成 C 运行时可以链接的代码,它运行运行时初始化程序。 运行时初始化程序在某处崩溃,但没有回溯,因为它在地址空间中向上跳转到随机代码。

仍在进行运行时初始化。 我已经调整了程序集以避免尝试设置命令行参数。 我现在得到了一个干净的回溯。 看起来 init 线程在进入运行时初始化部分之前就中止了。

传递给 pthread_create 的地址看起来是正确的。 当我将代码更改为直接分支到运行时初始化程序(而不是生成线程)时,它似乎可以正确完成。 所以看起来它与线程的错误设置是隔离的。

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1280.0x670]
0x000007fefd921520 in msvcrt!_HUGE () from C:\Windows\system32\msvcrt.dll
(gdb) bt
#0  0x000007fefd921520 in msvcrt!_HUGE () from C:\Windows\system32\msvcrt
#1  0x00000000004c7f57 in pthread_create_wrapper ()
#2  0x000007fefd89415f in srand () from C:\Windows\system32\msvcrt.dll
#3  0x000007fefd896ebd in msvcrt!_ftime64_s ()
   from C:\Windows\system32\msvcrt.dll
#4  0x0000000076fb5a4d in KERNEL32!BaseThreadInitThunk ()
   from C:\Windows\system32\kernel32.dll
#5  0x00000000770eb831 in ntdll!RtlUserThreadStart ()
   from C:\Windows\SYSTEM32\ntdll.dll

您可能想要调用某些 Windows 特定函数,例如 _beginthread 或 _CreateThread,而不是 pthread_create。

我还想警告您有关使用 gdb 的信息。 Go 链接器不会将 dwarf 信息写入目标文件(请参阅问题 #10776)。

亚历克斯

事实证明,我从 Linux 借来的运行时代码使用的参数传递协议与 Windows 上的 gcc 使用的不同。 修复后,一切正常。 我的简单测试运行完成,没有错误。

更改为https://github.com/nadiasvertex/go/commit/1fc1f59fceb1794a7c4723742434d30f9ed5c14chttps://github.com/nadiasvertex/go/commit/81e3aa8a91b98d255fe872aee6401da356d38fe6

我当前的构建脚本很尴尬,因为还有两个错误需要解决:

  1. 在“ar”完成之前删除临时.o文件(或者在“ar”读取它们之前可能没有完成写入它们。)
  2. 出于某种原因,当 .o 在 .a 中时,'ld' 会忽略 .ctor 部分,但当它是原始 .o 时,它可以正常工作。 换句话说,“ld -o test driver.o test.a”失败,但“ld -o test driver.o tmpo/go.o tmp0/000001.o tmpo/000000.o”工作得很好。

我想知道链接器是否对(1)使用了任何类型的异步,而对于(2)我不知道。 任何想法都会有所帮助。

我进行了一些测试,看起来上面的第二个错误实际上已修复。 可能 .o 文件中的错误数据导致链接器忽略我们编写的任何内容,现在它是正确的,它可以工作。

上面的第一个问题仍然是一个真正的问题。 如果我跑

go build -buildmode=c-archive -ldflags="-tmpdir .\tmpo" -o test.a calc.go
gcc -o test test_driver.o test.a -lws2_32 -lntdll

输出完美,一切正常,彩虹和独角兽。

如果我跑

go build -buildmode=c-archive  -o test.a calc.go
gcc -o test test_driver.o test.a -lws2_32 -lntdll

然后我得到:

test.a(000000.o): In function `Sum':
C:/Users/CHRIST~1/AppData/Local/Temp/go-build304995930/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to `_cgoexp_f070ceaf1261_Sum'
C:/Users/CHRIST~1/AppData/Local/Temp/go-build304995930/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to `crosscall2'
test.a(000000.o):calc.cgo2.c:(.rdata$.refptr._cgoexp_f070ceaf1261_Sum[.refptr._cgoexp_f070ceaf1261_Sum]+0x0): undefined reference to `_cgoexp_f070ceaf1261_Sum'
collect2.exe: error: ld returned 1 exit status

这类似于我上周描述的一个问题。 objdump 输出表明 go.o 文件显然没有被完全消耗:

$ objdump -a test.a
In archive test.a:
objdump: go.o: File format not recognized

000000.o:     file format pe-x86-64
rw-rw-rw- 0/0   4165 Dec 19 13:53 2015 000000.o


000001.o:     file format pe-x86-64
rw-rw-rw- 0/0  16188 Dec 19 13:53 2015 000001.o

我有问题 (1) 的解决方法,解决方法很愚蠢,但看起来很稳定。 在 'ar' 生成 C 存档之前,我添加了对 os.Stat() 的调用以查看 go.o 是否存在,如果存在,是否为空。 在我添加了这个调用之后,构建每次都能完美运行。

代码在这里: https ://github.com/nadiasvertex/go/commit/0df614097d48f091d8984db0decfce6edefcb0ea

此时,问题 #13494 的所有工作都应该完成。 当然,它需要审查和测试。 我将阅读“如何贡献”的内容来提交补丁。 如果时间允许,我将回到 c-shared 错误,看看需要什么。

我为上述问题提交了一个补丁,并开始在 c-shared 上工作。 生成 .dll 的初始工作已完成,但它引用了意外文件。 例如,当我运行我的测试设备时,它说它找不到 a.out.exe。 如果我将输出文件“test.dll”重命名为“a.out.exe”,测试系统就会运行并崩溃。 依赖关系可能只是链接器如何生成输出的产物。 如果我在生成期间使用最终输出名称,它可能会没问题。

段错误发生在 runtime.rt0_go,这里是上下文:

   0x000000006ab8ba86 <+246>:   mov    %gs:0x28,%rbx
=> 0x000000006ab8ba8f <+255>:   movq   $0x123,%fs:(%rbx)
   0x000000006ab8ba97 <+263>:   mov    0x852da(%rip),%rax        # 0x6ac10d78 <runtime.m0+88>
   0x000000006ab8ba9e <+270>:   cmp    $0x123,%rax
   0x000000006ab8baa4 <+276>:   je     0x6ab8baad <runtime.rt0_go+285>

寄存器是:

(gdb) info reg
rax            0x7a318c         8008076
rbx            0x6ac10d78       1791036792
rcx            0x6ac10a20       1791035936
rdx            0x0              0
rsi            0x6ab8d850       1790498896
rdi            0x6ac10d78       1791036792
rbp            0x0              0x0
rsp            0x99fed0         0x99fed0
r8             0x7fffffdb000    8796092870656
r9             0x62             98
r10            0x100000         1048576
r11            0x99fa98         10091160
r12            0x0              0
r13            0x0              0
r14            0x0              0
r15            0x0              0
rip            0x6ab8ba8f       0x6ab8ba8f <runtime.rt0_go+255>
eflags         0x10202          [ IF RF ]
cs             0x33             51
ss             0x2b             43
ds             0x0              0
es             0x0              0
fs             0x0              0
gs             0x0              0

我真的不确定这里的预期值应该是什么,或者当 c-archive 正确时为什么它们是错误的。

代码在这里: https ://github.com/nadiasvertex/go/commit/4a4e02259f7f7f3926c463b36b976dbfdbe79dde

这看起来像是 rt0_go 中的测试,表明 TLS 已正确设置。 我猜它实际上没有正确设置!

关于 TLS 应该做什么的问题可能超出了我的薪酬等级,因为它似乎是该语言的 ABI 的基础。 是否在某处讨论过选项? 如果没有,我可以以某种方式促进这样的对话吗? 在就应该做什么达成共识之前,我继续努力似乎没有多大意义。

@nadiasvertex我看不出它怎么可能在 runtime.rt0_go 中崩溃。 我可以亲眼看看吗? 你怎么让它崩溃?

但是是的,如果我们想让 Go DLL 由 Go 可执行文件加载,我们将不得不改变我们执行 TLS 的方式。 从我在网上收集到的。 我们可以使用 PE 文件工具(在 pecoff.doc 中搜索 .tls),或者我们可以使用 TlsGetValue Windows API。 鉴于您使用 gcc 生成 DLL,PE 文件摆弄不是一种选择,除非有人知道如何安排 gcc 来帮助解决这个问题。 这给我们留下了 TlsGetValue。 我担心 TlsGetValue 调用与我们现在所做的相比可能会很昂贵,但我不知道有什么替代方法。 也许其他人也会发表评论。

亚历克斯

https://github.com/nadiasvertex/go/tree/win-shared有代码。 只需构建一个 .dll 并从 C 函数调用 Go 函数。

@nadiasvertex我试过你的 win-shared 分支。

package main

//export Foo
func Foo() {
    println("foo")
}

func main() {
}

并建立

go build --buildmode=c-shared -o go.dll
nm -s go.dll

https://gist.github.com/mattn/ad8ed59a3efe86db2278

似乎没有导出Foo

我在https://github.com/nadiasvertex/go/tree/win-shared上运行 make.bat ,但它失败了:

runtime/cgo
# runtime/cgo
runtime\cgo\gcc_libinit_windows.c:9:21: fatal error: pthread.h: No such file or directory
 #include <pthread.h>
                     ^
compilation terminated.

如何解决此故障?

我的海湾合作委员会:

c:\dev\winshared\src>gcc --version
gcc (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

亚历克斯

我在http://tdm-gcc.tdragon.net使用了 TDM GCC 发行版,它只是
作品。 如果你不能包含 pthread,你可能没有安装它。
如果您使用的是 mingw,您可能需要确保已安装该软件包。

2015 年 12 月 22 日,星期二,晚上 10:12,Alex Brainman [email protected]
写道:

我在https://github.com/nadiasvertex/go/tree/win-shared上运行 make.bat ,
但它失败了:

运行时/cgo

运行时/cgo

runtime\cgo\gcc_libinit_windows.c:9:21:致命错误:pthread.h:没有这样的文件或目录
#包括
^
编译终止。

如何解决此故障?

我的海湾合作委员会:

c:\dev\winsharedsrc>gcc --version
海合会 (GCC) 4.9.1
版权所有 (C) 2014 Free Software Foundation, Inc.
这是免费软件; 查看复制条件的来源。 没有
保修单; 甚至不考虑适销性或特定用途的适用性。

亚历克斯


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -166794526。

应该可以在不使用 pthread 的情况下编写 runtime/cgo/gcc_libinit_windows.c。 它可以(并且应该)改用普通的 Windows 调用。

“普通”Windows 调用是什么意思? msvcrt,或者直接win32
应用程序接口?

2015 年 12 月 23 日星期三下午 1:12 Ian Lance Taylor [email protected]
写道:

应该可以写 runtime/cgo/gcc_libinit_windows.c 没有
完全使用 pthread。 它可以(并且应该)使用普通的 Windows 调用
反而。


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -166961224。

$GOROOT/src/runtime/cgo/gcc_windows_amd64.c 中的_cgo_sys_thread_start 使用_beginthread。 也许也这样做。

您还可以使用 Windows CreateThread 函数。 我不知道在这里做什么是正确的,因为我对 gcc 了解不多。

亚历克斯

这不是真正的 GCC 问题。 您可以忽略 gcc_libinit_windows.c 的名称以“gcc”开头的事实。 关键是,该文件包含 C 代码,并使用 C 编译器进行编译。 它定义了三个函数

x_cgo_sys_thread_create 必须启动一个运行 func 的新线程,并将 arg 传递给它。 这是 win32 CreateThread 函数或 Visual Studio _beginthread 函数。

x_cgo_wait_runtime_init_done 必须等到 x_cgo_notify_runtime_init_done 被调用。 x_cgo_notify_runtine_init_done 必须让 x_cgo_wait_runtine_init_done 执行。 我不太了解 Windows 同步,但我猜想这可以使用 win32 CreateSemaphore 和 WaitForSingleObject 函数来完成。

我将对这段代码和 c-archive 补丁进行这些调整。

我已经删除了 pthread 特定的代码,并将其推送到官方树中的 c-archive 补丁中,以及我的 Go 的 github fork 的 win-shared 分支中。 如果您想再看一下错误,您可能会走得更远。 谢谢!

更改在这里: https ://github.com/nadiasvertex/go/commit/d88e7e06bea1c7f0a33f87f6cddbdbe149a80619
代码在这里: https ://github.com/nadiasvertex/go/tree/win-shared

运行 make.bat 仍然失败:

...
runtime/cgo
# runtime/cgo
runtime\cgo\gcc_libinit_windows.c:9:21: fatal error: pthread.h: No such file or directory
 #include <pthread.h>
                     ^
compilation terminated.
runtime/race
testing/iotest
...

我希望我运行的是正确的版本:

c:\dev\winshared\src>git rev-parse HEAD
d88e7e06bea1c7f0a33f87f6cddbdbe149a80619

c:\dev\winshared\src>git status
On branch win-shared
Your branch is up-to-date with 'origin/win-shared'.

nothing to commit, working directory clean

c:\dev\winshared\src>

亚历克斯

当我从我的 c-archive 分支迁移补丁时,我犯了一个错字。 我更新了 gcc_libinit.c 而不是 gcc_libinit_windows.c。 代码已经更新,我验证我这次修补了正确的文件。

代码已更新

我现在可以构建 Go。 谢谢你。

只需构建一个 .dll 并从 C 函数调用 Go 函数。

我不知道该怎么做。 我用了

go build --buildmode=c-shared -o go.dll

命令并创建一个 go.dll 文件。 你如何从 C 函数中调用它? 命令是什么? 谢谢你。

亚历克斯

我试过使用 TlsGetValue

https://github.com/alexbrainman/winapi/commit/a05e0a78114d5bd0464c0f9c7f27f48e89b218bd

它工作正常。 TlsGetValue

(gdb) disas
Dump of assembler code for function TlsGetValue:
=> 0x7c8097e0 <+0>:     mov    %edi,%edi
   0x7c8097e2 <+2>:     push   %ebp
   0x7c8097e3 <+3>:     mov    %esp,%ebp
   0x7c8097e5 <+5>:     mov    %fs:0x18,%eax
   0x7c8097eb <+11>:    mov    0x8(%ebp),%ecx
   0x7c8097ee <+14>:    cmp    $0x40,%ecx
   0x7c8097f1 <+17>:    jae    0x7c845054 <SetUnhandledExceptionFilter+367>
   0x7c8097f7 <+23>:    andl   $0x0,0x34(%eax)
   0x7c8097fb <+27>:    mov    0xe10(%eax,%ecx,4),%eax
   0x7c809802 <+34>:    pop    %ebp
   0x7c809803 <+35>:    ret    $0x4
End of assembler dump.
(gdb)

看起来相当小,所以它可能是我们的一个选择(我没有检查 amd64 版本)。 我还发现这篇文章https://msdn.microsoft.com/en-us/library/windows/desktop/ms686997 (v=vs.85).aspx 关于如何在 DLL 中做同样的事情,所以我们可以使用那也是。

这是我们想做的事情吗? 也许有一种方法可以通过 gcc 处理 TLS,也许会更简单。

亚历克斯

PS:我们以前在运行时包中定义了get_tls asm宏,但现在它只是说

#define get_tls(r)      MOVL TLS, r

MOVL TLS, r是如何实现的?

OT:我真的希望你在 1.6 上取得成功,并佩服你对追踪所有小问题的坚持。

@alexbrainman

我有一个看起来像这样的“驱动程序”.c 文件:

#include "test.h"
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv) {
  for(int i=0; i<100; i++) {
     for(int j=0; j<100; j++) {
         GoInt v1 = Sum(i,j);
         GoInt v2 = i+j;

         printf("Sum=%lld expect %lld\n", v1, v2);

         if (v1!=v2) {
            abort();
         }
     }
  }
  return 0;
}

我有一个如下所示的测试 .go 文件:

package main

import "C"


//export Sum
func Sum(x, y int) int {
        return x+y
}

func main() {

}

我使用以下命令构建:

go build -buildmode=c-shared -o test.dll test.go
gcc -c -o test_driver.o test_driver.c
gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll
copy test.dll a.out.exe

那是我遇到 TLS 问题的时候。

我想从哪里得到 test.h 文件?

亚历克斯

它是由 cgo 生成的。

在 2016 年 1 月 3 日星期日晚上 7:59,Alex Brainman [email protected]写道:

我想从哪里得到 test.h 文件?

亚历克斯


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -168561360。

我不得不稍微修改你的 C 程序(我的 gcc 无法处理 for() 循环),现在一切都建立起来了:

c:\dev\src\issues\issue11058>dir
 Volume in drive C has no label.
 Volume Serial Number is D2A1-D2A1

 Directory of c:\dev\src\issues\issue11058

04/01/2016  12:50 PM    <DIR>          .
04/01/2016  12:50 PM    <DIR>          ..
04/01/2016  12:45 PM    <DIR>          .hg
04/01/2016  12:45 PM               115 test.go
04/01/2016  12:47 PM               246 test_driver.c
               2 File(s)            361 bytes
               3 Dir(s)   3,758,317,568 bytes free

c:\dev\src\issues\issue11058>type test.go
package main

import "C"


//export Sum
func Sum(x, y int) int {
        return x+y
}

func main() {

}
c:\dev\src\issues\issue11058>type test_driver.c
#include "test.h"
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv) {
        int i=2, j=3;
        GoInt v1 = Sum(i,j);
        GoInt v2 = i+j;

        printf("Sum=%lld expect %lld\n", v1, v2);

        if (v1!=v2) {
        abort();
        }
        return 0;
}
c:\dev\src\issues\issue11058>go build -buildmode=c-shared -o test.dll test.go

c:\dev\src\issues\issue11058>gcc -c -o test_driver.o test_driver.c

c:\dev\src\issues\issue11058>gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll

c:\dev\src\issues\issue11058>

但是当我运行test-lib.exe时,我收到此错误https://gist.github.com/alexbrainman/72057484f5a42821ca86#file -cgoerror-jpg。

亚历克斯

go 链接器生成名为 a.out 的 .dll。 然后重命名它。 这
在 Windows 上不起作用。 我会解决这个问题,但现在你还必须复制
test.dll 到 a.out。 我忘了在说明中提到这一点。

在 2016 年 1 月 3 日星期日晚上 9:30,Alex Brainman [email protected]写道:

我不得不稍微修改你的 C 程序(我的 gcc 无法处理 for() 循环),
现在一切都在构建:

c:\devsrc\issues\issue11058>目录
驱动器 C 中的卷没有标签。
卷序列号为 D2A1-D2A1

c:\devsrc\issues\issue11058 目录

2016 年 4 月 1 日下午 12:50

.
2016 年 4 月 1 日下午 12:50..
2016 年 4 月 1 日下午 12:45.hg
2016 年 4 月 1 日下午 12:45 115 test.go
2016 年 4 月 1 日下午 12:47 246 test_driver.c
2 个文件 361 字节
3 Dir(s) 3,758,317,568 字节空闲

c:\devsrc\issues\issue11058>类型 test.go
包主

导入“C”

//导出总和
func Sum(x, y int) int {
返回 x+y
}

功能主要(){

}
c:\devsrc\issues\issue11058>类型 test_driver.c

包括“test.h”

包括

包括

int main(int argc, char **argv) {
诠释 i=2, j=3;
GoInt v1 = Sum(i,j);
GoInt v2 = i+j;

    printf("Sum=%lld expect %lld\n", v1, v2);

    if (v1!=v2) {
    abort();
    }
    return 0;

}
c:\devsrc\issues\issue11058>go build -buildmode=c-shared -o test.dll test.go

c:\devsrc\issues\issue11058>gcc -c -o test_driver.o test_driver.c

c:\devsrc\issues\issue11058>gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll

c:\devsrc\issues\issue11058>

但是当我运行 test-lib.exe 时,我得到了这个错误
https://gist.github.com/alexbrainman/72057484f5a42821ca86#file -cgoerror-jpg
.

亚历克斯


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -168567443。

你还必须复制
test.dll 到 a.out。

copy test.dll a.out.exe可以解决问题。 谢谢你。 我现在需要调试你原来的问题。

亚历克斯

@nadiasvertex我可以在 runtime.rt0_go 中重现崩溃。 它在https://github.com/nadiasvertex/go/blob/master/src/runtime/asm_amd64.s#L112线上崩溃。 我已经使用 objdump 来反汇编 test.dll。 这是有趣的部分:

    6dc8bae3:   e8 18 38 00 00          callq  6dc8f300 <runtime.settls>
    6dc8bae8:   65 48 8b 1c 25 28 00    mov    %gs:0x28,%rbx
    6dc8baef:   00 00 
    6dc8baf1:   64 48 c7 03 23 01 00    movq   $0x123,%fs:(%rbx)
    6dc8baf8:   00 
    6dc8baf9:   48 8b 05 78 e2 07 00    mov    0x7e278(%rip),%rax        # 6dd09d78 <runtime.m0+0x58>

如果将它与简单的 Go 可执行文件(我的 go.exe)的类似部分进行比较:

  4e2551:   e8 ea 3c 00 00          callq  4e6240 <runtime.settls>
  4e2556:   65 48 8b 1c 25 28 00    mov    %gs:0x28,%rbx
  4e255d:   00 00 
  4e255f:   48 c7 83 00 00 00 00    movq   $0x123,0x0(%rbx)
  4e2566:   23 01 00 00 
  4e256a:   48 8b 05 27 9f 79 00    mov    0x799f27(%rip),%rax        # c7c498 <runtime.m0+0x58>

你会看到g(BX)被翻译成0x0(%rbx) ,而你的新 test.dll 有%fs:(%rbx) 。 我怀疑翻译是你的问题。 我不知道g(BX)是如何转换的,也许其他人会提供帮助。

请注意,我不确定您是否应该花时间解决这个问题。 因为,即使您要使现有代码工作,您也必须想出一种不同的方法在 DLL 中执行 TLS(参见前面的讨论)。

亚历克斯

这种一般的转换发生在 cmd/internal/obj/x86/asm6.go 中。 我不得不承认,我不知道在这种情况下这种特定的转变是如何发生的。

谢谢@ianlancetaylor的建议。 我会让@nadiasvertex决定在这里做什么。

亚历克斯

目前有什么解决方法吗? 也许是一种手动方法,让我们生成可以手动与 gcc 链接在一起的目标文件?

@nadiasvertex go.o 在写入后是否曾明确关闭? 我想知道这是否可能是您需要执行 os.Stat 解决方法以使其刷新到磁盘的原因。

@jtsylve目前无法直接从 Go 生成功能性 .dll
Windows 上的软件包。 但是,Windows 的 -buildmode=c-archive 补丁是
完成验收过程。 该补丁适用于我
我用它做了有限的测试。 您可以下载补丁并应用
它到编译器源,然后用它生成一个静态库。 你
然后可以编写一个 .c 文件,该文件公开 .dll 入口点并调用
静态库,并从该 .c 文件和 .a 文件生成一个 .dll 。

看到有人在更大范围内测试它会很有用,所以如果你
想试试这个我很乐意帮助你让它工作。

2016 年 1 月 7 日星期四下午 12:51,Joe Sylve [email protected]写道:

@nadiasvertex https://github.com/nadiasvertex是明确的 go.o
写入后关闭? 我想知道这是否是你需要的原因
执行 os.Stat 解决方法以使其刷新到磁盘。


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -169754620。

@alexbrainman我不确定我在决定什么。 共享库支持在我的环境中非常重要,因此决定_不_这样做对我来说并不是一个真正的选择。

如果您要我追踪编译器问题并找出它行为不端的原因,那很好。 只要我可以向可能知道答案的人提问。 :-) 另外,我现在的工作量很大,所以我可能会慢慢行动。

我不确定我要决定什么。 ...

您需要修复 runtime.rt0_go 中的崩溃。 您可以通过更改 cmd/internal/obj/x86/asm6.go 中的一些代码以在 runtime.rt0_go 的可执行文件和 DLL 版本中生成相同的代码来修复崩溃。 但这不是完整的解决方案。 如果你重读之前关于 TLS 的讨论,我们都同意 Go DLL 不能像当前 Go 可执行文件那样对 TLS 使用相同的方法。 如果 Go DLL 由 Go 可执行文件加载,它们将相互踩踏。 让第一个 Go DLL 运行也许是可以的,但最终我们需要在这里做一些不同的事情。 这里有更大的鱼要炸。

只要我可以向可能知道答案的人提问。

每个人都很乐意帮助您了解他们所知道的。

我可能会慢慢移动。

目前没有其他人愿意这样做。 这完全取决于你。

亚历克斯

@nadiasvertex我能够构建并使用 win-archive 生成​​静态库,但无法构建 win-shared。 构建冻结,如下所示。

C:\GoP2\src>make.bat
##### Building Go bootstrap tool.
cmd/dist


有任何想法吗?

是的。 win-shared 代码被破坏,因为编译器没有发出正确的 TLS 原语。 这会导致您遇到的死锁。 究竟什么是正确的原语是我必须调查的问题。

看到有人在更大范围内测试它会很有用,所以如果你
想试试这个我很乐意帮助你让它工作。

@nadiasvertex我们将对其进行测试。

我可以在https://github.com/golang/go/tree/release-branch.go1.6之上重新设置https://github.com/nadiasvertex/go/tree/win-shared或者您的更改不适合1.6吗?

gerrit 中有一个补丁等待,链接到 c-archive 版本
这张票。 该补丁有许多基于 1.6 的修复。 理想情况下你
将需要 1.6,应用该补丁,然后在此基础上重新设置此代码。

2016 年 2 月 13 日星期六,上午 12:33 Bruno Clermont [email protected]
写道:

看到有人在更大范围内测试它会很有用,所以如果你
想试试这个我很乐意帮助你让它工作。

@nadiasvertex https://github.com/nadiasvertex我们将对其进行测试。

我可以重新设置https://github.com/nadiasvertex/go/tree/win-shared
https://github.com/golang/go/tree/release-branch.go1.6或您的顶部
更改不适合 1.6?


直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -183592803。

仅供参考,我在 Go repo 的 fork 中创建了一个新分支,其中包含基于 1.6 的最新 c-archive 补丁,以及允许 c-shared 运行所需的初始(最小)更改。 它还没有“固定”,但至少是人们可以测试的。

https://github.com/nadiasvertex/go/tree/win-shared-1.6

@nadiasvertex我们在构建时遇到了这个错误( "c:\go-win-shared-1.6\bin\go.exe" build -x -buildmode=c-shared ):

...
mkdir -p $WORK\runtime\cgo\_obj\
cd C:\go-win-shared-1.6\src\runtime\cgo
CGO_LDFLAGS="-g" "-O2" "-lmsvcrt" "-lm" "-mthreads" "C:\\go-win-shared-1.6\\pkg\\tool\\windows_amd64\\cgo.exe" -objdir "C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\" -importpath runtime/cgo -import_runtime_cgo=false -import_syscall=false "-exportheader=C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\_cgo_install.h" -- -I "C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\" -Wall -Werror cgo.go
go build runtime/cgo: C:\go-win-shared-1.6\pkg\tool\windows_amd64\cgo.exe: exit status 2
    go version go1.6rc2 windows/amd64
    os:
        Windows
    os_family:
        Windows
    osfullname:
        Microsoft Windows 8.1 Pro
    osmanufacturer:
        Microsoft Corporation
    osrelease:
        8
    osversion:
        6.3.9600
"C:\MinGW\bin\gcc.exe" --version
    gcc.exe (GCC) 4.9.3
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我只能在c-archive中使用它...

但我只尝试从 MacOSX 主机交叉构建到 Windows 32 和 64 位。

c共享失败:

go build -o libsimple.a -buildmode=c-shared lib.go
# runtime
asm: invalid instruction: 00019 (/path/to/sandbox/go/src/runtime/sys_windows_386.s:251) SUBL    $runtime.callbackasm(SB), AX, R???1

谢谢。 我知道这仍然是坏的。 只有 c-archive 真正有效。

生成的.h文件使用 GCC 内部类型:

typedef __SIZE_TYPE__ GoUintptr;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

_Complex__SIZE_TYPE__ ,有人成功地用 VC++ 构建.a文件吗?

_Complex 不是 GCC 内部类型,它是 C99 的复杂类型。
__SIZE_TYPE__ 有点特定于 GCC,但我们可能
可以改用 size_t 。

但无论如何,我不认为支持 VC++ 是最重要的,
尤其是因为 VC++ 不支持 C99。

@minux ,VC++ 2015 几乎完全支持 C99。

C99 一致性 Visual Studio 2015 完全实现了 C99 标准库,但依赖于 Visual C++ 编译器尚不支持的编译器功能的任何库功能除外(例如,未实施)。

https://msdn.microsoft.com/en-us/library/hh409293.aspx

@ianlancetaylor随着 c-archive 补丁的结束,我开始更仔细地研究 Windows 的 c-shared 内容。 我重新阅读了这里的讨论,我有一些笔记和问题。

我在 src/cmd/internal/obj/x86/asm6.go 中看到:

3610: // Windows TLS base is always 0x14(FS)

3690: // Windows TLS base is always 0x28(GS)

评论谈论“TLS 初始初始化”和直接访问系统。 在我看来,这就像 Windows 将是一个“TLS 初始初始化”系统,因为大概有人必须初始化 TLS 区域,然后将其存储到上面的那些位置。 那是对的吗?

最后,我认为问题是这样的:

Windows 有一个映射到 FS(32 位)和 GS(64 位)的线程环境块。 此块中的许多“插槽”都针对某些值进行了映射。 Windows 在 FS:0x2C 和 GS:0x58 有一个称为“线程本地存储阵列”的 TLS 约定,但 Go 决定在 FS:0x14 和 GS:0x28 使用所谓的“任意”插槽来管理自己的 TLS .

令人担忧的是,其他代码可能会出于自己的目的使用“任意”插槽,从而意外破坏运行时的 TLS 空间并导致灾难性故障。

那是对的吗?

我想简单的解决方案是“做 C 做的事情,要么调用 Win API 函数,要么重新实现算法。”

显然,我宁愿只发出对 API 的调用。

但是,我看到了一些问题,我不清楚它们是如何解决的。

  1. 特别提到了 DLL,但我不明白为什么它们比静态库是一个更大的问题。 如果踩踏是由另一个运行时完成的,那么它是 DLL 还是静态库又有什么关系呢?
  2. Windows 上的 GCC 似乎使用一个函数来获取 TLS 地址。 但是,在我在 Go 中查看的地方,“TLS”似乎是一个“命名”位置,可以转换为上面的内容。 考虑使用 Win32 API 函数来处理 TLS 是否合理?

如果使用 API 是合理的,似乎最可靠的实现是使用 TlsAlloc 和 TlsFree 进行 TLS 插槽管理,并使用 TEB 中的普通 FS 和 GS TLS 数组来访问值(为了性能)。我看到了大意是 Go 并没有真正使用 TLS。 因此,如果是这种情况,对性能的影响可能非常小。

谢谢。

令人担忧的是,其他代码可能会出于自己的目的使用“任意”插槽,从而意外破坏运行时的 TLS 空间并导致灾难性故障。

那是对的吗?

的种类。 如果某些“其他代码”使用相同的 TLS 插槽(但还没有人抱怨这一点),您所描述的现在可能会发生。 这肯定会在未来发生,如果 Go 程序会加载 Go dll。 他们都使用相同的插槽(除非我们解决了这个问题)。

特别提到了 DLL,但我不明白为什么它们比静态库是一个更大的问题。 如果踩踏是由另一个运行时完成的,那么它是 DLL 还是静态库又有什么关系呢?

我看到的问题是关于两个独立的 Go 运行时冲突。 第二个 Go 运行时是否存在于 DLL 或静态库中并不重要。 如果 Go DLL 或静态库是由 Go 可执行文件以外的任何东西加载的,那么你应该没问题。 但是如果 Go 可执行文件加载 Go DLL 或静态库,它将失败。

亚历克斯

谢谢,亚历克斯,这是有道理的。 这引发了其他问题。

  1. 由于看起来每个 DLL 都有自己的 Go 运行时副本,因此它无法尊重 GOMAXPROCS 变量。 主进程和每个 DLL 将加载生成最多 GOMAXPROCS 系统线程。 这可以接受吗?
  2. 似乎它们也会有单独的 GC,那么运行时是否“知道”它何时调用 DLL 并合谋使传递的内存块安全?
  3. 在 Go 可执行文件加载 Go DLL 并让它们都依赖于运行时的同一个副本的情况下,是否有任何计划简单地使运行时本身成为一个 DLL?

对于 c-shared,每个单独的库都将拥有自己的 Go 运行时副本。 这是正常和预期的。 每个库都应被视为一个单独的独立单元。

你提到主进程将使用它自己的 GOMAXPROCS。 使用 c-shared 时,期望您正在创建要加载到 C 程序中的 DLL。 创建要加载到 Go 程序中的 DLL 意味着插件接口,该接口尚未在任何地方实现。

运行时不知道它何时调用 DLL。 调用 c 共享 DLL 就像调用任何 C DLL 一样。 如果您尝试从 Go 调用它,则需要使用 cgo。

@nadiasvertex每个 DLL 都可以使用 IMAGE_TLS_DIRECTORY 拥有自己的链接器提供的 TLS 部分。 它用于__declspec(thread) MSVC 扩展

这是一篇描述它的旧博客文章(使用 32 位用户空间); http://blog.dkbza.org/2007/03/pe-trick-thread-local-storage.htmlhttp://www.nynaeve.net/?p=183 (有很多博客在谈论它)

这消除了使用 TEB 的需要,我同意它不太安全。 事实上,所有的 golang .exe 也可以使用它,完全不需要其他方式的 TLS。

@ianlancetaylor ,谢谢。

@maruel ,我知道 MSVC 扩展。 但是,这仅在使用 Go 当前不支持的 MSVC 工具链时有效。 它需要编译器、链接器和 DLL 加载器之间非常具体的合作。 此外,这种机制对于 Vista 之前的 DLL 根本不起作用,甚至在 Vista 中也可能无法正确实现 (http://www.nynaeve.net/?p=189)。 这可能使它对支持 Windows XP 的 Go 毫无用处。

GCC 和 Clang 似乎也使用不同的机制来实现隐式 TLS,所以......

此外,TLS 对 Go 的重要性似乎不如对 C/C++ 的重要性。 也就是说,Go 并不真正支持 TLS 作为语言中的概念,并且在自己的运行时中很少使用它。 所以 TLS 支持不一定需要像...集成。

除非我错了,否则似乎找到一种方法来防止 Go DLL 相互踩踏以及各种其他语言运行时就足够了。 Windows TEB 具有一流的 TLS 机制,需要显式使用某些 API 函数。 如果可能的话,我更愿意从这里开始。

@ianlancetaylor@alexbrainman :我相信我已经在 asm6.go 中找到了翻译错误。 作为复习:

我们想要:

  4e2556:   65 48 8b 1c 25 28 00    mov    %gs:0x28,%rbx
  4e255f:   48 c7 83 00 00 00 00    movq   $0x123,0x0(%rbx)

但我们得到:

    6dc8bae8:   65 48 8b 1c 25 28 00    mov    %gs:0x28,%rbx
    6dc8baf1:   64 48 c7 03 23 01 00    movq   $0x123,%fs:(%rbx)

看起来 src/cd/internal/obj/x86/asm6.go:2266 处的代码无条件地将共享库中的前缀覆盖为 %fs。 我编写了一些诊断程序来检测对此代码的调用,并且在 c 共享构建期间它在 Windows 上被激活。

我更新了我的代码以避免在 Windows 上进行这种转换,结果是创建的 .dll 在我的测试工具上运行得非常好。 所以这是一个下来。

我只是欢呼,如果我能用我有限的知识帮助测试这个(和
一些详细的说明) - 请使用我作为“真正的用户又名愚蠢
测试员”。

再次,我敬畏并为你的工作鼓掌。

希望我能帮上忙

马丁

Op woensdag 16 maart 2016 heeft Christopher Nelson [email protected]
het volgende geschreven:

@ianlancetaylor https://github.com/ianlancetaylor , @alexbrainman
https://github.com/alexbrainman :我相信我已经找到了
asm6.go 中的翻译错误。 作为复习:

我们想要:

4e2556: 65 48 8b 1c 25 28 00 移动 %gs:0x28,%rbx
4e255f: 48 c7 83 00 00 00 00 movq $0x123,0x0(%rbx)

但我们得到:

6dc8bae8:   65 48 8b 1c 25 28 00    mov    %gs:0x28,%rbx
6dc8baf1:   64 48 c7 03 23 01 00    movq   $0x123,%fs:(%rbx)

看起来 src/cd/internal/obj/x86/asm6.go:2266 的代码是
无条件地将共享库中的前缀覆盖为 %fs。 我写
一些诊断程序来检测对该代码的调用,并且它正在被激活
在 c 共享构建期间在 Windows 上。


您收到此消息是因为您发表了评论。
直接回复此邮件或在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -197067982

我的 win-shared-1.6 分支已更新为另一个修复程序。 通过确保首先使用正确的名称创建输出文件来防止链接时错误。 显然这在 OS X 上也是一个问题,所以我重用了该代码。

如果有人想测试它并确保它解决了这个问题,那就太好了。

现在剩下的“唯一”问题是确定在执行多个 Go 运行时的情况下如何处理线程局部变量。 例如,如果一个进程加载了两个都具有运行时的不同 DLL。

@ianlancetaylor , @alexbrainman你想让我提交一个“到目前为止”修复的补丁吗? 这将在仅使用一个 Go DLL 的情况下启用 c-shared。 还是您希望我也等到本地问题也得到处理?

你想让我提交一个“到目前为止”修复的补丁吗?

我不介意你怎么做。 较小的 CL 始终是首选。

亚历克斯

嗨,请帮帮我!
我尝试使用 golang dll 作为 C++ 应用程序的插件。

简单的 C++ 应用程序:

#include <iostream>
#include <chrono>
#include <thread>
#include <sstream>
#include <windows.h>

typedef long long GoInt64;
typedef GoInt64 GoInt;
typedef GoInt(__stdcall *DoubleIt_Type)(GoInt i);

unsigned int loop = 0;
bool dll_test() {
    HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\temp\\tarantool_dll\\libdoubler.dll");
    if (!hGetProcIDDLL) {
        std::cout << "could not load the dynamic library" << std::endl;
        return false;
    }
    std::this_thread::sleep_for(std::chrono::seconds(3));

    DoubleIt_Type DoubleIt = (DoubleIt_Type)GetProcAddress(hGetProcIDDLL, "DoubleIt");
    if (!DoubleIt) {
        std::cout << "could not locate the DoubleIt function" << std::endl;
        return false;
    }

    std::cout << "loop = " << loop << "    DoubleIt returned " << DoubleIt(21) << std::endl;

    FreeLibrary(hGetProcIDDLL);
    std::this_thread::sleep_for(std::chrono::seconds(3));
    loop++;
    return true;
}

int main() {
    while (true) {
        dll_test();
    }
    return EXIT_SUCCESS;
}

“你好,世界”golang dll代码:

package main

import (
    "C"
    "log"
    )

//export DoubleIt
func DoubleIt(x int) int {
    log.Println("DoubleIt: ", x)
    return x * 2
}

func main() {

}

golang dll 的简单 build.bat:

cd C:\temp\tarantool_dll
set GOROOT=C:\go-win-shared-1.6\
set GOARCH=amd64
set GOPATH=C:\temp\tarantool_dll\app
:: C:\TDM-GCC-64\mingwvars.bat
C:\go-win-shared-1.6\bin\go build -o libdoubler.dll -buildmode=c-shared main

当我在调试、x64 模式下使用 vs2015 运行 C++ 代码时,我的应用程序失败并出现类似

Exception thrown at 0x0000000067255CED in test_plugin.exe: 0xC0000005: Access violation executing location 0x0000000067255CED.

FreeLibrary(hGetProcIDDLL);之后但未在主线程中引发异常

控制台日志:

2016/06/14 00:21:25 DoubleIt:  21
loop = 0    DoubleIt returned 42

对于更复杂的 Golang 代码,我可以“稳定”工作 10-15 个循环
我使用 Telegram bot api (http://github.com/Syfaro/telegram-bot-api) 记录日志,但我发现我的进程内存在每个循环中都会增加。 在 10-15 循环后异常杀死我的应用程序。

对不起我的英语不好)

这在库存中还没有工作。 我有一个补丁可以让它工作
单个 DLL,但我还没有完成现在通用的解决方案。

2016 年 6 月 13 日星期一下午 5:48 jonywtf [email protected]写道:

嗨,请帮帮我!
我尝试使用 golang dll 作为 C++ 应用程序的插件。

简单的 C++ 应用程序:

包括

包括

包括

包括

包括

typedef long long GoInt64;typedef GoInt64 GoInt;typedef GoInt(__stdcall *DoubleIt_Type)(GoInt i);
无符号整数循环 = 0;bool dll_test() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\temp\tarantool_dll\libdoubler.dll");
如果(!hGetProcIDDLL){
std::cout << "无法加载动态库" << std::endl;
返回假;
}
std::this_thread::sleep_for(std::chrono::seconds(3));

DoubleIt_Type DoubleIt = (DoubleIt_Type)GetProcAddress(hGetProcIDDLL, "DoubleIt");
if (!DoubleIt) {
    std::cout << "could not locate the DoubleIt function" << std::endl;
    return false;
}

std::cout << "loop = " << loop << "    DoubleIt returned " << DoubleIt(21) << std::endl;

FreeLibrary(hGetProcIDDLL);
std::this_thread::sleep_for(std::chrono::seconds(3));
loop++;
return true;

}
int main() {
而(真){
dll_test();
}
返回 EXIT_SUCCESS;
}

“你好,世界”golang dll代码:

包主
进口 (
“C”
“日志”
)
//导出 DoubleItfunc DoubleIt(x int) int {
log.Println("DoubleIt:", x)
返回 x * 2
}
功能主要(){

}

golang dll 的简单 build.bat:

cd C:\temp\tarantool_dllset GOROOT=C:\go-win-shared-1.6set GOARCH=amd64set GOPATH=C:\temp\tarantool_dll\app:: C:\TDM-GCC-64\mingwvars.bat
C:\go-win-shared-1.6\bin\go build -o libdoubler.dll -buildmode=c-shared main

当我在调试中使用 vs2015 运行 C++ 代码时,x64 模式我的应用程序失败
就像是

在 test_plugin.exe 中的 0x0000000067255CED 处引发异常:0xC0000005:访问冲突执行位置 0x0000000067255CED。

在 FreeLibrary(hGetProcIDDLL) 之后; 但是在 Main 中没有抛出异常
线

控制台日志:

2016/06/14 00:21:25 双倍:21
循环 = 0 DoubleIt 返回 42

对于更复杂的 Golang 代码,我可以“稳定”工作 10-15 个循环
我使用 Telegram bot api (http://github.com/Syfaro/telegram-bot-api
https://github.com/Syfaro/telegram-bot-api) 用于日志,但我看到了
我的过程的记忆会增加每个循环。 并且经过 10-15 次循环异常
杀死我的应用程序。


你收到这个是因为你被提到了。

直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -2257188​​60,或者静音
线程
https://github.com/notifications/unsubscribe/AB8JvGrwvJCtziNkanWB1FnatQvoV34vks5qLdAjgaJpZM4E3dB1
.

@nadiasvertex我们在哪里? 我希望在 1.8 中看到适当的 c-shared 支持!

对不起,这几个月我真的很忙。 我可以为 1.8 完成“制作 DLL”代码。 (已经完成了,我只是还没有为它提交补丁。)但是,这会带来每个进程只能使用一个 Go DLL 的限制。

由于 Windows 如何执行 TLS,以及 Go 如何使用 Windows 的 TLS 系统,“正确的”DLL 支持要困难得多。 基本问题是,同一进程中的两个 Go 运行时会在 Windows 上相互踩踏,因为它们都认为自己拥有正在使用的 TLS“插槽”。 我向 golang-dev 列表写了一个提案,实际上得到了零反馈。

总而言之,我看到了两种可能的方法:

  1. 重写一些底层的 Go 运行时支持以期望多个运行时。 这可能是最简单的方法,并且涉及创建另一个间接级别。 Go TLS 插槽将指向包含插槽数组的内存页面。 还会有一个位图指示正在使用的插槽。 如果新的运行时在进程中启动,它将在位图中找到一个空闲位,为自己声明该位(转换为数组中的偏移量),然后将它现在使用的 TLS 页面放入该槽中。 位图需要由同一进程中的所有 Go 运行时共享,以便每个线程的 TLS 索引相同。 (如果每个线程的 TLS 索引发生变化,这_appears_ 与用于 TLS 访问的 Go Assembly 伪寄存器交互不良。)位图可以使用命名共享内存之类的东西在进程中的所有运行时之间共享。
  2. 停止使用“未保留”的 TLS 索引并完全支持 Windows 的 TLS 系统。 这将涉及实现类似https://msdn.microsoft.com/en-us/library/windows/desktop/ms686997 (v=vs.85).aspx 的东西。 从表面上看,这似乎是一种更简单的方法,但 Go 似乎使用伪寄存器进行 TLS 访问。 这必须以某种方式转换为由汇编程序调用几个 Windows 函数。 我还没有深入研究这种方法。 此外,它可能会对性能产生影响。

我很想得到一些关于这些想法的反馈。 整个 9 月我都很忙,然后我在 11 月搬迁我的住所。 我可能在 10 月或 12 月有时间。 如果我能得到一个好的设计并从更有知识的人那里得到一些支持,我就会更有动力去研究它。 :-)

就个人而言,我认为 dll 创建和正确的 TLS 支持应该是两个独立的问题。 前者几乎肯定可以制作 1.8 窗口,而后者可能需要更多的思考和工作才能完成。 你觉得@alexbrainman和@ianlancetaylor 怎么样?

有没有办法防止多链接冲突并使二进制文件在链接或执行时失败?

这样,为 1.8 实现单个.dll链接是安全的。

同时让开发人员有责任为他们想要嵌入的所有 Go 库构建他们的“一体化” .dll ……如果他们真的需要的话。

我很确定大多数观看此问题的用户都可以接受单个.dll限制。

@bclermont :我不确定是否有一种简单的方法可以让链接仅针对 DLL 爆炸。 他们每个人都有自己的 Go 运行时私有副本,据我了解,这是设计使然。 如果两个 DLL 导出相同的符号并且可执行文件绑定两个库,我认为动态链接器不会抱怨。

我想如果我们检测到另一个 Go 运行时已经初始化了自己,我们可以在 Windows 中注册一个命名对象并在运行时初始化时失败。

即使您可以将两个 Go DLL 加载到单个进程中,它们
每个都有自己的 Go 运行时,因此它们不会互操作。

因此,我不认为一个 Go DLL 的限制是不合理的。
这也是其他操作系统上 c-shared 的状态。 请离开
提前并发送 CL。

切换到使用 Windows TLS 将具有巨大的性能
打去。 因为我们不知道那些 TLS 函数的作用
以及它们需要多少堆栈空间,因此我们必须切换
到系统堆栈来调用它们。 这必须在开始时发生
几乎所有功能。

我们确实有一个通用的解决方案:保留一个机器寄存器到
持有 g,就像 RISC 架构一样,处理保留的
注册为实际 TLS 插槽的直写缓存。 然而,
这还没有实现,它也会有性能
影响。

切换到使用 Windows TLS 将具有巨大的性能
打去。 因为我们不知道那些 TLS 函数的作用
以及它们需要多少堆栈空间,因此我们必须切换
到系统堆栈来调用它们。

我们真的只需要使 TlsGetValue 快速。 我做了一些小实验(https://github.com/golang/go/issues/11058#issuecomment-167939906)来看看 TlsGetValue 在里面的样子。 在我的电脑上看起来很简单。 也许我们可以假设它在任何地方都做同样的事情,并在 Go 中使用相同的逻辑来获取 TLS。 我们甚至可以仅将其限制为 Go DLL,因此我们不会影响普通 Go 用户(可执行文件)。

亚历克斯

你反汇编的代码
https://github.com/golang/go/issues/11058#issuecomment -167939906
从 Go 堆栈调用看起来很安全,但是,它并没有显示整个
功能。

例如,当 TLS 索引大于等于 0x40 时,
它跳转到 0x7c845054。

该位置的代码是什么样的? 我想那里有代码
可能需要检查并为 TLS 分配新的存储空间,并且
很容易溢出有限的 Go 堆栈导致难以诊断
也难以重现的问题(TLS 索引必须是
大到足以触发备用代码路径,通常
意味着问题不会出现在简单的测试用例中。)

当然,我们可以自己查看TLS索引,切换到
系统堆栈在太大时调用 TlsGetValue,但是,这
设计对我来说看起来很脆弱。 (我们假设:

  1. TLS 索引值的阈值,以及
  2. 当 TLS 索引较少时,TlsGetValue 在 Go 堆栈上调用是安全的
    比假设的阈值。)

关于 Windows exe 的两个单独的 TLS 机制和
DLL,我认为这很难维护,如果我们不好好测试
DLL测试的覆盖范围,它必然会有点腐烂。

该位置的代码是什么样的?

(gdb) disas
Dump of assembler code for function TlsSetValue:
=> 0x7c809c65 <+0>:     mov    %edi,%edi
   0x7c809c67 <+2>:     push   %ebp
   0x7c809c68 <+3>:     mov    %esp,%ebp
   0x7c809c6a <+5>:     push   %esi
   0x7c809c6b <+6>:     push   %edi
   0x7c809c6c <+7>:     mov    %fs:0x18,%eax
   0x7c809c72 <+13>:    mov    0x8(%ebp),%edi
   0x7c809c75 <+16>:    cmp    $0x40,%edi
   0x7c809c78 <+19>:    mov    %eax,%esi
   0x7c809c7a <+21>:    jae    0x7c845087 <SetUnhandledExceptionFilter+418>
   0x7c809c80 <+27>:    mov    0xc(%ebp),%eax
   0x7c809c83 <+30>:    mov    %eax,0xe10(%esi,%edi,4)
   0x7c809c8a <+37>:    xor    %eax,%eax
   0x7c809c8c <+39>:    inc    %eax
   0x7c809c8d <+40>:    pop    %edi
   0x7c809c8e <+41>:    pop    %esi
   0x7c809c8f <+42>:    pop    %ebp
   0x7c809c90 <+43>:    ret    $0x8
   0x7c809c93 <+46>:    nop
   0x7c809c94 <+47>:    nop
   0x7c809c95 <+48>:    nop
   0x7c809c96 <+49>:    nop
   0x7c809c97 <+50>:    nop
End of assembler dump.
(gdb) x/20i 0x7c845087
   0x7c845087 <SetUnhandledExceptionFilter+418>:        sub    $0x40,%edi
   0x7c84508a <SetUnhandledExceptionFilter+421>:        cmp    $0x400,%edi
   0x7c845090 <SetUnhandledExceptionFilter+427>:        jae    0x7c8450f9 <SetUnhandledExceptionFilter+532>
   0x7c845092 <SetUnhandledExceptionFilter+429>:        cmpl   $0x0,0xf94(%esi)
   0x7c845099 <SetUnhandledExceptionFilter+436>:        jne    0x7c8450e8 <SetUnhandledExceptionFilter+515>
   0x7c84509b <SetUnhandledExceptionFilter+438>:        call   *0x7c8010bc
   0x7c8450a1 <SetUnhandledExceptionFilter+444>:        cmpl   $0x0,0xf94(%esi)
   0x7c8450a8 <SetUnhandledExceptionFilter+451>:        jne    0x7c8450e2 <SetUnhandledExceptionFilter+509>
   0x7c8450aa <SetUnhandledExceptionFilter+453>:        mov    %fs:0x18,%eax
   0x7c8450b0 <SetUnhandledExceptionFilter+459>:        mov    0x7c8856d4,%ecx
   0x7c8450b6 <SetUnhandledExceptionFilter+465>:        mov    0x30(%eax),%eax
   0x7c8450b9 <SetUnhandledExceptionFilter+468>:        push   $0x1000
   0x7c8450be <SetUnhandledExceptionFilter+473>:        or     $0x8,%ecx
   0x7c8450c1 <SetUnhandledExceptionFilter+476>:        push   %ecx
   0x7c8450c2 <SetUnhandledExceptionFilter+477>:        pushl  0x18(%eax)
   0x7c8450c5 <SetUnhandledExceptionFilter+480>:        call   *0x7c80100c
   0x7c8450cb <SetUnhandledExceptionFilter+486>:        test   %eax,%eax
   0x7c8450cd <SetUnhandledExceptionFilter+488>:        mov    %eax,0xf94(%esi)
   0x7c8450d3 <SetUnhandledExceptionFilter+494>:        jne    0x7c8450e2 <SetUnhandledExceptionFilter+509>
   0x7c8450d5 <SetUnhandledExceptionFilter+496>:        call   *0x7c8010b4
(gdb)

你是对的。 那里可能正在增长 TLS 区域。 我看到调用 SetUnhandledExceptionFilter 并假设它是异常处理程序,它会在那里崩溃或其他什么。

亚历克斯

2016 年 8 月 18 日 13:54,Minux Ma [email protected]写道:

你反汇编的代码
https://github.com/golang/go/issues/11058#issuecomment -167939906
从 Go 堆栈调用看起来很安全,但是,它并没有显示整个
功能。

例如,当 TLS 索引大于等于 0x40 时,
它跳转到 0x7c845054。

该位置的代码是什么样的? 我想那里有代码
可能需要检查并为 TLS 分配新的存储空间,并且
很容易溢出有限的 Go 堆栈导致难以诊断
也难以重现的问题(TLS 索引必须是
大到足以触发备用代码路径,通常
意味着问题不会出现在简单的测试用例中。)

当然,我们可以自己查看TLS索引,切换到
系统堆栈在太大时调用 TlsGetValue,但是,这
设计对我来说看起来很脆弱。 (我们假设:

  1. TLS 索引值的阈值,以及
  2. 当 TLS 索引较少时,TlsGetValue 在 Go 堆栈上调用是安全的
    比假设的阈值。)

关于 Windows exe 的两个单独的 TLS 机制和
DLL,我认为这很难维护,如果我们不好好测试
DLL测试的覆盖范围,它必然会有点腐烂。

好吧,我们有两种(稍微)不同的机制用于 Linux 上的 TLS 访问
对于可执行文件和共享对象——本地执行和初始执行——以及它
工作正常......它适用于 c-shared 案例虽然有点薄
冰,因为你只能加载这么多使用初始的共享对象
在加载程序抱怨之前执行模型进入一个进程(我猜这是
类似于 Windows 实现中 40 的 TLS 索引限制?)。 为了
所有这一切,我还没有看到很多关于这导致问题的真实报告。

如果你想正确地做到这一点,正如 Minux 已经说过的那样,应该存储 g
大部分时间都在寄存器中,就像我们在大多数其他端口上所做的那样。 然后
(相对)我们访问 TLS 存储的几次可以支付的成本
调用一般动态(或任何 Windows 版本)
调用)系统堆栈上的代码。 如果你想真正勇敢,你可以
实现类似于通用 tlsdesc 模型的东西,并且只能通过
systemstack 当你真的需要...

在 Windows 的 Go 1.7 中是否有任何解决方法可以让共享库工作? 阅读上面的评论,如果您只需要加载 Go 共享库,这似乎是一种解决方法,这是我的问题。
基本问题:我有一个共享库,我想加载一个 Go 共享库并执行一些工作。

使用buildmode=c-archive编译静态库,然后使用 gcc 将其链接到共享库。

基本问题:我有一个共享库,我想加载一个 Go 共享库并执行一些工作。

这不足以描述您的问题。 请提供更多详细信息。

亚历克斯

关于我的问题的更多信息:
我在使用 Go 1.7 的 Windows 7/10 上。 我想创建一个像共享库一样的 Go 包,它公开 C 函数以供其他 DLL/exe 加载和调用。
示例:我有一个 Go 包“hello”,它有一个公开的方法“SayHello(name string)”。 我希望能够将其编译为具有一个导出方法“SayHello”的 hello.dll。 一个单独的 C/C++ 可执行文件使用 LoadLibrary 加载 hello.dll 并调用 SayHello 函数。

关于我的问题的更多信息:

谢谢你的解释。 当前的问题就是它。
不幸的是我没有解决你的问题。 也许其他人会提供帮助。

亚历克斯

@jtsylve你能否给我一个如何“使用 buildmode=c-archive 编译静态库,然后使用 gcc 将其链接到共享库”的过程示例。
这种方法有什么限制?

@LukeMauldin

go build -buildmode=c-archive -o libxxx.a
gcc -m64 -shared -o xxx.dll xxx.def libxxx.a -Wl,--allow-multiple-definition -static -lstdc++ -lwinmm -lntdll -lWs2_32

然后使用VS的lib命令生成xxx.lib:

lib /def:xxx.def /machine:x64

使用命令设置buildmode=c-archive然后使用gcclib效果很好! 谢谢你。 这种方法有什么限制? 为什么这个方法不是官方推荐的,这个问题已经关闭了?

此问题未关闭,因为不需要外部
链接器来创建共享库。

2016 年 9 月 20 日星期二下午 12:44 LukeMauldin [email protected]
写道:

使用命令设置 buildmode=c-archive 然后使用 gcc 和 lib
工作完美! 谢谢你。 这种方法有什么限制? 为什么
这个方法不是官方推荐的,这个问题已经关闭了吗?


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -248376388,或者静音
线程
https://github.com/notifications/unsubscribe-auth/AAkQ9ZWQZDpqu21o_l5LYcTcUZkrJhQ3ks5qsBuYgaJpZM4E3dB1
.

使用外部链接器创建共享库非常好。
实际上,构建 cgo 程序需要外部链接器
默认情况下(并且构建 c-archive/c-shared 隐式启用
cgo。)

这张票的目的是让它更加自动化。 我希望
除 TLS 问题外,请在 10 月份完成此票证。 TLS 应该
可能完全是另一个问题。

在 2016 年 9 月 20 日星期二晚上 9:57,Minux Ma通知@github.com 写道:

使用外部链接器创建共享库非常好。
实际上,构建 cgo 程序需要外部链接器
默认情况下(并且构建 c-archive/c-shared 隐式启用
cgo。)


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment -248489183,或者静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvORPY8F_5KixAqQ0REM4vJxi5Ldrks5qsI8DgaJpZM4E3dB1
.

我将从 Windows x64 上的 TLS 问题中看到哪些实际影响,包括 C 程序中的 Go 共享库 .dll(由上述说明生成)?

你只能加载一个 Go 运行时,这意味着如果你有多个 Go 共享库,那么你会遇到问题。 如果您只加载一个 Go 库,那么您应该不会看到任何问题。

只要使用完全相同的运行时版本(即 1.7.1)构建,我可以加载多个 Go 库吗?

最好将所有 Go 运行时构建到一个 dll 中,然后将所有 Go 共享库链接到它。
Go 运行时基本上有 2 个选项:去静态链接或去动态链接。 哪里去动态链接将是从“-buildmode = shared -linkshared std”生成的dll

@LukeMauldin不。您必须将所有 Go 静态库链接到一个 DLL 中。 Windows 暂时不(也不会)支持多个 DLL。 这就是 TLS 问题。

@spusnei-pbsc 这基本上就是我正在做的事情。

我在 Windows 上有一个场景,它有几个部分:

  • 使用上述步骤生成的 Go 共享库 (A.dll)
  • 在运行时加载 A.dll 并调用 CGO 公开的 C 函数的第三方 COTS 库
  • 一个使用 CGO 加载第三方 COTS 库的 Go 应用

在上面的场景中,第三方COTS库一加载A.dll,就好像和实际运行应用程序的Go运行时有冲突,进程出错,出现这样的错误
fatal error: unexpected signal during runtime execution
堆栈跟踪通常如下所示:
runtime.throw(0x67df97, 0x2a) C:/Go/src/runtime/panic.go:566 +0x9c fp=0xc04234b4e8 sp=0xc04234b4c8
runtime.sigpanic() C:/Go/src/runtime/signal_windows.go:155 +0x19c fp=0xc04234b518 sp=0xc04234b4e8
runtime.mallocgc(0x10, 0x0, 0x9e40d00, 0x0) C:/Go/src/runtime/malloc.go:679 +0x361 fp=0xc04234b5b8 sp=0xc04234b518
runtime.growslice(0x622520, 0x0, 0x0, 0x0, 0x5, 0x3, 0x8, 0x210) C:/Go/src/runtime/slice.go:126 +0x255 fp=0xc04234b648 sp=0xc04234b5b8

出于测试目的,如果我禁用加载 A.dll 的 COTS 库,那么系统会按预期工作,所以我很确定这是 Go 应用程序运行时和 A.dll 中的 Go 运行时之间的冲突。 我确保使用完全相同版本的 Go 运行时 (1.7.1) 并使用相同的 C 编译器来编译 A.dll 和正在运行的 Go 应用程序。 有没有办法解决这个限制?

如本线程前面所述,不支持。 我了解到这种情况不适用于任何平台。 你不能有多个 DLL 将它们自己的 Go 运行时加载到同一个进程中。 这在 Windows 上肯定行不通,因为每个运行时都认为它在进程控制块的 TLS 区域中拥有一个特定的插槽。

对单个 .dll 限制没问题。
+1

@nadiasvertex这是个好主意! 你上面提到的,可能在11月完成,你是怎么做出来的?

@janckerchen抱歉,我一直在搬家。 不过,我相信这周我会有一些时间。 我希望得到一些 PR 来解决这个问题。

只是一个更新。 我相信我已经完成了这方面的工作。 然而,大部分工作是完成良好的测试。 我已经开始将现有的 c-shared 测试框架从 bash 移植到 Go,以便它可以在 Windows 下运行。 这与我们使用 c-archive 遵循的模式相同。 但是,它进展缓慢,因为我必须先了解测试在做什么,然后弄清楚如何将它们转换为 Go,然后再决定这些测试的哪些部分适用于 Windows。

有机会制作 1.8 窗口吗? 我没有大量的空闲时间,但如果我能帮忙,请告诉我。

对不起,将这样的东西带入 1.8 为时已晚。

@nadiasvertex听起来您已经取得了积极的进展。 您是否在某处提交了代码更改? 我似乎无法找到它们。

不,它们在我的官方 Go 存储库的本地副本中。 我不想
提交它们,直到我有好的测试。 我计划提交测试更新
首先,然后提交 DLL 更新。

2016 年 11 月 25 日星期五上午 9:45 jjoergensen [email protected]写道:

@nadiasvertex https://github.com/nadiasvertex听起来像你做了
积极进展。 您是否在某处提交了代码更改? 我不能
似乎找到了他们。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-262970194或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvN_-d2QMdpvRLswYjHKUny97r2PXks5rBvRugaJpZM4E3dB1
.

第 1 阶段:已在https://go-review.googlesource.com/33579提出了重写测试

我确信我需要在这个补丁上做很多工作才能让它被接受,但到目前为止它在 Linux 上通过了。 我还没有测试过 Android 部分。

@nadiasvertex欢迎任何更新。 我如何帮助您进行测试?

我认为发布团队正忙于 1.8。 我正在等待评论
测试。 一旦通过,我将对 Windows 进行良好的测试,以确保
有用。

我可以尝试将更改移植到我的 Go 的 github 分支上,以供其他人使用
看着。

2016 年 12 月 16 日星期五晚上 9:07 janckerchen [email protected]
写道:

@nadiasvertex https://github.com/nadiasvertex欢迎任何更新。
我如何帮助您进行测试?


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-267736255或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvL8ePmFxIeEJXLqXe88HftgJAJkKks5rI0P3gaJpZM4E3dB1
.

嗨@nadiasvertex。 非常感谢您迄今为止的努力!

这个问题已经持续了一年多,似乎有点卡住了? 您能否描述一下剩下的东西,从哪里开始修复剩余的部分以及如何处理它? 当前的源代码在哪里,有人将如何编译、测试并继续?

不幸的是,我缺乏对该主题的了解,因此我无法自己实现它,但我真的很想看到实现“dll”功能。 我希望开始在我们公司内部慢慢地用 golang 替换一些现有的 .NET 代码,这似乎是一种简单的方法。 一切顺利,乔纳森。

谢谢。

基本状态是代码都写好了。 我相信它有效。

我在 Go 编译器中重写了 DLL 测试套件,以便提供良好的
测试。 几个月前提交了一个补丁,但由于 1.8
发布周期核心开发人员没有审核它。 我还在等待
那次审查。 审核完成后,我可以集成 Windows DLL
更改(实际上非​​常小)并确保它通过测试
套房。

本周末我将尝试使用最新的 Go 更新我的 github 存储库
源,然后在它上面应用我的补丁。 这样其他人都可以
下载并测试它。

测试重写位于: https ://go-review.googlesource.com/#/c/33579/

我可以集成 Windows DLL
改变

我希望我的更改首先解决问题 #10776。

测试重写位于: https ://go-review.googlesource.com/#/c/33579/

当我有空闲时间时,我会回顾一下。 但伊恩也必须看看它。

亚历克斯

本周末我将尝试使用最新的 Go 源代码更新我的 github 存储库,然后在其上应用我的补丁。 这样任何人都可以下载并测试它。

嗨 Christopher (@nadiasvertex) - 你介意将你的 DLL 实现代码推送到 github 存储库或分支,以便我们可以在测试/正式合并发生之前使用它吗?

谢谢!

杰森

@nadiasvertex :对于我正在从事的项目来说,这将非常有用。 -buildmode中的 DLL 支持对于该项目来说是一个交易破坏者,因此合并它会很棒。 让我知道我是否可以以任何方式提供帮助,或者只是在合并之前试一试。

我不得不再次道歉,我太忙了,我忘记了这一点。 我将会
这个周末真的要努力把它整理一下,这样其他人就可以看到了
它。

2017 年 4 月 4 日星期二上午 12:10 乔纳森·斯托伊科维奇 <
通知@github.com> 写道:

@nadiasvertex https://github.com/nadiasvertex :这将是非常
这对于我正在从事的项目很有用。 DLL 支持
-buildmode 是该项目的交易破坏者,因此合并将
太棒了。 让我知道我是否可以以任何方式提供帮助或只是给它一个
在合并之前拍摄。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-291388305或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvO5UufSbbMfqkqWZe0PZmP45afxwks5rscLAgaJpZM4E3dB1
.

@nadiasvertex您的修复是否对 CGo 等的使用增加了任何限制? 分支或分叉上是否有可以访问修复的时间表?

如果我错了,请纠正我,但我不认为@nadiasvertex正在解决 tls 问题,即木头阻止 go 库在 Windows 上用作动态库

@mattetti哦,我一定是误解了。 我只是略读了上面的评论。

此修复旨在允许您直接从 Go 代码创建 DLL
从工具。 它不能解决任何潜在的问题
多个 Go DLL 链接到一个进程中。 我的理解是
所有平台都存在这个问题。 无论如何,没有共识
关于问题的“正确”解决方案是什么。

2017 年 4 月 7 日星期五凌晨 4:58 avatarscape [email protected]写道:

@mattetti https://github.com/mattetti哦,我一定是误会了。 一世
只略读以上评论。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-292481410或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvKXSDhMqKu8JBYwX_n5x9fW_h3xzks5rtfrRgaJpZM4E3dB1
.

@nadiasvertex这是否也修复了 buildmode=plugin?

没有。 插件模式随 1.8 发布,仅适用于 Linux。
但是,如果他们一般地解决了 TLS 问题,那么它可能不会
添加它需要做很多工作。

2017 年 4 月 7 日星期五上午 9:45 avatarscape [email protected]写道:

@nadiasvertex https://github.com/nadiasvertex这也解决了吗
构建模式=插件?


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-292540108或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvGOg6M4Bmz6Pcnnh2VTaZG7qmT2Wks5rtj3ngaJpZM4E3dB1
.

如果您解决了问题,我可以使用插件功能。

上次我检查 TLS 问题在 Mac 和 Windows 上仍然存在。 我使用 hack 来解决它,但它不可靠,应该有人真正根据操作系统规范正确实施 TLS。 AFAIK,没有人在研究它。

@nadiasvertex社区可以参与修复、清理和抛光。 如果您不介意继续在分支上推送甚至损坏的进行中文件; 现在无论事情在哪里。 我们不想为你做更多的工作; 但只是想从你离开的地方开始,即使它没有完全准备好或者它没有构建。

@glycerine最后我检查了它确实有效,只是不能将多个 Go DLL 链接到同一个二进制文件中。 我会在这个周末抽出一些时间来更新我的 Go 编译器的分支,对 DLL 进行更改,以便人们可以进行测试。

我对 DLL 编译器测试的一些更改进行了审查,其中有一些我需要解决的评论。 然后我可以提交 DLL 修复的代码。 但是,正如我所说,我会尝试将所有内容都放入我的 github 存储库中,以便其他人可以对其进行测试。 那将是非常有价值的。

关于 TLS,在这个线程中有一个很长的对话。 不过,既然插件 API 似乎已经建立起来,或许可以重新审视此事。

对于担心将多个共享库链接到一个进程的问题的人:我一直在使用 Ruby 和 Node.js 加载 buildmode=c-shared 库,并且通过始终加载它们,我已经相当容易地解决了这个问题在一个子进程中。 最终不必处理它会很好,但现在这似乎是一个不错的解决方法。

这是一个绝妙的主意。 我在想类似的事情。

2017 年 4 月 7 日 23:54,“Dane Schneider” [email protected]写道:

对于关心链接多个共享问题的人
库到一个进程中:我一直在加载 buildmode=c-shared 库
使用 Ruby 和 Node.js,我已经很好地解决了这个问题
通过始终将它们加载到子进程中很容易。 会很高兴
最终不必处理这个问题,但现在这似乎是一个
相当可靠的解决方法。


您收到此消息是因为您发表了评论。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-292670613或静音
线程
https://github.com/notifications/unsubscribe-auth/ABIw2mD8wyxgMEIUs2pf11rYwTB1CjwDks5rtr6xgaJpZM4E3dB1
.

如果有人想在这个项目中帮助@nadiasvertex ,他们可以接管他的https://go-review.googlesource.com/#/c/33579/。 CL 将 misc/cgo/testcshared/test.bash 转换为 Go,因此我们可以在 Windows 上运行它。 我没有看到 Go Team 未经测试就接受任何 DLL 更改。

亚历克斯

我在这里重新调整了针对 go 1.8.1 的更改:

https://github.com/nadiasvertex/go/tree/win-shared-1.8

没有测试以确保这适用于 1.8。 我编译了它,但仅此而已。 我感谢提供测试它的帮助,并鼓励任何对此功能感兴趣的人测试和报告他们的结果。

只是对任何对 dll 感兴趣的人的提醒...... Exe 也应该能够导出函数,以便 dll 可以调用位于 exe 中的函数,因此,理论上任何 EXE 都应该可以重命名为 .dll并将其加载到任何程序中,如果该 exe 具有导出功能(这是一个很酷的功能/技巧)。

不确定仅制作 exe 的导出功能是否比创建实际的 DLL 更容易。 只是可能会节省一步……如果只需要创建一个带有导出函数的 exe,然后将其重命名为 dll。 它不会是一个真正纯正的 dll,但可能仍然像一个一样工作。 不记得如果 exe 有一些不同的行为,比如 dll 没有的 main() 函数,我的记忆不是很好。

还有这个帖子:
http://stackoverflow.com/questions/40573401/building-a-dll-with-go-1-7

...它使用 GCC 从 go 生成的 .a 和 .h 文件创建 DLL。 可能之前在这个帖子的某个地方讨论过,我不知道。

和这个:
https://groups.google.com/forum/#!topic/golang -dev/ckFZAZbnjzU

嗨z505,您所指的stackoverflow正在谈论“在Hello2.c中重新导出”

你明白吗? 你能解释一下那里的意思吗?

是的,几分钟前我刚刚将一个 GoLang dll 加载到 freepascal/lazarus 并成功导出了一个返回生命意义的函数(42 作为 int32 类型)。
代码在这里:
https://github.com/z505/goDLL

基本上你制作一个 C 程序(样板文件)来欺骗 GCC 导出 Go 代码,因为 go 可以生成 C 程序可以导入和使用的 .a 和 .h 文件

C 程序如下所示:
https://github.com/z505/goDLL/blob/master/goDLL.c

golang 代码如下所示:
https://github.com/z505/goDLL/blob/master/exportgo.go

然后使用任何语言编写的程序在运行时导入 dll。 我使用了 fpc/lazarus,但你可以使用任何你想要的东西。
https://github.com/z505/goDLL/blob/master/fpcprog.lpr

问题:随意动态卸载 dll 可能会导致问题,因为 Go 运行时,AFAIK 不能随心所欲地卸载......这可能正在这里工作:
https://github.com/golang/go/issues/11100

在 exe 生命周期内随时卸载 dll,这对于随心所欲地动态卸载插件的插件系统非常有用。

非常感谢,我明白该怎么做了。 非常好心,你让我知道。
效果很好,谢谢。

@nadiasvertex :我们终于有时间试用你的叉子了。 我们得到一个错误: buildmode=c-shared not supported on windows/amd64这似乎来自这里。 除了-buildmode=c-shared之外,是否有一个特定的标志要传递给go build $ 或者上面的开关/案例中缺少windows/amd64

我可能在补丁中错过了! 让我再检查一遍!

2017 年 5 月 11 日星期四凌晨 1:03 Jonathan Stoikovitch [email protected]
写道:

@nadiasvertex https://github.com/nadiasvertex :我们终于有时间了
试试你的叉子。 我们得到一个错误:buildmode=c-shared not
在似乎来自这里的 windows/amd64 上受支持
https://github.com/nadiasvertex/go/blob/066eb2b2eecf4bcdf814d86f3c64ad3c4b3d9593/src/cmd/go/build.go#L362
是否有一个特定的标志可以传递去构建,而不是
-buildmode=c-sharedor 是否缺少 windows/amd64
上面的开关/外壳
https://github.com/nadiasvertex/go/blob/066eb2b2eecf4bcdf814d86f3c64ad3c4b3d9593/src/cmd/go/build.go#L356-L360
?


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-300682950或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvEBNzQfzaEM0n-I_Asty7yyYnMg9ks5r4paHgaJpZM4E3dB1
.

@jstoiko :我在 github 存储库中修复了这个问题。 看起来这是 1.8 中的新代码。

我通过@nadiasvertex拉品牌,是否有任何文档来解释如何使用该功能?

  1. 只是这个与否
go build --buildmode=c-shared -o test.dll
  1. 我可以通过上述方法在 Mac OSX 平台上交叉构建 dll 吗?

只是想检查一下这个状态。 越过这条线需要什么?

@nadiasvertex我也想回应@janckerchen关于交叉编译的问题。 这可以与@karalabexgo 一起使用吗? 这就是我目前输出全套 darwin/unix 二进制文件的方式。

对于我正在从事的特定项目以及总体而言,如果能够完成最后一块拼图,那就太好了。 一个完全跨平台的 buildmode=c-shared 使得在用许多语言构建共享一个共同的快速 golang 核心的库方面做一些令人惊奇的事情成为可能。

如果在我的 GitHub fork 上做一些测试,那会给我们很多
更有信心。 我在重写共享单元测试方面取得了进展
图书馆。 它已经通过了一轮反馈。 我需要
整合新的反馈。 这个周末我可能有更多的时间。

我没有看过 xgo,但我会尝试。

2017 年 6 月 16 日星期五下午 3:09 Dane Schneider [email protected]
写道:

只是想检查一下这个状态。 需要什么才能得到
这过线了?

@nadiasvertex https://github.com/nadiasvertex我也想回声
@janckerchen https://github.com/janckerchen关于的问题
交叉编译。 这可以与@karalabe 一起使用吗
https://github.com/karalabe的 xgo https://github.com/karalabe/xgo
这就是我目前输出一整套跨平台二进制文件的方式。

能把这最后一块拼图做好就太好了,
对于我正在从事的特定项目以及一般情况下的项目。 一个完全
跨平台 buildmode=c-shared 可以在
以多种语言构建库的术语,这些库共享一个共同的快速
戈朗核心。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-309109977或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvBLH78kpvqvu8fV15oXmSv7LDyJ-ks5sEtLVgaJpZM4E3dB1
.

抱歉,理解这个问题的状态以及是否有即将到来的 ETA 令人困惑。 它显然不适用于 Win 10 ,在 GO 1.8.3 下,即使用 -buildmode=c-shared
谢谢

@amiracam :您可能想尝试一下@nadiasvertex的分叉,请参阅上面的链接

我最近收到了一个补丁,这是这个补丁的先决条件。 一世
通过此更改在 GitHub 中维护一个测试分支。 我希望人们会
测试它并让我知道结果。

无论哪种方式,我都将开始更新 testcshared 以使用 Windows
为了验证这个补丁是否有效。

2017 年 8 月 22 日 14:57,“amiracam” [email protected]写道:

抱歉,让您无法理解此问题的状态以及是否存在
ETA 在地平线上。 它显然不适用于 Win 10 ,在 GO 1.8.3 下,即
使用 -buildmode=c-shared
谢谢


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/golang/go/issues/11058#issuecomment-324119589或静音
线程
https://github.com/notifications/unsubscribe-auth/AB8JvGR1BFE29p6h-cVaGhAl_OAI9HzGks5sayScgaJpZM4E3dB1
.

@nadiasvertex顺便说一句,在您的 repo 上,克隆会导致与下载 repo 的 zip 不同的 repo 源内容。 此外,它似乎是包含解决 Windows 支持问题的代码的 zip

@amiracam如上所述,修复程序位于repo 的一个分支中

我计划很快针对 1.9 进行 rebase,因此我还将在那里添加一个新分支。 不过,我并不是要维护替代品,只是为了测试。

@nadiasvertex我很乐意在 1.9 rebase 后给你的分支一个机会! 让我们知道:)

今天晚些时候我会拿一个 windows 盒子:+1:

理解,鉴于我通过提供的链接导航到所述分支,“克隆”按钮复制到剪贴板功能会生成一个 git url,该 url 克隆一个不包含相关 mod 的 repo,而从同一页面下载 zip 功能将生成包含相关 mod 的 repo 存档。

我们使用了 zip 版本,初步的 hello world-ish 测试成功,将继续使用它,因为我们想要将我们正在开发的 GO 库重构为在 Windows 64 位上运行的许多 C 程序,将报告任何我们遇到的问题,谢谢

@nadiasvertex顺便说一句,不确定是否与您的更改有关,但生成的 .h 有 3 个错误:
消息:'标识符“__SIZE_TYPE__”未定义'在:'29,9'来源:''
消息:'预计';''在:'32,24' 来源:''
消息:'预计';''在:'33,25' 来源:''

https://www.screencast.com/t/z0FRYqUns1Q

@amiracam这很奇怪,因为我在该分支的提交列表中看到了补丁。 不过,我会调查一下。 感谢您指出了这一点!

关于生成的 .h 文件,我的补丁与此无关。 它们只是编译器和链接器的更改。 我没有碰 C FFI 层。 我想知道您是否使用受支持的 C 编译器。

@nadiasvertex ,是的,同意,我从 GIT 页面中检查了补丁,它们确实显示在那里,然后我看了看 zip。

我在 Win 10 64 位,我相信它使用 GCC,实际上我使用 GCC 来编译项目

@nadiasvertex ,所以我可以生成一个 .h 和 .dll 并可以将其导入 ac 项目并进行构建,该构建将生成一个没有任何错误的可执行文件,但是当我尝试运行可执行文件时,它运行没有错误但什么也不做,即。 它应该有打印到控制台,我可以明天早上提交我非常琐碎的实验,顺便说一句,我正在使用 TDM-GCC 发行版

@nadiasvertex
去代码:

包主

导入“fmt”
导入“C”

函数 main(){}
//导出HelloWorld
功能你好世界(){
fmt.Println("你好")
}

哪个通过
go build -buildmode=c-shared -o hello.dll hello.go

生成一个 hello.dll 以及 hello.h
顺便说一句,还尝试将输出命名为 hello.so

C代码(驱动程序.c):

包括

包括“你好.h”

诠释主要(){
setvbuf(stderr, NULL, _IONBF, 0);
文件 *fn = fopen64("hello.txt", "w");
fprintf(fn, "你好");
fclose(fn);
printf("你好 \n");
你好世界();
返回0;
}

我用的
gcc -o hello.exe driver.c hello.so

这将生成 hello.exe

执行时绝对不会做任何事情

如果我注释掉导出的 HelloWorld 函数以及 hello.h 和 compile 的包含,我确实得到了一个工作 exe,但当然这只是一个健全性检查。 关于链接共享对象/ dll 的一些东西会生成一个静默的非工作可执行文件。

我是 C 新手,对 GO 也很陌生,我该如何调试呢?

谢谢

@amiracam你在 Linux 或 macOS 下试过吗? 如果是这样,它在那里工作吗?

是的,先生,在 MacOS Sierra 上运行良好,尚未在 Ubuntu 下设置 GO,但如果它在 Mac 上运行,很确定它可以在 Linux 上运行,最终我们希望成为跨平台 Windows / Linux,我也将在那里开始测试

@amiracam好的。 知道这一点很有用。 我会仔细看看的。

@nadiasvertex
也许其他有用的东西是我们使用 buildmode=c-archive 取得了成功,基本上遵循这里的说明:
https://stackoverflow.com/questions/40573401/building-a-dll-with-go-1-7

但使用干净的 GO 1.8.3 发行版

在赢 10

我们可以生成一个工作可执行文件

@nadiasvertex嗨,你现在有 1.9 的变基吗? 谢谢

@ianlancetaylor

我正在尝试在 Windows 上进行 c-shared 工作。 在 cmd/go/internal/work/build.go:BuildModeInit 中有一个 codegenArg 变量 - 它控制链接器的 -installsuffix 参数。 对于 darwin,codegenArg 是空白的,但对于其他操作系统,它设置为“-shared”。 我应该为 Windows 设置什么? 无论哪种方式,它似乎都有效。 谢谢你。

亚历克斯

更改https://golang.org/cl/68410提到这个问题: misc/cgo/testcshared: skip all but TestExportedSymbols on windows

@alexbrainman codegenArg被传递给编译器和汇编器,并设置安装后缀。 该参数的主要目的是更改代码生成。 如果您不需要将任何特殊参数传递给 Windows 上的编译器——如果普通代码在共享库中工作正常——那么不要设置codegenArg 。 关于 installsuffix 的一点是将使用-shared编译的包与以正常方式编译的包分开。 希望这是有道理的。

希望这是有道理的。

这确实有道理。 谢谢你的解释。 这就是我阅读代码的方式。

它只是codegenArg=""codegenArg="-shared"都适用于编译器和汇编器,我需要选择一个值。 我选择的值将影响链接器将构建的包存储在哪里。 例如 misc/cgo/testcshared/cshared_test.go 有一些代码https://github.com/golang/go/blob/d153df8e4b5874692f4948e9c8e10720446058e3/misc/cgo/testcshared/cshared_test.go#L43 :L54 需要更改以支持我的改变。 我想知道还有谁会受到影响? 如果有一天我们需要更改codegenArg的值会怎样? 达尔文已经在那条船上了。 因此,根据您的建议,我将暂时使用codegenArg=""

亚历克斯

更改https://golang.org/cl/68770提到这个问题: misc/cgo/testcshared: delete testp0.exe not testp0 file

更改https://golang.org/cl/69091提到了这个问题: cmd/dist, cmd/link, cmd/go: make c-shared work on windows

更改https://golang.org/cl/69090提到这个问题: misc/cgo/testcshared: use correct install directory on windows

此页面是否有帮助?
0 / 5 - 0 等级