虚拟异常 class 导致动态链接器错误
Virtual exception class causes dynamic linker error
在小型复制器中,使用 vtable 的异常 class 的 typeinfo/vtable 的符号查找失败。为什么会出错?是否有可能使 RTTI 为 classes 和加载了 dlopen 的 vtable 正常工作?间接加载的目的是。
lib.h:
#include <exception>
class myexception : public std::exception {
virtual void info();
};
void f();
lib.cc:
#include "lib.h"
void myexception::info() {};
void f() { throw myexception(); }
main.cc:
#include "lib.h"
int main() {
try { f(); }
catch(myexception) {}
}
stub.cc:
#include <dlfcn.h>
#include <stdlib.h>
__attribute__((constructor)) void init() {
dlopen("libreal.so", RTLD_NOW | RTLD_GLOBAL);
}
build.sh:
g++ lib.cc -Wall -Wextra -shared -o libf.so -fPIC -g
g++ main.cc -Wall -Wextra libf.so -fPIE -g
mv libf.so libreal.so
g++ stub.cc -Wall -Wextra -shared -o libf.so -fPIC -ldl -g
使用 GCC 或 clang+libstdc++:
./a.out |& c++filt
./a.out: symbol lookup error: ./a.out: undefined symbol: typeinfo for myexception
,并使用 clang+libc(或使用 -fPIC 而不是 -fPIE 的 GCC):
./a.out |& c++filt
./a.out: symbol lookup error: ./a.out: undefined symbol: vtable for myexception
编辑: 最初的问题是用 GCC 段错误编译的二进制文件。只有在没有 fPIC/fpic/fPIE/fpie 的情况下编译二进制文件时才会出现这种情况。 (Clang 不需要标志,并且关于 clang 行为的问题没有更新)。为了简化问题,我将问题编辑为仅询问运行时链接器问题而不是段错误。
来自 dlopen
的手册页:
RTLD_LAZY
Perform lazy binding. Only resolve symbols as the code that references them is executed. If the symbol is never referenced, then it is never resolved. (Lazy binding is only performed for function references; references to variables are always immediately bound when the library is loaded.)
这很关键。 GNU ld 默认实现惰性绑定。这意味着您的后期绑定方法可能适用于函数,但不适用于数据。 (Vtables 和 RTTI 信息是数据)。如果你 link 带有 -z now
的可执行文件(类似于 dlopen
的 RTLD_NOW
标志),该方法也将停止对函数起作用。
有两种基本方法可以解决这种情况。
- 不要在库外使用
myexception
的运行时数据(vtable 和类型信息)。这意味着您可以直接使用 myexception
执行的操作非常受限。您可以将引用运行时数据的所有操作包装在从库导出的非虚函数中。
- 将
myexception
的运行时数据移动到存根库中。对于大多数 C++ 编译器,这意味着在那里定义第一个(按声明顺序)非内联虚函数。您可以在 class 的顶部声明一个虚拟虚函数并在 stub.cc
中实现它。其余的可能会在 lib.cc. 中实现
在小型复制器中,使用 vtable 的异常 class 的 typeinfo/vtable 的符号查找失败。为什么会出错?是否有可能使 RTTI 为 classes 和加载了 dlopen 的 vtable 正常工作?间接加载的目的是
lib.h:
#include <exception>
class myexception : public std::exception {
virtual void info();
};
void f();
lib.cc:
#include "lib.h"
void myexception::info() {};
void f() { throw myexception(); }
main.cc:
#include "lib.h"
int main() {
try { f(); }
catch(myexception) {}
}
stub.cc:
#include <dlfcn.h>
#include <stdlib.h>
__attribute__((constructor)) void init() {
dlopen("libreal.so", RTLD_NOW | RTLD_GLOBAL);
}
build.sh:
g++ lib.cc -Wall -Wextra -shared -o libf.so -fPIC -g
g++ main.cc -Wall -Wextra libf.so -fPIE -g
mv libf.so libreal.so
g++ stub.cc -Wall -Wextra -shared -o libf.so -fPIC -ldl -g
使用 GCC 或 clang+libstdc++:
./a.out |& c++filt
./a.out: symbol lookup error: ./a.out: undefined symbol: typeinfo for myexception
,并使用 clang+libc(或使用 -fPIC 而不是 -fPIE 的 GCC):
./a.out |& c++filt
./a.out: symbol lookup error: ./a.out: undefined symbol: vtable for myexception
编辑: 最初的问题是用 GCC 段错误编译的二进制文件。只有在没有 fPIC/fpic/fPIE/fpie 的情况下编译二进制文件时才会出现这种情况。 (Clang 不需要标志,并且关于 clang 行为的问题没有更新)。为了简化问题,我将问题编辑为仅询问运行时链接器问题而不是段错误。
来自 dlopen
的手册页:
RTLD_LAZY
Perform lazy binding. Only resolve symbols as the code that references them is executed. If the symbol is never referenced, then it is never resolved. (Lazy binding is only performed for function references; references to variables are always immediately bound when the library is loaded.)
这很关键。 GNU ld 默认实现惰性绑定。这意味着您的后期绑定方法可能适用于函数,但不适用于数据。 (Vtables 和 RTTI 信息是数据)。如果你 link 带有 -z now
的可执行文件(类似于 dlopen
的 RTLD_NOW
标志),该方法也将停止对函数起作用。
有两种基本方法可以解决这种情况。
- 不要在库外使用
myexception
的运行时数据(vtable 和类型信息)。这意味着您可以直接使用myexception
执行的操作非常受限。您可以将引用运行时数据的所有操作包装在从库导出的非虚函数中。 - 将
myexception
的运行时数据移动到存根库中。对于大多数 C++ 编译器,这意味着在那里定义第一个(按声明顺序)非内联虚函数。您可以在 class 的顶部声明一个虚拟虚函数并在stub.cc
中实现它。其余的可能会在 lib.cc. 中实现