同一个共享对象的多次加载(dlopen)和卸载(dlclose)调用导致段错误。

huangapple go评论93阅读模式
英文:

Multiple load (dlopen) and unload (dlclose) of the same shared object calls resulting in segmentation fault

问题

在我的代码中,我有一个for循环,在循环中首先调用dlopen加载一个共享对象,然后调用加载的库的函数,最后调用dlclose卸载它。循环的第一次迭代按预期工作,但在第二次迭代(当i=1时),dlopen调用导致段错误(核心已转储)。

    void *handle;
    char* (*goImg)();
    char *error;
    int i;
    for (i=0; i<5; i++) {
        handle = dlopen("mylib.so", RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

       goImg = dlsym(handle, "writeToFileInWrapper");
       if ((error = dlerror()) != NULL)  {
           fputs(error, stderr);
           exit(1);
       }
       goImg();

        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }
       dlclose(handle);
   }

生成mylib.so的脚本:

echo "Building golang code to create an archive file."
go build -buildmode=c-archive -o bin/goHelper.a goHelper.go

echo "Building wrapperCodeInC to be consumed as a shared library."
gcc -c -fPIC -o bin/shared/wrapperCodeInC.o -I./bin -I./wrapper wrapper/wrapperCodeInC.c

gcc -s -shared -lpthread -Wl,-Bsymbolic -o bin/mylib.so -Wl,--whole-archive bin/goHelper.a -Wl,--no-whole-archive bin/shared/wrapperCodeInC.o

这里,goHelper.go中有几个用Go语言编写的函数,而wrapperCodeInC.c中有用于调用这些Go函数的包装函数。

在循环的第一次运行中,dlopen()、goImg()和dlclose()按预期工作,但在第二次运行(i=1)期间,dlopen导致核心转储。有什么想法是什么原因导致这个问题的?

注意:如果我从构建文件中删除-Wl,-Bsymbolic,那么我会得到一个类似于这个问题的错误:https://github.com/golang/go/issues/30822

如果我在dlopen调用中添加RTLD_NODELETE标志(dlopen("mylib.so", RTLD_LAZY | RTLD_NODELETE)),那么所有的迭代都会正常运行,但我不确定这是否是正确的做法。

英文:

In my code, I have a for loop where first I am calling dlopen to load a shared object, then calling a function of the loaded library, and then dlclose to unload it. The first iteration of the loop works as expected but during the second iteration (when i=1) dlopen call is causing segmentation fault (core dumped).

    void *handle;
    char* (*goImg)();
    char *error;
    int i;
    for (i=0; i&lt;5; i++) {
        handle = dlopen(&quot;mylib.so&quot;, RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

       goImg = dlsym(handle, &quot;writeToFileInWrapper&quot;);
       if ((error = dlerror()) != NULL)  {
           fputs(error, stderr);
           exit(1); }
       goImg();

        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }
       dlclose(handle);
   }

Script to generate mylib.so:

echo &quot;Building golang code to create an archive file.&quot;
go build -buildmode=c-archive -o bin/goHelper.a goHelper.go

echo &quot;Building wrapperCodeInC to be consumed as a shared library.&quot;
gcc -c -fPIC -o bin/shared/wrapperCodeInC.o -I./bin -I./wrapper wrapper/wrapperCodeInC.c

gcc -s -shared -lpthread -Wl,-Bsymbolic -o bin/mylib.so -Wl,--whole-archive bin/goHelper.a -Wl,--no-whole-archive bin/shared/wrapperCodeInC.o

Here, goHelper.go has few functions written in go language and wrapperCodeInC.c has the wrapper functions to invoke those go functions.

In the first run of the loop dlopen(), goImg(), and dlclose() works as expected but then during the second run (i=1), dlopen is dumping core. Any idea what could be causing this?

Note: If I remove -Wl,-Bsymbolic from the build file, then I get an error similar to this issue: https://github.com/golang/go/issues/30822

If I add flag RTLD_NODELETE in dlopen call (dlopen("mylib.so", RTLD_LAZY | RTLD_NODELETE )), then all the iterations run fine but I am not sure if that is the right thing to do.

答案1

得分: 1

dlclose在Go共享库上不起作用,并且这个问题自2015年以来一直存在。问题的根源似乎是Go没有提供一种优雅地终止运行时可能启动的任何后台线程的方式,而这些线程在调用dlclose时可能仍在运行。

除了这个限制之外,dlclose似乎并不需要做太多事情

另请参阅https://stackoverflow.com/q/47425089/33732。

因此,通过使用RTLD_NODELETE,您可以提醒库的缺乏“可卸载性”。

如果这是一个更通用的“插件”系统的一部分,其中可以使用多种不同语言编写的库,那么您可能不希望将RTLD_NODELETE应用于所有库。在这种情况下,您可以尝试在创建共享库时使用-z nodelete链接器选项。然后调用dlopen的代码不需要了解加载的任何特定.so文件的怪癖。(在Go中有代码自动添加该参数;我认为如果您使用go tool link进行链接而不是直接运行gcc,则会使用该参数。)

英文:

dlclose doesn't work on Go shared libraries, and it's been an open issue since 2015. The root of the issue seems to be that Go offers no way to gracefully terminate any background threads that the runtime might have started, and which might still be running at the time you call dlclose.

Besides that limitation, dlclose is apparently not required to do much of anything anyway.

See also https://stackoverflow.com/q/47425089/33732.

Thus, it seems that you may as well call attention to the library's lack of "unloadability" by using RTLD_NODELETE.

If this is part of a more general "plug-in" system, where you could have libraries written in several different languages, then you might not want to apply RTLD_NODELETE to all of them. In that case, you could try using the -z nodelete linker option when creating the shared library. Then the code that calls dlopen doesn't need to be aware of the quirks of any particular .so file it loads. (There's code in Go to add that parameter automatically; I think it's used if you use go tool link to do your linking instead of running gcc directly.)

huangapple
  • 本文由 发表于 2021年11月11日 12:11:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/69923098.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定