Go二进制文件在其他机器一执行就退出的问题

在编译机上把一个go项目编译成二进制,cp到服务器(和编译机不是一台机器,但操作系统和指令集架构一样)的某个目录,执行后直接退出了,没有任何报错信息。



在本地Mac上把代码clone下来,go build编译成二进制,可以正常运行。在Mac上交叉编译成linux/amd64的二进制文件,sz到测试服务器上,还是会直接退出。之后又找了一台ubuntu amd64的机器,还是一样。


但在这台ubuntu amd64机器上clone代码,进行编译,又可以正常运行…

后面发现,引入Mac使问题变复杂了…其实本身很简单: 在Ubuntu系统 amd64架构机器A上面源码编译,并可正常运行的二进制文件,sz到另一台Ubuntu系统 amd64架构的机器B上,运行后直接退出,没有任何报错


询问群友:

  • ldd (链接库缺失,或者版本不对啥的)

  • cgo 禁掉试试 (cgo禁掉bin又大一圈?) 。还是 CGO_ENABLED=0 吧, 没烦扰


ldd是什么?有何作用?


ldd 是 Linux 下的一个命令行工具,用于打印共享库的依赖关系。它的作用是查看一个可执行文件或共享库所依赖的其他共享库。

在 Linux 系统中,可执行文件和共享库通常依赖于其他共享库,这些共享库提供了程序所需的函数和符号。ldd 命令可以帮助我们了解一个程序的依赖关系,以及它需要哪些共享库才能正常运行。

使用 ldd 命令,您可以通过指定一个可执行文件或共享库的路径作为参数来查询其依赖项。ldd 将会列出该文件所直接依赖的共享库,并显示它们的路径。这对于调试和解决程序运行时的依赖问题非常有用。

以下是使用 ldd 命令的示例:

1
ldd /path/to/executable

ldd 命令将输出类似以下的结果:

1
2
3
4
5
6
7
8
linux-vdso.so.1 (0x00007ffddc1d5000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f956d6e5000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f956d4e1000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f956d15e000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f956cdc0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f956c9cf000)
/lib64/ld-linux-x86-64.so.2 (0x00007f956d98b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f956c7b7000)

上述输出显示了被查询文件直接依赖的共享库及其路径。这些依赖库在程序执行时将被加载。

总之,ldd 是一个有用的工具,可用于分析和调试可执行文件和共享库的依赖关系,有助于解决共享库缺失或版本不匹配等问题。


执行ldd 后,如何验证共享库是否可用?


要验证共享库是否存在,您可以使用以下方法:

  1. 使用 ls 命令检查共享库文件是否存在。例如,对于 /lib/x86_64-linux-gnu/libpthread.so.0,您可以运行以下命令来检查文件是否存在:
1
ls /lib/x86_64-linux-gnu/libpthread.so.0

如果文件存在,将显示文件路径和名称。

  1. 使用 file 命令查看共享库文件的详细信息。例如,对于 /lib/x86_64-linux-gnu/libpthread.so.0,您可以运行以下命令:
1
file /lib/x86_64-linux-gnu/libpthread.so.0

该命令将显示有关文件的详细信息,包括文件类型和架构。

  1. 如果您拥有合适的权限,您还可以使用 ldconfig 命令来验证共享库是否在系统的库路径中。例如,对于 /lib/x86_64-linux-gnu/libpthread.so.0,您可以运行以下命令:
1
ldconfig -p | grep libpthread.so.0

如果库文件存在并已正确配置,将显示该库的路径和版本信息。

请注意,上述方法假设您具有适当的权限来访问和执行这些命令。如果在执行命令时遇到权限问题,请尝试使用 sudo 命令以管理员权限运行。


一般用ls就够了~


### linux-vdso.so.1 这个为什么没有路径?

linux-vdso.so.1 是一个特殊的虚拟动态共享对象(Virtual Dynamic Shared Object),用于实现某些系统调用的快速路径。它在 Linux 内核中提供,并不是一个实际的共享库文件。

