VST3 SDK:ClassInfo 对象中 std::string 成员的有问题的构造导致程序崩溃

问题描述 投票:0回答:2

问题:

我正在尝试使用 SDK 中包含的 VST 托管实用程序来加载插件。代码如图:

#include "vst/v3/Vst3CommonIncludes.h"

int main()
{
    std::string vst3_module_path = R"(C:\Program Files\Common Files\VST3\Kontakt.vst3)";
    std::string error;
    std::shared_ptr<Module> vst_module = Module::create(vst3_module_path, error);
    std::vector<ClassInfo> class_infos = vst_module->getFactory().classInfos();;

    assert(error.empty());
    assert(class_infos.size());

    ClassInfo plugin_info = class_infos[0]; //Crash
    //... load the plugin and do more things
    return 0;
}

其中

Vst3CommonIncludes.h
仅包含来自
pluginterfaces/vst
public.sdk/source/vst
的所有 VST SDK 标头 8

就我而言,SDK 包含源文件和 cmake 文件,可将它们构建到静态库中。所以我的代码和SDK代码共享同一个编译器。

VST SDK 来源来自 Steinberg Media


调查完成:

我的调查显示

PluginFactory::classInfos()
返回了损坏的数据,尝试从中分配会导致
std::bad_alloc
,因为
std::string
的大小无效。

VST SDK中PluginFactory::classInfos()的定义:

PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept
{
    auto count = classCount ();
    Optional<FactoryInfo> factoryInfo;
    ClassInfos classes;
    classes.reserve (count);
    auto f3 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> (factory);
    auto f2 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory2> (factory);
    Steinberg::PClassInfo ci;
    Steinberg::PClassInfo2 ci2;
    Steinberg::PClassInfoW ci3;
    for (uint32_t i = 0; i < count; ++i)
    {
        if (f3 && f3->getClassInfoUnicode (i, &ci3) == Steinberg::kResultTrue)
//------------Unexpected behaviour here--------------------
            classes.emplace_back (ci3);                //--
//---------------------------------------------------------
        else if (f2 && f2->getClassInfo2 (i, &ci2) == Steinberg::kResultTrue)
            classes.emplace_back (ci2);
        else if (factory->getClassInfo (i, &ci) == Steinberg::kResultTrue)
            classes.emplace_back (ci);
        auto& classInfo = classes.back ();
        if (classInfo.vendor ().empty ())
        {
            if (!factoryInfo)
                factoryInfo = Optional<FactoryInfo> (info ());
            classInfo.get ().vendor = factoryInfo->vendor ();
        }
    }
    return classes;
}

就地构造新的 ClassInfo 元素后,

ClassInfo::data::category
和其他
std::string
成员(名称、供应商等)在调试器中读取
<NULL>

进入

std::string
的构造函数,我发现在构造
this
时,
data.category
指针不等于
&data.category
,并且偏移了4个字节。

&data.category = 0x 0000 009b 546f ed14
this (std::string constructor scope) = 0x 0000 009b 546f ed18
//Actual address varies but the offset remains the same

因此字符串对象被损坏,随后导致程序崩溃。

附加信息:


std::string相关实验:

另外,在尝试

ClassInfo
及其字符串成员时,我遇到了这个:

ClassInfo ci;
ci.get().category = "testCategory";                  //OK
const_cast<string&>(ci.category()) = "testCategory"; //Crash, Access violation at 0xFFFFFFFFFFFFFFFF

我认为这与问题高度相关,但我无法给出解释。

C++ 标准一致性:

我也补充了

#if __cplusplus != 201703L
#error
#endif

对于每个相关文件,所以我确信它们共享相同的STL实现,问题仍然会发生。

重现尝试:

我希望重新创建一个最小的场景,其中包含VST SDK,并使用我自己的

MimicClassInfo
,在某些方面类似于原始
ClassInfo
的结构。问题不会出现。


编译器和SDK信息:

MSVC 14.37.32822,使用C++17标准。 VST SDK 3.7.8 版本 34 (2023-05-15)

c++ visual-c++ vst
2个回答
1
投票

这种行为的另一个原因是用于构建 C++ 文件的不同编译器标志(尤其是宏)。

例如,如果某个

class X
有条件地(基于某些宏)定义了一个附加数据成员,则会影响数据成员布局。如果您编译两个 C++ 文件,一个包含该宏,另一个不包含该宏,那么如果这两个 C++ 翻译单元在
class X
的同一个对象上工作,您的应用程序可能会崩溃。

因此,您的 C++ 代码在语法上可能是正确的,但由于编译器标志不同,链接器可能会构建有错误的应用程序。


1
投票

我不太确定以下是否是您遇到问题的原因。我相信也值得检查一下。

几年前我发现的问题是 VS 中的成员函数指针的大小根据上下文而变化:

  • 如果计算转发类的成员函数指针大小,您将得到 24 个字节(在 64 位平台上)
  • 如果你计算声明类的成员函数指针大小,你会得到 8 个字节

因为在x86-64汇编编译器中使用无效的偏移量来读取/写入成员函数指针后面的数据成员的数据。

struct X;

struct Y {
    void (X:: * field1_)();
    std::string field2_;
};

在上面的示例中,访问

field2_
有时编译器使用偏移量 8,有时使用 32,这会导致段错误。该代码在语法上是有效的,找出问题原因的唯一方法是读取 x86 程序集。

#include <string>

struct X;

size_t foo()
{
    using XFooPtr = void (X:: *)();
    return sizeof(XFooPtr);
}

组装

unsigned __int64 foo(void) PROC                                 ; foo
        mov     eax, 24
        ret     0
unsigned __int64 foo(void) ENDP                                 ; foo
#include <string>

struct X {};

size_t foo()
{
    using XFooPtr = void (X:: *)();
    return sizeof(XFooPtr);
}

组装

unsigned __int64 foo(void) PROC                                 ; foo
        mov     eax, 8
        ret     0
unsigned __int64 foo(void) ENDP                                 ; foo

以下是重现该问题的代码的最小示例。在 vs2022 上仍然有效。

/// @file 2.cpp
class X ;

size_t foo() noexcept { return sizeof(void (X::*)()); }
/// @file 1.cpp

// cl 1.cpp 2.cpp

class X {};

size_t foo() noexcept;
size_t bar() noexcept { return sizeof(void (X::*)()); }

#include <iostream>

int main() {
    std::cout << foo() << "\n";
    std::cout << bar() << "\n";
}

如果你编译并运行这个应用程序,你会得到 24 和 8。

它如何影响您的应用程序。假设您有

struct X
struct Y
,它们封装了指向
struct X
的成员函数的指针。

/// @file X.h

#pragma once

struct X{};
/// @file Y.h

#pragma once

#include <string>

struct X;

struct Y {
    void (X:: * field1_)();
    std::string field2_;
};

并且您有两个翻译单元 main.cpp 和 2.cpp

/// @file 2.cpp

#include "X.h"
#include "Y.h"

#include <iostream>

void foo(Y&y) noexcept {
    std::cout << y.field2_ << "\n";
}
/// @file main.cpp

#include "Y.h"

void foo(Y& y);

int main()
{
    auto y = Y{};
    y.field2_ = "Hello world";
    foo(y);

}

现在如果你编译并运行这个应用程序,它就会崩溃。

© www.soinside.com 2019 - 2024. All rights reserved.