C++ Python 模块在 Blender 中崩溃但在 Python 控制台中没有

C++ Python module crashes in Blender but not in Python console

问题

我正在尝试使用 Blender 2.82a 从 Python 3.7 调用我的 C++ 代码(也发生在 2.83 中)。该代码应优化相机路径。它可以在没有 Blender 的情况下使用,但是,我使用 Blender 设置一个场景,其中包含相机路径并查询场景中的深度值。

我尝试在 C++ 和 Python 控制台中调用优化函数。两者都没有任何问题。问题是,当我在 Blender 中调用它时,Blender 崩溃了。

这是崩溃报告:

# Blender 2.83.0, Commit date: 2020-06-03 14:38, Hash 211b6c29f771

# backtrace
./blender(BLI_system_backtrace+0x1d) [0x6989e9d]
./blender() [0xc1548f]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x153c0) [0x7fa5fb3dc3c0]
/lib/x86_64-linux-gnu/libpthread.so.0(raise+0xcb) [0x7fa5fb3dc24b]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x153c0) [0x7fa5fb3dc3c0]
./blender(_ZN5Eigen8IOFormatD1Ev+0xa3) [0x179bc43]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(_Z2_zRK6CameraRKN5Eigen6MatrixIdLi9ELi1ELi0ELi9ELi1EEEiiRKSt8functionIFdRK3RayEE+0x2e2) [0x7fa5d1538e72]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(_ZN11OpticalFlow13GradPathErrorERKSt6vectorI6CameraSaIS1_EEiiRKSt8functionIFdRK3RayEEd+0x5a7) [0x7fa5d1539c77]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(_Z16_gradientDescentRKSt6vectorI6CameraSaIS0_EEiiRKSt8functionIFdRK3RayEEd+0x54b) [0x7fa5d153b5fb]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(_ZN11OpticalFlow12OptimizePathERKSt6vectorI6CameraSaIS1_EEiiRKSt8functionIFdRK3RayEEdNS_18OptimizationMethodE+0x22) [0x7fa5d153bcb2]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(+0x3d910) [0x7fa5d1533910]
/home/name/Programs/blender-2.83.0-linux64/2.83/python/lib/python3.7/optFlowCam.cpython-37m-x86_64-linux-gnu.so(+0x317ed) [0x7fa5d15277ed]
./blender(_PyMethodDef_RawFastCallKeywords+0x2f3) [0x570f373]
./blender(_PyCFunction_FastCallKeywords+0x25) [0x570f3f5]
./blender(_PyEval_EvalFrameDefault+0x7468) [0xc0fb48]
./blender(_PyEval_EvalCodeWithName+0xadc) [0x57c0d8c]
./blender(PyEval_EvalCodeEx+0x3e) [0x57c0ebe]
./blender(PyEval_EvalCode+0x1b) [0x57c0eeb]
./blender() [0x11f35ac]
./blender() [0x1600cde]
./blender() [0xec6a93]
./blender() [0xec6d07]
./blender(WM_operator_name_call_ptr+0x1a) [0xec720a]
./blender() [0x14f2082]
./blender() [0x15020d5]
./blender() [0xeca877]
./blender() [0xecaecc]
./blender(wm_event_do_handlers+0x310) [0xecb5e0]
./blender(WM_main+0x20) [0xec2230]
./blender(main+0x321) [0xb4bfd1]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7fa5facb50b3]
./blender() [0xc11c0c]

我正在使用 Eigen 进行线性代数计算,并使用 pybind11 将其编译成 python 模块。 Eigen 类型都是固定大小的,因为我不需要它们是动态的 (问题的可能原因)。我在 Ubuntu 20.04 上用 gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0 编译。我目前使用 c++11 标准,但这不是必需的。

当前调查结果

使用 faulthandler.enabled() 它给了我

Fatal Python error: Segmentation fault

Current thread 0x00007fa5fab18040 (most recent call first):
  File "/Text", line 16 in <module>

我已经发现它 在程序的同一行 上崩溃,这是矩阵向量乘法的结果应该是 returned并插入到 std::vector。我之前打印了向量和矩阵,以确保它们不包含垃圾并且工作正常。

我也试过将它存储在一个中间变量中并打印它,然后它在打印时崩溃了。 乘法本身似乎不会导致段错误

我想,我尝试直接从 Blender 调用函数,它出现的地方,但随后它起作用了,return结果没有段错误。

