【发布时间】:2015-09-19 19:05:32
【问题描述】:
背景
在工作中,我经常使用优化代码的核心转储进行事后调试。
对于某些难以重现的、不可重现的故障,我希望获得额外的信息。在这些情况下添加额外的跟踪是不可行的,因为绝大多数调用都是成功的,并且每分钟会添加数百万个“不必要的”跟踪,这将快速滚动日志文件。捕获和跟踪并不总是可行的,一些错误可能会破坏环境,导致跟踪失败。
由于我们的核心转储包括调用堆栈内存,我认为我可以使用调用堆栈内存上的一个区域进行“跟踪”。
问题
感谢优化编译器这样的代码不起作用
void process (int i)
{
int save_me = i;
// Do something else
}
这个想法是通过分配给局部变量来将输入变量存储在堆栈上。这通常在调试模式下运行良好,但在优化构建中,编译器认为该语句没有副作用并将其删除。
alloca 似乎可以工作,除非我们针对一些不支持 alloca 的平台,而且我不确定它与 C++ 的配合如何。
我做了一些实验,即使在优化的构建中,以下代码似乎也能够使状态“粘”在堆栈上:
#include <cstdint>
#include <stdexcept>
#include <istream>
#include <sstream>
struct saved_state
{
saved_state ()
: head (0xAABBCCDD)
, tail (0xEEFF0000)
{
std::fill (state, state + 16, 0);
}
void push (std::int32_t input) volatile
{
for (auto i = 15U; i > 0U; --i)
{
state[i] = state[i - 1];
}
state[0] = input;
}
volatile std::uint32_t head ;
volatile std::int32_t state [16];
volatile std::uint32_t tail ;
};
void invoke (std::int32_t i)
{
if (i > 10)
{
throw std::runtime_error ("Busted");
}
}
void process (std::istream & input)
{
saved_state volatile ss;
while (!input.eof ())
{
std::int32_t i;
if (input >> i)
{
ss.push (i);
invoke (i);
}
}
}
int main()
{
std::istringstream input ("1\n2\n30\n");
process (input);
return 0;
}
问题
我可以期望代码执行我希望它执行的操作吗?它似乎适用于我们当前的一组编译器(clang 和 gcc),但我可以期待它继续工作吗?
有没有更好的方法来实现我想做的事情?
我所说的更好是指更简单、更健壮或符合标准。
【问题讨论】:
-
volatile表示对象的加载/存储不能被优化掉,使用它通常是保持变量活动的一种公认方式。 -
你的程序是如何被杀死的?在它们被杀死之前运行一个可以输出所需信息的处理程序是否可行?对我来说似乎更简单,更健壮。例如,如果它们因调用
std::terminate而死,您可以安装std::terminate_handler。 -
故障通常表现为某种低级异常,触发平台提供的故障处理程序,该处理程序收集了大量信息。我不确定是否有办法连接到该故障处理程序并以可靠的方式注入信息。不过我会调查的。谢谢。
-
您可以指示编译器不要优化代码(至少在 Visual Studio 中)。但是,您将不会从优化中受益。恕我直言,您应该接受它,而是学习如何调试优化的代码。您不希望每次出现错误时都修改代码并将部分
volatile构建发送给客户以重现错误并随后进行分析。有时它甚至会变得无法重现,更不用说很难重建客户使用的确切版本。
标签: c++ debugging postmortem-debugging