如何在C++中从FPGA的memcpy“总线错误”中恢复?

How to Recover in C++ from memcpy "Bus Error" from FPGA?

提问人:user2725742 提问时间:11/15/2023 更新时间:11/22/2023 访问量:68

问:

我所读到的所有内容都表明它不会引发异常,因此 try-catch 语句不能用于处理此类错误。硬件团队已向我提供了内存地址和范围,并通过 访问它们,但存在一些集成问题(即需要他们修复的问题)。memcpymmap

一个 DDR 通道可以完美地工作,而执行相同操作的相同代码通常会在另一个通道上死亡。程序只是停止,终端上印有“总线错误”。

一旦弄清楚了这一点,内存交互应该会更顺畅,但此接口接受来自另一个设备(即另一个团队)的内存操作。我可以尝试验证任何传入的操作,但硬件团队也有可能做一些奇怪的事情来更改有效操作,或者只是导致有效操作的“总线错误”。

那么,如何防止我的 C++ 应用程序因其他团队的未来/意外更改而死亡呢?我需要设置信号处理程序吗?还有其他选择吗?

C++ 错误处理 mmap memcpy

评论

2赞 Quimby 11/15/2023
您是否可能通过未对齐的指针访问内存?某些架构(如 armv8)不允许对设备内存进行未对齐的访问。总线错误不是你发现的,而是你修复的原因。
0赞 Pepijn Kramer 11/15/2023
你确定你的硬件没问题吗?如果是,你的指针是否具有有效值?您是否使用易失性指针?如果没有更多信息,我(可能还有其他人)很难说出任何有用的话
1赞 Homer512 11/15/2023
总线错误是一个信号,因此信号处理程序是唯一能够捕获它的东西。但是,您打算如何恢复?我想您可以尝试重新映射内存页面,希望它能解决问题,但您唯一的解决方案是恢复,以便 memcpy 可以继续使用完全相同的指令或终止有问题的线程;更好的整个过程。就我个人而言,我会为每个可能失败的操作分叉一个新的子进程,以便子进程可以死亡
1赞 Red.Wave 11/16/2023
如果是 C++,请尝试避免 .该函数在用户代码中唯一剩下的用例是类型双关语;在 C++20 中,该用法通过 安全地处理。明智的选择是禁止使用它并在代码审查中拒绝它。memcpystd::bit_cast
0赞 user4581301 11/16/2023
总线错误是写入不可写内存时看到的常见错误消息。

答:

1赞 fm_user8 11/16/2023 #1

与这些组合的信号处理程序似乎是有序的......

static sigjmp_buf jmpbuf;
siglongjmp(jmpbuf,signum); // call from within signal handler

if(sigsetjmp(jmpbuf,1)) {
    std::cout << "Something bad happened\n";
} else {
    // Normal operation, basically saving a checkpoint before a dangerous operation
}
0赞 Homer512 11/22/2023 #2

扩展@user2725742使用跳远的建议,这里有一个将信号转换为异常的版本。我用它来捕获总线和分段故障错误,因为坦率地说,我不知道生成总线错误的好方法。

我们首先定义一个 RAII 类来设置和恢复信号处理程序,因为我们将混合异常和全局状态更改。这是一个相当小的、不可移动的版本,但足以满足我们的需求。

#include <signal.h>

#include <cerrno>
#include <system_error>

class SignalRegistration
{
    int sig;
    struct sigaction oldaction;
public:
    using action_fun = void (*)(int, siginfo_t*, void*);

    SignalRegistration(int sig, action_fun fun)
    : sig(sig)
    {
        struct sigaction action {};
        action.sa_sigaction = fun;
        action.sa_flags = SA_SIGINFO;
        if(sigaction(sig, &action, &oldaction))
            throw std::system_error(
                  errno, std::generic_category(), "sigaction");
    }
    SignalRegistration(const SignalRegistration&) = delete;
    SignalRegistration& operator=(const SignalRegistration&) = delete;
    ~SignalRegistration()
    { sigaction(sig, &oldaction, nullptr); }
};

现在,我们可以定义信号的实际处理程序。这个想法是这样的:

  1. 我们激活 SIGBUS 和 SIGSEGV 的信号处理程序
  2. 我们为 longjmp 设置状态
  3. 如果调用信号处理程序,它将保存信号状态并通过longjmp
  4. 返回到设置它的位置。在那里,信号状态被转换为异常longjmp

为了在多线程环境中实现此工作,longjmp 缓冲区和保存的信号状态需要是一个线程局部变量。

#include <sstream>
// using std::ostringstream

class FaultHandler
{
public:
    struct SigState
    {
        sigjmp_buf env;
        siginfo_t info;
    };
    static inline thread_local SigState instance = {};
private:
    static void sighandler(int /*sig*/, siginfo_t* siginfo, void* /*ucontext*/)
    {
        instance.info = *siginfo;
        siglongjmp(instance.env, 1);
    }
    SignalRegistration bus, segv;

public:
    FaultHandler()
    : bus(SIGBUS, &FaultHandler::sighandler),
      segv(SIGSEGV, &FaultHandler::sighandler)
    {}
    [[noreturn]] static void raise()
    {
        std::ostringstream ss;
        ss << "Received signal " << instance.info.si_signo
           << " code " << instance.info.si_code
           << " address " << instance.info.si_addr;
        int errcode = instance.info.si_signo == SIGBUS ? EIO : EFAULT;
        throw std::system_error(errcode, std::generic_category(), ss.str());
    }
};

现在我们可以用它来保护或其他一些功能。具体要求:memcpy

  1. 它们不得涉及使用非平凡的析构函数创建新对象。准确地说:如果调用信号处理程序,则在 和 之间创建的自动变量将被遗忘,而无需先调用其析构函数sigsetjmpsiglongjmp
  2. 该函数应该是异步信号安全的。请参见 man 3 siglongjmp 中的未定义行为一节(奇怪的是,其他具有相同标题的手册页中没有)

无论如何,使用这个是安全的:memcpy

#include <cstring>
// using std::memcpy

void* memcpy_fault(void* out, const void* in, std::size_t nbytes)
{
    FaultHandler check;
    if(sigsetjmp(FaultHandler::instance.env, 1))
        FaultHandler::raise();
    return std::memcpy(out, in, nbytes);
}

这里有一个快速测试,看看它的工作原理:

#include <iostream>

int main()
{
    char buf[10];
    try {
        memcpy_fault(buf, nullptr, 4);
    } catch(std::system_error& err) {
        std::cout << "Caught exception: " << err.what() << '\n';
    }
}