【问题标题】:Why does dlsym produce different results in cgo than in c?为什么 dlsym 在 cgo 中产生的结果与在 c 中不同?
【发布时间】:2019-06-23 02:42:09
【问题描述】:

我有两个相同行为的实现,我认为它们应该会产生相同的结果,但会产生不同的结果。在 Go 中使用 cgo 编译时,我得到的符号地址解析与在 C 中编译时不同。我想了解原因。

我将问题简化为几个小例子,一个用 C 语言,一个用 Go 语言。我在我的 Mac 笔记本电脑上运行的 Ubuntu 18 Docker 容器中测试了这些。

test.c:

// gcc test.c -D_GNU_SOURCE -ldl
// Output: Real: 0x7fd05559d7d0 Current: 0x7fd05559d7d0

#include <dlfcn.h>
#include <stdio.h>

int main() {
    void * fd = dlopen("libc.so.6", RTLD_LAZY);
    void * real_sym = dlsym(fd, "accept");
    void * curr_sym = dlsym(RTLD_NEXT, "accept");
    printf("Real: %p Current: %p\n", real_sym, curr_sym);
    return 0;
}

test.go:

// go build test.go
// Output: Real: 0x7f264583b7d0 Current: 0x7f2645b1b690
package main

// #cgo CFLAGS: -D_GNU_SOURCE
// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
import "C"
import "fmt"

func main() {
    fp := C.dlopen(C.CString("libc.so.6"), C.RTLD_LAZY)
    real_sym := C.dlsym(fp, C.CString("accept"))
    curr_sym := C.dlsym(C.RTLD_NEXT, C.CString("accept"))
    fmt.Printf("Real: %p Current: %p\n", real_sym, curr_sym)
}

test.c 被编译时,我得到Real: 0x7fd05559d7d0 Current: 0x7fd05559d7d0 的输出(gcc test.c -D_GNU_SOURCE -ldl)。但是,当我构建test.go 时,我看到了Real: 0x7f264583b7d0 Current: 0x7f2645b1b690

我假设 go 本身包装了一些符号,但我想确切地知道发生了什么。谢谢!


在看到一些最初的 cmets 之后,还有几件额外的作品。我如下更改了test.c,然后循环运行(while [ 1 ]; do ./a.out; done)。它始终为我获得相同的地址(但每次运行都不同)。

// gcc test.c -D_GNU_SOURCE -ldl
// Output: Real: 0x7fd05559d7d0 Current: 0x7fd05559d7d0

#include <dlfcn.h>
#include <stdio.h>

    int main() {
    void * fd = dlopen("libc.so.6", RTLD_LAZY);
    void * real_sym = dlsym(fd, "accept");
    void * curr_sym = dlsym(RTLD_NEXT, "accept");
    if(real_sym != curr_sym) {
        printf("Real: %p Current: %p\n", real_sym, curr_sym);
    }
    return 0;
}

我还尝试修改 Go 代码以检查它是否与 Go 调用 C 的方式有关,但地址仍然不匹配:

// go build dos.go
// Output: Real: 0x7f264583b7d0 Current: 0x7f2645b1b690
package main

// #cgo CFLAGS: -D_GNU_SOURCE
// #cgo LDFLAGS: -ldl
// #include <dlfcn.h>
// #include <stdio.h>
// int doit() {
//     void * fd = dlopen("libc.so.6", RTLD_LAZY);
//     void * real_sym = dlsym(fd, "accept");
//     void * curr_sym = dlsym(RTLD_NEXT, "accept");
//     printf("Real: %p Current: %p\n", real_sym, curr_sym);
//     return 0;
// }
import "C"

func main() {
    C.doit()
}

另一点是,如果我查找 malloc 符号而不是 accept,我会在 C 和 Go 代码中得到匹配的两个地址。

【问题讨论】:

  • 你为什么希望它们是一样的?如果你愿意,我稍后会删除这条无益的评论,但我不明白你的假设。
  • 动态链接器不一定每次都在同一(虚拟)地址加载库和单个符号。我目前看不出有任何理由期望这两个程序的输出是相同的。
  • 如果你重复运行任何一个程序,你会得到相同的结果吗?
  • 您的dlopen 可能已经加载了您应该避免的 另一个 libc.so.6。对此有一个RTLD_NOLOAD 选项。还有一个噩梦,称为符号版本控制...

标签: c go dlopen cgo dlsym


【解决方案1】:

原因是 Go 链接到 libpthread,但你的 C 程序没有。如果我将-lpthread 添加到 gcc 参数,它也会打印不同的指针。因此,libpthread 定义了自己的 accept 并覆盖了 libc (这很有意义)。

我想出的方法是我在两个程序中都插入了一个睡眠,然后翻遍了/proc/$pid/maps,看看返回的指针引用了什么。这表明在 Go 的情况下,“当前”指针驻留在 libpthread 中。 “真正的”指针总是引用 libc。

【讨论】:

    【解决方案2】:

    符号没有加载到内存中的固定地址;它们去装载机决定放置它们的任何地方。

    这是我在我的机器上多次运行你的 C 程序的输出。

    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7f4b5f3127d0 Current: 0x7f4b5f26ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7f45727127d0 Current: 0x7f457266ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7fc3373127d0 Current: 0x7fc33726ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7f0e555127d0 Current: 0x7f0e5546ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7f2fdd9127d0 Current: 0x7f2fdd86ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7fec7db127d0 Current: 0x7fec7da6ee30
    govind@Govind-PC:/mnt/c/Temp$ ./dlst
    Real: 0x7f07de1127d0 Current: 0x7f07de06ee30
    govind@Govind-PC:/mnt/c/Temp$
    

    另见:

    Address Space Layout Randomization

    【讨论】:

    • 我对ASLR的理解有点生疏。我正在尝试理解这篇文章。 ASLR 是否不适用于进程的多次运行和/或符号之间的关系?在这种情况下,当我们有相同的符号被同一个进程访问时,它不会有相同的地址吗?显然不是来自你的测试和我的 Go 代码。 . .但它似乎有时会成立。
    猜你喜欢
    • 2023-03-14
    • 2011-05-16
    • 2019-09-25
    • 1970-01-01
    • 2019-08-12
    • 2017-03-18
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多