linux-vdso.so.1 不是一个独立的文件,它在内核加载可执行文件时由动态链接器自动注入到进程地址空间中。它的作用是优化某些系统调用的执行,以避免陷入到内核空间并减少系统调用的开销。它对应于特定的内核版本和硬件架构,因此其地址和功能在不同的系统上可能会有所不同。

因此,您不需要担心验证 linux-vdso.so.1 是否存在或查找其文件路径。它是 Linux 内核的一部分,并且会在运行时自动注入到进程中。在 ldd 命令的输出中,它通常被列为 (vdso),表示它是通过虚拟方式提供的动态共享对象。

需要注意的是,由于它是一个虚拟共享对象,因此无法直接访问其文件或文件路径。


/lib64/ld-linux-x86-64.so.2 这个呢?


/lib64/ld-linux-x86-64.so.2 是 Linux 系统上的动态链接器/loader,负责在可执行文件运行时加载共享库和解析符号链接。它是在程序启动时由内核自动调用的。

该文件是动态链接器的二进制文件,它负责加载可执行文件所需的共享库,并解析程序中引用的符号。它是一个重要的系统文件,用于运行基于动态链接的可执行文件。

验证 /lib64/ld-linux-x86-64.so.2 是否存在的方法和之前提到的方法相同:

  1. 使用 ls 命令检查文件是否存在:
1
ls /lib64/ld-linux-x86-64.so.2
  1. 使用 file 命令查看文件的详细信息:
1
file /lib64/ld-linux-x86-64.so.2

如果文件存在,将显示文件的路径和详细信息。

请注意,/lib64/ld-linux-x86-64.so.2 是系统级文件,通常会随着操作系统的安装而存在,并且在正常情况下不应该缺失。如果您发现此文件缺失或存在问题,可能需要重新安装操作系统或修复系统文件来恢复其功能。


Golang中CGO_ENABLED=0 什么意思?


在 Go 语言中,CGO_ENABLED 是一个环境变量,用于控制 Go 编译器是否启用 CGo(C 调用 Go)功能。当设置为 0 时,表示禁用 CGo 功能。

CGo 是 Go 语言提供的一项功能,它允许在 Go 代码中直接调用 C 代码,并且能够让 C 代码调用 Go 语言的函数。这对于需要与现有 C/C++ 代码进行交互的情况非常有用。

设置 CGO_ENABLED=0 的作用是禁用 CGo 功能,使 Go 编译器不会处理任何与 C 代码相关的内容。在这种情况下,Go 代码不能直接调用 C 函数,也不能被 C 代码调用。

禁用 CGo 可以用于一些特定的场景,比如构建纯 Go 语言的静态可执行文件,或在一些环境中限制了使用 CGo 的情况下。

要禁用 CGo,可以在执行 Go 命令或构建过程时设置 CGO_ENABLED 环境变量为 0,例如:

1
CGO_ENABLED=0 go build

这将告诉 Go 编译器禁用 CGo 功能,并仅编译纯 Go 代码。



在B机器上,ldd 二进制文件 (一执行就退出,无任何报错信息)

1
2
3
4
linux-vdso.so.1 (0x00007fffd97fc000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f61b4766000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61b4592000)
/lib64/ld-linux-x86-64.so.2 (0x00007f61b478f000)

在A机器上,ldd 二进制文件 (可正常执行)

1
2
3
4
linux-vdso.so.1 =>  (0x00007ffce968d000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fe95be54000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe95ba8a000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe95c071000)

在A机器上如果用CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build,即禁用cgo,再ldd 编译出的二进制文件,则会报 not a dynamic executable

但可以正常执行

将禁用cgo的二进制文件sz到B机器上,ldd也会报 not a dynamic executable,但执行后还是马上退出


静态编译是什么?与之相关的是什么?


静态编译(Static Compilation)是一种将程序的所有依赖都打包到可执行文件中的编译方式。在静态编译中,所有的库和依赖在编译时被链接到最终的可执行文件中,使得可执行文件独立于系统上的其他库或依赖项,可以在没有额外依赖的情况下直接运行。

与静态编译相关的概念是动态链接(Dynamic Linking)。在动态链接中,程序的依赖项(如共享库)在运行时被加载并链接到可执行文件中。这意味着可执行文件本身只包含了程序的代码和一些必要的信息,而实际的依赖项将在运行时从系统中的共享库中加载。

