为什么dlsym在cgo中产生与在c中不同的结果?

时间:2019-01-29 21:45:19

标签: c go dlopen cgo dlsym

我有两个行为相同的实现,我相信应该会产生相同的结果,但是会产生不同的结果。使用cgo在Go中进行编译时,与在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)
}

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

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


看到一些最初的评论后,又多了一些。我如下更改了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代码中都将两个地址匹配。

2 个答案:

答案 0 :(得分:6)

符号未加载到内存中的固定地址中;无论装载机决定将它们放到哪里,它们都可以走。

这是我在计算机上多次运行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

答案 1 :(得分:6)

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

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