如何确保在优化期间不会删除局部变量

时间:2015-09-19 19:05:33

标签: c++ debugging postmortem-debugging

背景

在工作中,我经常最终使用优化代码的核心转储进行postmorten调试。

对于某些难以发生的难以再现的故障,我想向我提供额外的信息。在这些情况下添加额外的跟踪是不可行的,因为绝大多数调用都是成功的,并且每分钟会添加数百万个“不必要的”跟踪,这将快速滚动日志文件。捕获和跟踪并不总是可行的,因为某些错误可能会破坏环境,导致跟踪失败。

由于我们的核心转储包括callstack内存,我认为我可以使用callstack内存中的一个区域来“跟踪”。

问题

感谢优化编译器这样的代码不起作用

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&amp; gcc),但我能指望它继续工作吗?

有没有更好的方法来实现我想要做的事情?

更好的意思是更简单,更强大或符合标准。

1 个答案:

答案 0 :(得分:1)

优化编译可能难以调试:

您可以尝试以下方式:

在你的例子中:

void process (int i)
{
   int save_me = i;
   // Do something else
}

(预初始化的)形式参数和自动变量都在同一个堆栈上,相隔几个字节。如果崩溃发生在&#34;做其他事情&#34;优化器已经完成了它不再使用的堆栈项目。

我运气好的是:

void process (int i)
{
   // Do something else

   if (bool_that_compiler_can_not_predetermine_is_always_false)
   {
       std::cerr << "error:  int i is " << i << std::endl;
   }
}

由于编译器无法确定cerr行永远不会被执行,因此它将生成代码,并将形式参数保留在范围内。

当然,除了cerr之外,您还可以选择其他操作。也许是日志条目?也许更小的东西。重点是,在丢弃i的值(或者,如果仍然需要,save_me)之后,核心转储中的失败不会发生,直到&#34; process&#34;

结束。

Optimzers也可以对代码进行重新排序,但是在进程结束时if子句的位置(我认为)强制在该子句之前完成do-something-else的所有部分。

我有时使用时间戳来创建can-not-true-clause子句。 (因为:: time(0)非常有效)。

如果你有一个main,argc很容易使用,即(0 == argc)或(argc> 100), 多余的args很容易被忽略。