【问题标题】:Linux - same function called when loading so dynamically and staticallyLinux - 在动态和静态加载时调用相同的函数
【发布时间】:2023-03-28 06:05:02
【问题描述】:

我有一个编译成 .so 文件的 c++ 项目(使用 g++5 编译)。 在另一个项目中(在同一解决方案下)我有一个链接到该项目的测试(CMake 的命令 target_link_libraries(...))。

我编译项目,并将输出的 .so 文件复制到“/tmp/proj.so”。

除了将测试链接到项目之外,我还使用dlopen动态加载“/tmp/proj.so”,它有一个全局函数create_foo,它创建了一个新的foo对象。

我想要实现的目标是进行一项测试,将同一项目的两个版本与另一个版本进行比较,这样我就知道我不会因为更改项目中的内容而破坏任何东西。

dlopen打开后,我调用dlsym找到create_foo,然后调用它。 create_foo is 类似:

extern "C" bool create_foo(foo** instance){
    *instance = new foo();
    return true;
}

所以在我的测试中我有类似的东西(我删除了不相关的代码,如空检查):

#include <dlfcn.h>
#include "foo.h"

int main()
{
    foo f1;
    void* handle = dlopen("/tmp/proj.so", RTLD_NOW);
    bool(*create_foo_func)(foo**);
    create_foo_func = (bool(*)(foo**))dlsym(handle, "create_foo");

    foo* f2;
    (*create_foo_func)(&f2);

    assert(f1.bar() == 10);
    assert(f2->bar() == 10);
}

两个断言都可以。 所以接下来我将foo::bar改为return 5而不是10,编译了项目但我没有更改/tmp/proj.so文件! 当我运行程序时,我得到了:

f1.bar() == 5
f2->bar() == 5 //I would expect this to be 10 since I did not change it

所以我在两个电话中都得到了5,而不是我希望的f1.bar()==5f2-&gt;bar() == 10

我确定 dll 正在加载并且动态中的 create_foo 被调用(我可以在调试器的模块列表中看到它,并且如果我尝试 dlsym("NOT_create_foo") 它会失败,另一种方式也失败,即将 create_foo 函数名更改为某事但不更改 /tmp/proj.so)。

当我在代码中添加 printf("static links"),编译它并保持 /tmp/proj.so" 文件不变(意味着它没有这个 printf)时,我看到它被打印了两次。

那么我在这里做错了什么?

我正在处理的实际项目很大并且正在使用 CMake。我可能遗漏了我认为不相关的重要细节,如果您认为我应该在某个地方查看,请发表评论,我将编辑答案。

【问题讨论】:

  • foo::bar 函数是在foo 类中内联定义的吗?或者您的应用程序是否与包含 foo::bar 定义的(修改后的)源文件链接?
  • @Some 程序员老兄, foo::bar 不是内联的。它与定义一起编译。另请注意,问题始于构造函数。即当我在 ctor 中打印 f1 和 f2 打印时,尽管我没有复制修改后的 .so
  • 澄清一下。 foo 类的实现在您的程序中,而不是库中。因此,您对其所做的任何更改都将在您的程序中。该库真正做的只是创建一个实例,该实例的函数已经在您的程序中。
  • 我习惯了 Visual Studio 的术语(但现在我在 linux 中使用 CMake),所以在 Visual Studio 术语中,我有一个编译为动态库(.so 文件)的项目由几个头文件和源文件组成。这个 lib 中的主要对象是 Foo,所以 Foo 的头文件和实现在这个项目中。在另一个项目(与另一个 Cmake 的不同文件夹)中,我有一个引用该项目的测试(CMake 的“add_dependencies”和“target_link_libraries”),在这个测试中,我有来自问题的main 函数。 (在下一条评论中继续)
  • 所以我希望如果我更改 Foo 的实现,例如 Foo::Foo() 现在将打印“我不在 .so 文件中”,然后通过创建 @987654344 @ 这将被打印出来,但是当我使用 (*create_foo_func)(&amp;f2); 创建 f2 时,它不会打印这一行。不幸的是 f1 和 f2 都打印了这行,这意味着它们都创建了相同类型的对象(或至少使用相同的实现)

标签: c++ linux cmake shared-libraries dlopen


【解决方案1】:

恐怕你希望能够实现你想要的。就编译器而言,对象具有相同的类型,因此它会生成对相同实现的调用。在链接时,它将被解析为可执行文件中定义的foo::bar

      $ g++ main.cc -c
      $ objdump -rd
      ...
      44:   e8 00 00 00 00          callq  49 <main+0x49>
      45: R_X86_64_PC32       _ZN3foo3barEv-0x4
      ...
      6e:   e8 00 00 00 00          callq  73 <main+0x73>
      6f: R_X86_64_PC32       _ZN3foo3barEv-0x4

【讨论】:

  • 首先,谢谢。秒,你知道为什么会这样吗?第三,这种情况有解决方法吗?我想过重命名,但不确定它是否会起作用
  • “你知道为什么会这样吗?”——嗯,这就是它的工作原理——因为编译器两个对象具有相同的类型,所以它会生成汇编代码来调用相同的本地方法。跨度>
  • "第三,这种情况有解决办法吗?" - 您可以重命名 foo 的参考实现(例如 foo_ref)。在这种情况下,您将通知编译器区分两个对象。
【解决方案2】:

dlopen 手册页没有这样说,但 here 是这样说的

Only a single copy of an object file is brought into the address space, even if dlopen() is invoked multiple times in reference to the file, and even if different pathnames are used to reference the file.

和 linux dlopen 手册页:

   If the same library is loaded again with dlopen(), the same file handle is returned. 
   The dl library maintains reference counts for library handles, so a dynamic library 
   is not deallocated until dlclose() has been called on it as many times as dlopen() 
   has succeeded on it.

因此,dlopen 似乎将/tmp/proj.so 视为与已为测试可执行文件本身加载的库相同的库。您应该能够通过比较返回的句柄来测试它。

【讨论】:

  • 这不是我描述的情况。我不是动态打开同一个库,只有一次,第二次由链接器在链接中完成(编译后)
  • 相同:当您运行动态链接的二进制文件时,所需的共享库会以相同的机制为您加载。这是由 /lib/ld-linux.so 程序完成的。
猜你喜欢
  • 2014-02-07
  • 2020-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-20
  • 2014-10-28
  • 2011-09-09
  • 1970-01-01
相关资源
最近更新 更多