为了账号安全,请及时绑定邮箱和手机立即绑定

LD_PRELOAD-ing go 可执行文件时未调用共享对象中的构造函数

LD_PRELOAD-ing go 可执行文件时未调用共享对象中的构造函数

Go
守着星空守着你 2022-06-06 14:42:48
在 Alpine 映像中构建的 GO 可执行文件存在一个奇怪的行为,其中标准 LD_PRELOAD 功能无法正常工作。看起来动态加载器没有调用构造函数!我有一个例子去应用程序(getgoogle.go):package mainimport (    "fmt"    "net/http")func main() {    resp, err := http.Get("http://google.com/")    if err == nil {        fmt.Println(resp.StatusCode)    }}以及示例共享对象代码 ( libldptest.c)#include <stdio.h>static void __attribute__((constructor)) StaticConstructor(int argc, char **argv, char **env){    printf(">>> LD_PRELOADED!\n");}我正在使用这个 Dockerfile(gotest图像)创建一个基于 debian 的 docker 图像:FROM golangCOPY libldptest.c hello-world.go /RUN gcc -shared -o /libldptest.so /libldptest.cRUN go build -gcflags='-N -l' -o /getgoogle /getgoogle.goENV LD_PRELOAD=/libldptest.so然后运行以下命令:$docker run -it gotest /getgoogle>>> LD_PRELOADED!200这意味着构造函数在这里工作。但是当对基于 alpine 的 docker 镜像做同样的事情时FROM golang:1.12-alpineRUN apk add gcc libc-devCOPY libldptest.c hello-world.go /RUN gcc -shared -o /libldptest.so /libldptest.cRUN go build -gcflags='-N -l' -o /getgoogle /getgoogle.goENV LD_PRELOAD=/libldptest.so并运行与上面相同的命令$docker run -it gotest /getgoogle200$docker run -it gotest ls>>> LD_PRELOADED!bin  src这意味着运行 go 应用程序时未调用静态构造函数!(但在运行时被调用ls)请注意,我已经检查过动态加载程序是否将库添加到进程空间。我很感激能理解它为什么不起作用。
查看完整描述

2 回答

?
明月笑刀无情

TA贡献1828条经验 获得超4个赞

不要忽略第一条评论。如果您坚持使用 Go 的内部链接器,该链接器不以与 libc 使用兼容的方式链接,那么您将不能使用任何 C 代码,包括LD_PRELOADed C 代码甚至动态链接器本身的功能。正如 Florian(来自 glibc)在链接问题中所说,它对 glibc 也无效,并且只是偶然地在那里“工作”。

即使您以某种方式“机械地”弄清楚为什么没有调用您的ctor,您仍然在损坏的进程状态下运行C代码,并且任何事情都可能出错。即使您分析了所有内容并且看起来很好,这也可以随着下一次动态链接器/libc 更新而完全改变。

如果你想这样做,请使用 Go 中的外部链接器选项。


查看完整回答
反对 回复 2022-06-06
?
侃侃无极

TA贡献2051条经验 获得超10个赞

从评论中可以看出,Go/Alpine 环境中的静态构造函数存在一个主要问题。简而言之,从 ABI 的角度来看,调用静态构造函数的需求分配给了可执行文件,而不是加载程序。Go 可执行文件不是基于 C 运行时的,它只调用依赖共享对象的静态构造函数,而不是LD_PRELOAD-ed 共享对象。在 glibc 的情况下, a LD_PRELOAD-ed 共享对象的构造函数由实现调用,而不是由加载器设计。在 musl-libc 上它们不是。


我做了一个“hack”-ish解决方法,让现有的 Go 应用程序与LD_PRELOAD-ed 共享对象一起工作。我正在使用这样一个事实,即LD_PRELOADmusl-libc 在此环境中正确地编辑了该库,并且 Gopthread_create在初始化的早期阶段就调用了该库。


我正在覆盖/挂钩-ed 共享对象pthread_create中的符号LD_PRELOAD并使用它来调用构造函数。


#include <pthread.h>

#include <dlfcn.h>


int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)

{

    int (*pthread_create_original)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = dlsym(RTLD_NEXT,"pthread_create");

    static int already_called = 0;


    if (!already_called)

    {

        already_called = 1;

        // call here your constructors

    }


    return pthread_create_original(thread,attr,start_routine,arg);

}

警告:这适用于当前的 Go 运行时,但构建此解决方案的假设远非未来证明。Next Go 版本很容易破坏它。


查看完整回答
反对 回复 2022-06-06
  • 2 回答
  • 0 关注
  • 121 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信