我怀疑这是某种 内存对齐问题 并尝试了 here 和 Eigen 纪录片中建议的所有方法。即,我在每个 std::vector 中使用 Eigen::aligned_allocator,仅将 Eigen 对象作为 const & 传递,并且在相机和光线 class 中具有 EIGEN_MAKE_ALIGNED_OPERATOR_NEW,它们具有 Eigen 类型成员.

使用 #define EIGEN_DONT_VECTORIZE#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 只给了我部分成功。它不再像以前那样在同一行崩溃。奇怪的是,如果我还在 return 之前添加一个 cout,函数将完成并 returns。

发生崩溃的部分:

该项目不是public并且C++代码相当冗长,所以我只包含其中的一部分。如果您需要更多,请告诉我。其余部分看起来非常相似,所以如果概念上有问题,这里也可能有问题。这不是一个最小的示例(并且它包含一些调试打印),因为我不知道为什么会发生并且错误并不总是在同一部分。

// in header

// this helped somehow
#define EIGEN_DONT_VECTORIZE
#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT
// ***********************************

#include <iostream>
#include <numeric>
#include <array>
#include <vector>
#include <Eigen/Dense>
#include <Eigen/StdVector>
#include "matrix_types.h"
#include "camera.h"

// *******************************************************************************
// Vector9d is a typedef

// in cpp
Vector9d _z(const Camera& cam, const Vector9d& derivX, int x_dir, int y_dir, const std::function<double(const Ray&)>& depthTest){

    Eigen::IOFormat HeavyFmt(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]");

    Matrix9d M0 = OpticalFlow::M(cam, x_dir, y_dir, depthTest);
    std::cout << "M_\n" << M0.format(HeavyFmt) << "\n" <<std::endl;

    Vector9d z = M0 * derivX;

    return z;
}

std::vector<Vector9d, Eigen::aligned_allocator<Vector9d>> OpticalFlow::GradPathError(const std::vector<Camera>& pathPositions, int x_dir, int y_dir, const std::function<double(const Ray&)>& depthTest, double h){

    int n = pathPositions.size()-1;
    Eigen::IOFormat HeavyFmt(Eigen::FullPrecision);
    Eigen::IOFormat HeavyMtxFmt(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]");

    std::vector<Vector9d, Eigen::aligned_allocator<Vector9d>> gradPE;
    gradPE.reserve(n+1);

    // save values that will be used more often in calculations
    std::vector<Vector9d, Eigen::aligned_allocator<Vector9d>> derivXs;
    std::vector<Vector9d, Eigen::aligned_allocator<Vector9d>> zs;
    std::vector<std::array<Matrix9d, 9>> gradMs;
    derivXs.reserve(n+1);
    zs.reserve(n+1);
    gradMs.reserve(n+1);

    for(int i = 0; i<n+1; ++i){

        derivXs.push_back(_derivCamPath(pathPositions, i));

        Camera cam = pathPositions[i];
        Vector9d derivX = _derivCamPath(pathPositions, i);

        zs.push_back(_z(cam, derivX, x_dir, y_dir, depthTest)); // <--- crashed here, if vectorization not turned off
        gradMs.push_back(GradM(cam, x_dir, y_dir, depthTest, h));
    }

    for(int i = 0; i<n+1; ++i){
        Vector9d derivZ = _derivZ(zs, i);
        std::cout << "Zt_" << i << "\n" << derivZ.format(HeavyFmt) << "\n" << std::endl;
        gradPE.push_back(1.0/(n+1) * _w(derivZ, derivXs[i], gradMs[i]));
    }

    // if this is included and vectorization turned off, it doesn't crash
    // std::cout << "end" << std::endl;

    return gradPE; // <-- crash here if vectorization is off
}

我希望有人能帮我找到原因,或者我还能尝试进一步追踪。我对 C++ 不是很有经验,所以代码可能有明显的问题。

我想我找到了原因。

这一行 ./blender(_ZN5Eigen8IOFormatD1Ev+0xd3) [0x2041673] 实际上以一种不太可读的格式命名了罪魁祸首。我使用 gdb 调试来自 Blender 的 python 调用,在回溯中,同一行是 #0 0x0000000002041673 in Eigen::IOFormat::~IOFormat() ()

问题是我在我的程序中使用了 Eigen::IOFormat 来调试打印矩阵和向量,以便更容易地复制到另一个程序中,我只是想检查这些值是否正确。您可以在我在问题中发布的摘录代码中看到它。我只在这两个函数中使用它,段错误只发生在这两个函数中。可能还有其他问题,但就目前而言,它似乎有效。