提问人:user2725742 提问时间:11/15/2023 更新时间:11/22/2023 访问量:68
如何在C++中从FPGA的memcpy“总线错误”中恢复?
How to Recover in C++ from memcpy "Bus Error" from FPGA?
问:
我所读到的所有内容都表明它不会引发异常,因此 try-catch 语句不能用于处理此类错误。硬件团队已向我提供了内存地址和范围,并通过 访问它们,但存在一些集成问题(即需要他们修复的问题)。memcpy
mmap
一个 DDR 通道可以完美地工作,而执行相同操作的相同代码通常会在另一个通道上死亡。程序只是停止,终端上印有“总线错误”。
一旦弄清楚了这一点,内存交互应该会更顺畅,但此接口接受来自另一个设备(即另一个团队)的内存操作。我可以尝试验证任何传入的操作,但硬件团队也有可能做一些奇怪的事情来更改有效操作,或者只是导致有效操作的“总线错误”。
那么,如何防止我的 C++ 应用程序因其他团队的未来/意外更改而死亡呢?我需要设置信号处理程序吗?还有其他选择吗?
答:
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); }
};
现在,我们可以定义信号的实际处理程序。这个想法是这样的:
- 我们激活 SIGBUS 和 SIGSEGV 的信号处理程序
- 我们为
longjmp
设置状态 - 如果调用信号处理程序,它将保存信号状态并通过
longjmp
- 返回到设置它的位置。在那里,信号状态被转换为异常
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
- 它们不得涉及使用非平凡的析构函数创建新对象。准确地说:如果调用信号处理程序,则在 和 之间创建的自动变量将被遗忘,而无需先调用其析构函数
sigsetjmp
siglongjmp
- 该函数应该是异步信号安全的。请参见
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';
}
}
评论
memcpy
std::bit_cast