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

Coo*_*oop 2 c go dlopen dlsym cgo

我有两个相同行为的实现,我认为应该产生相同的结果,但相反产生不同的结果.在Go using中编译时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;
}
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)

我得到了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;
}
Run Code Online (Sandbox Code Playgroud)

我还尝试修改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()
}
Run Code Online (Sandbox Code Playgroud)

另一点是,如果我寻找malloc符号而不是C和Go代码,我得到两个地址匹配accept.

Gov*_*mar 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$
Run Code Online (Sandbox Code Playgroud)

也可以看看:

地址空间布局随机化


Axe*_*ner 6

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

我想出来的方式是我在两个程序中插入一个睡眠,然后翻找/proc/$pid/maps以查看返回的指针引用的内容.这表明在Go的情况下,"当前"指针位于libpthread中."真实"指针总是引用libc.