我正在了解如何使用GCC的ld版本脚本在ELF共享库中版本化符号,我知道可以使用以下指令导出相同符号的不同版本:
__asm__(".symver original_foo,foo@VERS_1.1");
如果函数的语义发生变化,但仍希望库导出旧版本,以便使用该库的旧应用程序仍能与新版本一起工作,则这很有用。
但对于C++库,符号MyClass的vtable
将被导出。如果稍后通过添加更多虚拟函数来更改类,如何导出包含原始vtable符号和新版本vtable的原始类?
编辑:我创建了一个测试用例,似乎通过将一个类的所有符号重命名为另一个类的符号可以实现我的目标。这似乎像我所希望的那样工作,但是这种方法是否保证可行或者只是我走了运?以下是代码:
编辑2:我更改了类的名称(希望)更少地引起混淆,并将定义分成了2个文件。
编辑3:它似乎在clang++上也能正常工作。我将澄清我要问的总体问题:
是否确保此技术在Linux上对C++共享库中的类进行二进制向后兼容性,而不考虑虚拟函数的差异?如果不能,为什么?(提供反例会很好)。
libtest.h:
struct Test {
virtual void f1();
virtual void doNewThing();
virtual void f2();
virtual void doThing();
virtual void f3();
virtual ~Test();
};
libtest_old.h:
// This header would have been libtest.h when test0 was theoretically developed.
struct Test {
virtual void f3();
virtual void f1();
virtual void doThing();
virtual void f2();
virtual ~Test();
};
libtest.cpp:
#include "libtest.h"
#include <cstdio>
struct OldTest {
virtual void f3();
virtual void f1();
virtual void doThing();
virtual void f2();
virtual ~OldTest();
};
__asm__(".symver _ZN7OldTestD1Ev,_ZN4TestD1Ev@LIB_0");
__asm__(".symver _ZN7OldTestD0Ev,_ZN4TestD0Ev@LIB_0");
__asm__(".symver _ZN7OldTest7doThingEv,_ZN4Test7doThingEv@LIB_0");
__asm__(".symver _ZN7OldTestD2Ev,_ZN4TestD2Ev@LIB_0");
__asm__(".symver _ZTI7OldTest,_ZTI4Test@LIB_0");
__asm__(".symver _ZTV7OldTest,_ZTV4Test@LIB_0");
__asm__(".symver _ZN7OldTest2f1Ev,_ZN4Test2f1Ev@LIB_0");
__asm__(".symver _ZN7OldTest2f2Ev,_ZN4Test2f2Ev@LIB_0");
__asm__(".symver _ZN7OldTest2f3Ev,_ZN4Test2f3Ev@LIB_0");
void OldTest::doThing(){
puts("OldTest doThing");
}
void OldTest::f1(){
puts("OldTest f1");
}
void OldTest::f2(){
puts("OldTest f2");
}
void OldTest::f3(){
puts("OldTest f3");
}
OldTest::~OldTest(){
}
void Test::doThing(){
puts("New Test doThing from Lib1");
}
void Test::f1(){
puts("New f1");
}
void Test::f2(){
puts("New f2");
}
void Test::f3(){
puts("New f3");
}
void Test::doNewThing(){
puts("Test doNewThing, this wasn't in LIB0!");
}
Test::~Test(){
}
libtest.map:
LIB_0 {
global:
extern "C++" {
Test::doThing*;
Test::f*;
Test::Test*;
Test::?Test*;
typeinfo?for?Test*;
vtable?for?Test*
};
local:
extern "C++" {
*OldTest*;
OldTest::*;
};
};
LIB_1 {
global:
extern "C++" {
Test::doThing*;
Test::doNewThing*;
Test::f*;
Test::Test*;
Test::?Test*;
typeinfo?for?Test*;
vtable?for?Test*
};
} LIB_0;
Makefile:
all: libtest.so.0 test0 test1
libtest.so.0: libtest.cpp libtest.h libtest.map
g++ -fPIC -Wl,-s -Wl,--version-script=libtest.map libtest.cpp -shared -Wl,-soname,libtest.so.0 -o libtest.so.0
test0: test0.cpp libtest.so.0
g++ test0.cpp -o test0 ./libtest.so.0
test1: test1.cpp libtest.so.0
g++ test1.cpp -o test1 ./libtest.so.0
test0.cpp:
#include "libtest_old.h"
#include <cstdio>
// in a real-world scenario, these symvers would not be present and this file
// would include libtest.h which would be what libtest_old.h is now.
__asm__(".symver _ZN4TestD1Ev,_ZN4TestD1Ev@LIB_0");
__asm__(".symver _ZN4TestD0Ev,_ZN4TestD0Ev@LIB_0");
__asm__(".symver _ZN4Test7doThingEv,_ZN4Test7doThingEv@LIB_0");
__asm__(".symver _ZN4Test2f1Ev,_ZN4Test2f1Ev@LIB_0");
__asm__(".symver _ZN4Test2f2Ev,_ZN4Test2f2Ev@LIB_0");
__asm__(".symver _ZN4Test2f3Ev,_ZN4Test2f3Ev@LIB_0");
__asm__(".symver _ZN4TestD2Ev,_ZN4TestD2Ev@LIB_0");
__asm__(".symver _ZTI4Test,_ZTI4Test@LIB_0");
__asm__(".symver _ZTV4Test,_ZTV4Test@LIB_0");
struct MyClass : public Test {
virtual void test(){
puts("Old Test func");
}
virtual void doThing(){
Test::doThing();
puts("Override of Old Test::doThing");
}
};
int main(void){
MyClass* mc = new MyClass();
mc->f1();
mc->f2();
mc->f3();
mc->doThing();
mc->test();
delete mc;
return 0;
}
test1.cpp:
#include "libtest.h"
#include <cstdio>
struct MyClass : public Test {
virtual void doThing(){
Test::doThing();
puts("Override of New Test::doThing");
}
virtual void test(){
puts("New Test func");
}
};
int main(void){
MyClass* mc = new MyClass();
mc->f1();
mc->f2();
mc->f3();
mc->doThing();
mc->doNewThing();
mc->test();
delete mc;
return 0;
}