gtest和cstdarg之间的交互是否不好,或者我真的错过了这里的堆栈粉碎

Is there a bad interaction between gtest and cstdarg, or am I really missing the stack smash here?

本文关键字:错过了 真的 这里 或者 堆栈 之间 cstdarg 交互 是否 gtest      更新时间:2023-10-16

下面的代码旨在成为一个简单的错误日志记录系统,其行为方式类似于printf。

我的所有代码都在gtest环境中运行良好,但现在当我退出程序中的一个确定点(我的一个测试)时,它的堆栈会崩溃。在我写这篇文章之前,我的代码一直运行得很好,这是我第一次尝试cstdarg,所以它是最可疑的。

MyMutex error_lock;
#define MAX_ERR_MSG_SIZE 128
#define MAX_ERRORS 3
class ErrorQueue
{
   std::queue<char*> errors;
   std::list<char*> old_errors;
public:
   void push(char * msg)
   {
      if (errors.size() >= MAX_ERRORS)
      {
         pop();
      }
      errors.push(msg);
   }
   void pop()
   {
      if (old_errors.size() >= MAX_ERRORS)
      {
         delete [] old_errors.front();
         old_errors.pop_front();
      }
      old_errors.push_back(errors.front());
      errors.pop();
   }
   char * front()
   {
      return errors.front();
   }
   size_t size()
   {
      return errors.size();
   }
   ~ErrorQueue()
   {
      while(!errors.empty())
      {
         delete [] errors.front();
         errors.pop();
      }
      while (!old_errors.empty())
      {
         delete [] old_errors.front();
         old_errors.pop_front();
      }
   }
};
static ErrorQueue errors;

void WriteCallError(const char * error_message, ...)
{
   char err_buffer[MAX_ERR_MSG_SIZE];
   va_list args;
   va_start(args,error_message);
   std::vsnprintf(err_buffer,MAX_ERR_MSG_SIZE,error_message,args);
   va_end(args);
   char * err_string = new char[MAX_ERR_MSG_SIZE];
   memcpy(err_string,err_buffer,MAX_ERR_MSG_SIZE);
   {
      error_lock.Lock();
      errors.push(err_string);
      error_lock.Unlock();
   }
}

我在代码的其他地方多次调用WriteCallError,经过一定次数后,它会吐出,并告诉我堆栈已被打碎。

我的错在哪里?cstdarg和gtest之间有一些时髦的互动吗?这里有足够的信息吗?

编辑:使用一个简单的main,我尝试隔离它:

int main (int argc, char ** argv)
{
   int i = 0;
   while (1)
   {
      WriteCallError("Breaks on %d",i++);
   }
}

这不会造成堆栈粉碎。

我认为您的问题是,如果您正在写入的数据超出指定的限制,vsnprintf不会向数组写入终止nul字符。当您稍后访问这个C字符串时,由于没有终止符,它将读取超出有效数据末尾的数据并进入未知内存。

你在测试中没有发现这一点,因为你没有超过128个字符的限制。

快速修复方法是确保在使用vsnprintf:后正确终止阵列

void WriteCallError(const char * error_message, ...)
{
   char err_buffer[MAX_ERR_MSG_SIZE];
   va_list args;
   va_start(args,error_message);
   std::vsnprintf(err_buffer,MAX_ERR_MSG_SIZE,error_message,args);
   va_end(args);
   // Fix here
   err_buffer[MAX_ERR_MSG_SIZE - 1] = 0;
   char * err_string = new char[MAX_ERR_MSG_SIZE];
   memcpy(err_string,err_buffer,MAX_ERR_MSG_SIZE);
   {
      error_lock.Lock();
      errors.push(err_string);
      error_lock.Unlock();
   }
}

回答这样我就可以结束问题了,因为我解决了错误:

它发生在类似的代码中:

TEST(MyTest,Case1)
{
   MyStruct1 object1;
   memset(&object1,0,sizeof(object1));
   MyStruct2 object2[1];
   memset(&object2[0],0,sizeof(object1));
   object1.object2_ptr = object2;

   // DO SOME TESTING
}

由于对象1&对象2是堆栈变量,对象1比对象2大。当我进行memset时,我把内存设置得太远了。当我离开该函数的堆栈部分时,堆栈指针大喊发生了疯狂的事情,并告诉我我正在砸堆栈。

简单修复:

TEST(MyTest,Case1)
{
   MyStruct1 object1;
   memset(&object1,0,sizeof(MyStruct1));
   MyStruct2 object2[1];
   memset(&object2[0],0,sizeof(MyStruct2));
   object1.object2_ptr = object2;

   // DO SOME TESTING
}