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

在 Alpine 映像中构建的 GO 可执行文件存在一个奇怪的行为,其中标准 LD_PRELOAD 功能无法正常工作。


看起来动态加载器没有调用构造函数!


我有一个例子去应用程序(getgoogle.go):


package main


import (

    "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 golang

COPY libldptest.c hello-world.go /

RUN gcc -shared -o /libldptest.so /libldptest.c

RUN go build -gcflags='-N -l' -o /getgoogle /getgoogle.go

ENV LD_PRELOAD=/libldptest.so

然后运行以下命令:


$docker run -it gotest /getgoogle

>>> LD_PRELOADED!

200

这意味着构造函数在这里工作。


但是当对基于 alpine 的 docker 镜像做同样的事情时


FROM golang:1.12-alpine

RUN apk add gcc libc-dev

COPY libldptest.c hello-world.go /

RUN gcc -shared -o /libldptest.so /libldptest.c

RUN go build -gcflags='-N -l' -o /getgoogle /getgoogle.go

ENV LD_PRELOAD=/libldptest.so

并运行与上面相同的命令


$docker run -it gotest /getgoogle

200

$docker run -it gotest ls

>>> LD_PRELOADED!

bin  src

这意味着运行 go 应用程序时未调用静态构造函数!(但在运行时被调用ls)


请注意,我已经检查过动态加载程序是否将库添加到进程空间。


我很感激能理解它为什么不起作用。


守着星空守着你
浏览 122回答 2
2回答

明月笑刀无情

不要忽略第一条评论。如果您坚持使用 Go 的内部链接器,该链接器不以与 libc 使用兼容的方式链接,那么您将不能使用任何 C 代码,包括LD_PRELOADed C 代码甚至动态链接器本身的功能。正如 Florian(来自 glibc)在链接问题中所说,它对 glibc 也无效,并且只是偶然地在那里“工作”。即使您以某种方式“机械地”弄清楚为什么没有调用您的ctor,您仍然在损坏的进程状态下运行C代码,并且任何事情都可能出错。即使您分析了所有内容并且看起来很好,这也可以随着下一次动态链接器/libc 更新而完全改变。如果你想这样做,请使用 Go 中的外部链接器选项。

侃侃无极

从评论中可以看出,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){&nbsp; &nbsp; int (*pthread_create_original)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = dlsym(RTLD_NEXT,"pthread_create");&nbsp; &nbsp; static int already_called = 0;&nbsp; &nbsp; if (!already_called)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; already_called = 1;&nbsp; &nbsp; &nbsp; &nbsp; // call here your constructors&nbsp; &nbsp; }&nbsp; &nbsp; return pthread_create_original(thread,attr,start_routine,arg);}警告:这适用于当前的 Go 运行时,但构建此解决方案的假设远非未来证明。Next Go 版本很容易破坏它。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go