静态编译的主要优点是:

  1. 独立性:静态编译生成的可执行文件包含了所有的依赖项,因此可以在没有额外依赖的系统上运行,不受系统上共享库版本或依赖项的影响。

  2. 可移植性:静态编译的可执行文件可以在不同的系统上运行,而无需安装特定的库或依赖项。

  3. 安全性:静态编译可以提供一定程度的安全性,因为它可以减少对系统上已安装共享库的依赖,从而减少受到已知漏洞的风险。

然而,静态编译也有一些潜在的缺点:

  1. 文件大小:由于将所有依赖项嵌入到可执行文件中,静态编译的可执行文件通常比较大。

  2. 更新和维护:如果库或依赖项有更新或修复,需要重新编译和重新分发整个程序。

  3. 内存使用:每个静态编译的程序实例都会占用独立的内存空间,可能导致资源浪费。

在实际应用中,可以根据具体需求和情况来选择使用静态编译还是动态链接。一些场景,如构建独立、可移植的可执行文件或在资源受限的环境中部署程序,静态编译可能更合适。而在需要动态加载和共享依赖的情况下,动态链接则更常见。

go是静态编译吗?

Go 语言在默认情况下是采用静态编译的方式生成可执行文件。当您使用 go build 命令来构建 Go 代码时,Go 编译器会将程序的所有依赖项(包括标准库和第三方库)静态链接到最终的可执行文件中。

这意味着在使用静态编译的情况下,生成的可执行文件是独立于系统上的其他库或依赖项的,可以在没有额外依赖的系统上直接运行。您可以将生成的可执行文件复制到其他相同操作系统和架构的机器上,并且它应该能够在那里运行,而无需安装其他依赖。

当然,如果您使用了 Cgo(C 调用 Go)功能,那么在编译过程中可能会有部分动态链接的情况。Cgo 允许在 Go 代码中直接调用 C 代码,并且需要依赖系统上的共享库。但是,默认情况下,Go 编译器会将 Cgo 编译为静态链接的形式,以便生成一个独立的可执行文件。

总而言之,Go 语言在大多数情况下使用静态编译的方式,生成的可执行文件独立于系统上的其他库和依赖项。这使得 Go 代码的部署和分发变得更加简单和方便。


以下是一些命令和工具,您可以使用它们来进一步诊断二进制文件在机器 B 上无法正常运行的问题:

  1. ldd:在机器 B 上运行 ldd <binary_file> 命令,其中 <binary_file> 是您的二进制文件的路径。ldd 命令将显示二进制文件所依赖的共享库。确保这些共享库在机器 B 上都是可用的。

  2. strace:在机器 B 上使用 strace 命令来跟踪程序的系统调用。运行 strace <binary_file>,观察输出以查看是否有错误或异常情况。

  3. gdb:如果您的二进制文件是使用调试信息编译的,您可以使用 gdb 调试器来调试程序。运行 gdb <binary_file>,然后使用命令 run 启动程序,并观察输出和调试信息以找出问题所在。

  4. 日志输出:如果您的程序支持日志输出,您可以尝试将输出重定向到一个日志文件中,然后查看日志文件以查找错误消息或异常情况。

这些命令和工具可以帮助您进一步了解程序在机器 B 上的行为,并找出问题所在。根据您的具体情况,可能需要结合使用多个工具来进行问题诊断。

发现ldd 的共享库,在机器B上都存在。。使用strace(没有的话需要sudo apt install strace)



查了半天无果,要来了代码,添加了些调试信息,发现是代码的问题 (因为对代码不熟悉,以运维身份去部署。。)

1
2
3
4
5
6
7
8
9
10
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "./config.json",
},
cli.StringFlag{
Name: "log-dir",
Value: "./app.log",
},
}

即是动态读取配置。。如果当前目录没有config.json,则读不到配置,某步就直接退出了(而没有留下什么信息…这样处理显然不好。。)

所以实际上,之所以在其他机器上一执行就退出,只是单单因为其他机器上二进制所在目录没有config.json文件…