Class 加载的共享库中存在冲突
Class conflicts in loaded shared libraries
假设我的主进程中有一个 class:
class MyClass
{
void doStuff();
int myThing;
int mySecondThing;
bool myThirdThing;
};
然后我加载了一个共享库,mysharedlib.so
,其中 更新 版本的 class 编译于:
class MyClass
{
void doStuff();
int myThing;
int mySecondThing;
bool myThirdThing;
std::string myFourthThing;
u32 myFifthThing;
};
当我创建 MyClass 实例/在库函数和主要可执行文件函数之间传递现有实例时会发生什么?
我知道 the two libraries live in a different address space 但是在库和可执行文件之间传递数据让我很困惑。
使用 gmodule
时,这是否有不同的表现?
使用 MyClass 对象时可能会出现问题。
这取决于你如何使用它们。
采用以下场景(此处为伪造代码)。
MyClass* ptr = SharedLibHandle->CreateMyClass();
ptr->doStuffNonVirtual(); //1 this might work fine
ptr->doStuffVirtual(); //2 this will work fine
ptr->myThing= 5; // 3 this might work fine
MyClass* localAllocPtr = new MyClass();
SharedLibHandle()->DoSomethingWithTheClass(localAllocPtr);
...
void DoSomethingWithTheClass(MyClass* ptr)
{
ptr->myFourthThing = " mama " ; // 4 this might seem to work fine
}
在上面的示例中,根据实例化和使用的位置,有几种可能的用例:
ptr 处理 class 在 so 中以 so 中定义的大小实例化,然后由您的可执行文件以此处定义的大小使用的场景。
localAllocPtr 处理相反的情况(class 在您的可执行文件中实例化然后传递给 .so)。
各拿一张:
- 调用非虚函数。
非虚函数在编译时解析,这意味着如果您的可执行文件中有不同的代码实现,堆栈指针将跳转到您的函数实现而不是 .so 中的函数实现。如果您的代码在可执行文件和 so 中都相同,并且结构对齐方式保持不变(这很可能),它将按预期工作。
- 调用虚函数
这会很好地工作,因为它会跳转到一个 vftable,然后跳转到 .so 中的正确内存地址。 .so 初始化了 class,因此偏移、跳转和一切都是合法的。
- 访问共同定义的成员
只有当 myThing 在结构内具有相同的对齐方式时,这才能正常工作,这意味着他在结构内的 *(ptr+0) 偏移处。如果在您的 class 中 myThing 是第一个而 mySecondThing 是第二个,而在 .so 中 mySecondThing 是第一个而 myThing 是第二个,那么您将更改错误的参数。具有讽刺意味的是,如果您继续在可执行文件中使用 class 并且不将其传回 .so(假设无知是一种幸福)。
- 访问未分配的成员
当您的可执行文件分配 localAllocPtr 时,它将使用您的可执行文件中定义的 sizeof(MyClass) 分配它。在您的可执行文件中,class 没有定义字符串和 u32。将此分配的结构传递给 .so 时,.so 将根据其定义将 class 视为具有成员和大小。当访问 myFourthThing 时,它将访问通常为 *(ptr + 8) 的内存区域。如果该内存区域正在使用中(有人分配在那里),您将在您的 ptr 范围之外写入其他人的内存,并且它可能看起来工作正常但您最终会遇到最难找到的错误之一。如果在 *(ptr +8) 之后什么都没有分配,你会很幸运并得到一个段错误。
为了避免您所描述的那种问题,一种常见的方法是 pImpl idiom ,它允许您将 class 特定实现设为私有,因此您可以添加虚函数和成员变量,同时保持class 相同的暴露定义。
假设我的主进程中有一个 class:
class MyClass
{
void doStuff();
int myThing;
int mySecondThing;
bool myThirdThing;
};
然后我加载了一个共享库,mysharedlib.so
,其中 更新 版本的 class 编译于:
class MyClass
{
void doStuff();
int myThing;
int mySecondThing;
bool myThirdThing;
std::string myFourthThing;
u32 myFifthThing;
};
当我创建 MyClass 实例/在库函数和主要可执行文件函数之间传递现有实例时会发生什么?
我知道 the two libraries live in a different address space 但是在库和可执行文件之间传递数据让我很困惑。
使用 gmodule
时,这是否有不同的表现?
使用 MyClass 对象时可能会出现问题。
这取决于你如何使用它们。
采用以下场景(此处为伪造代码)。
MyClass* ptr = SharedLibHandle->CreateMyClass();
ptr->doStuffNonVirtual(); //1 this might work fine
ptr->doStuffVirtual(); //2 this will work fine
ptr->myThing= 5; // 3 this might work fine
MyClass* localAllocPtr = new MyClass();
SharedLibHandle()->DoSomethingWithTheClass(localAllocPtr);
...
void DoSomethingWithTheClass(MyClass* ptr)
{
ptr->myFourthThing = " mama " ; // 4 this might seem to work fine
}
在上面的示例中,根据实例化和使用的位置,有几种可能的用例:
ptr 处理 class 在 so 中以 so 中定义的大小实例化,然后由您的可执行文件以此处定义的大小使用的场景。
localAllocPtr 处理相反的情况(class 在您的可执行文件中实例化然后传递给 .so)。
各拿一张:
- 调用非虚函数。
非虚函数在编译时解析,这意味着如果您的可执行文件中有不同的代码实现,堆栈指针将跳转到您的函数实现而不是 .so 中的函数实现。如果您的代码在可执行文件和 so 中都相同,并且结构对齐方式保持不变(这很可能),它将按预期工作。
- 调用虚函数
这会很好地工作,因为它会跳转到一个 vftable,然后跳转到 .so 中的正确内存地址。 .so 初始化了 class,因此偏移、跳转和一切都是合法的。
- 访问共同定义的成员
只有当 myThing 在结构内具有相同的对齐方式时,这才能正常工作,这意味着他在结构内的 *(ptr+0) 偏移处。如果在您的 class 中 myThing 是第一个而 mySecondThing 是第二个,而在 .so 中 mySecondThing 是第一个而 myThing 是第二个,那么您将更改错误的参数。具有讽刺意味的是,如果您继续在可执行文件中使用 class 并且不将其传回 .so(假设无知是一种幸福)。
- 访问未分配的成员
当您的可执行文件分配 localAllocPtr 时,它将使用您的可执行文件中定义的 sizeof(MyClass) 分配它。在您的可执行文件中,class 没有定义字符串和 u32。将此分配的结构传递给 .so 时,.so 将根据其定义将 class 视为具有成员和大小。当访问 myFourthThing 时,它将访问通常为 *(ptr + 8) 的内存区域。如果该内存区域正在使用中(有人分配在那里),您将在您的 ptr 范围之外写入其他人的内存,并且它可能看起来工作正常但您最终会遇到最难找到的错误之一。如果在 *(ptr +8) 之后什么都没有分配,你会很幸运并得到一个段错误。
为了避免您所描述的那种问题,一种常见的方法是 pImpl idiom ,它允许您将 class 特定实现设为私有,因此您可以添加虚函数和成员变量,同时保持class 相同的暴露定义。