【问题标题】:httperf buffer overflow detected when rate is high速率高时检测到 httperf 缓冲区溢出
【发布时间】:2012-09-17 00:42:58
【问题描述】:

我在具有 2CPU 和 4GB RAM 的 Ubuntu 12.04.1 LTS 64 位上运行 httperf 0.9.0 (downloaded from Google Code)。我正在尝试对 Web 服务器进行基准测试,但遇到了以下缓冲区溢出问题。

终端命令:

httperf --timeout=5 --client=0/1 --server=localhost --port=9090 --uri=/?value=benchmarks --rate=1200 --send-buffer=4096 --recv-buffer=16384 --num-conns=5000 --num-calls=10

运行几秒后就崩溃了:

*** buffer overflow detected ***: httperf terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f1f5efa1007]
/lib/x86_64-linux-gnu/libc.so.6(+0x107f00)[0x7f1f5ef9ff00]
/lib/x86_64-linux-gnu/libc.so.6(+0x108fbe)[0x7f1f5efa0fbe]
httperf[0x404054]
httperf[0x404e9f]
httperf[0x406953]
httperf[0x406bd1]
httperf[0x40639f]
httperf[0x4054d5]
httperf[0x40285e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7f1f5eeb976d]
httperf[0x4038f1]
======= Memory map: ========
...
...
7f1f5fd74000-7f1f5fd79000 rw-p 00000000 00:00 0 
7f1f5fd91000-7f1f5fd95000 rw-p 00000000 00:00 0 
7f1f5fd95000-7f1f5fd96000 r--p 00022000 08:03 4849686                    /lib/x86_64-linux-gnu/ld-2.15.so
7f1f5fd96000-7f1f5fd98000 rw-p 00023000 08:03 4849686                    /lib/x86_64-linux-gnu/ld-2.15.so
7fff10452000-7fff10473000 rw-p 00000000 00:00 0                          [stack]
7fff1054f000-7fff10550000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted

我用 gdb 检查了the core dump file,如下:

(gdb) list
198   event_signal (EV_PERF_SAMPLE, 0, callarg);
199 
200   /* prepare for next sample interval: */
201   perf_sample_start = timer_now ();
202   timer_schedule (perf_sample, regarg, RATE_INTERVAL);
203 }
204 
205 int
206 main (int argc, char **argv)
207 {
(gdb) bt
#0  0x00007f33d4643445 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f33d4646bab in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f33d4680e2e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007f33d4716007 in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007f33d4714f00 in __chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x00007f33d4715fbe in __fdelt_warn () from /lib/x86_64-linux-gnu/libc.so.6
#6  0x0000000000404054 in set_active (s=<optimized out>, fdset=0x612bc0) at core.c:367
#7  0x0000000000404e9f in core_connect (s=0x17e7100) at core.c:980
#8  0x0000000000406953 in make_conn (arg=...) at conn_rate.c:64
#9  0x0000000000406bd1 in tick (t=<optimized out>, arg=...) at rate.c:94
#10 0x000000000040639f in timer_tick () at timer.c:104
#11 0x00000000004054d5 in core_loop () at core.c:1255
#12 0x000000000040285e in main (argc=11, argv=<optimized out>) at httperf.c:971

我跟踪了一下源代码,发现FD_SET似乎是原因。

最后,对于较低的费率(例如 --rate=100--rate=500),httperf 可以正常工作。我正在对不同的 Web 服务器进行基准测试,导致崩溃的速度不同。我的费率从 100 到 1200 不等。

关于更多细节,实际上我正在尝试重复the experiments done by Roberto Ostinelli,并且我已经调整了 TCP 设置并应用了他的博客文章中提到的补丁。

知道是什么导致了这个问题吗?谢谢!

【问题讨论】:

    标签: linux crash buffer-overflow httperf


    【解决方案1】:

    您正在尝试使用大于 1024 的 fd。 低负载时,您不需要/使用那么多 fd。 高负载时,您需要更多 fd,最终达到 1024 这会导致问题。

    即使我增加 __FD_SETSIZE 我也会遇到这个问题, 所以我认为无论代码是什么,实际上都有一个错误 做边界检查(gcc/llvm?)

    【讨论】:

    • 正如我的问题所说,我已经应用了博文中提到的补丁,即增加头文件中的fd相关限制。
    • 那么您知道如何解决这个问题吗?我在 Ubuntu 10.04 32 位上试过这个,它工作正常。此外,您的内容可能适合评论而不是答案? :-)
    【解决方案2】:

    较新版本的 glibc 在内部对 FD_SET(从 httperf 调用)进行自己的检查,这些检查失败,导致中止。尽管 httperf 是使用不同的 __FD_SET_SIZE 构建的,但 glibc 仍然使用它编译时使用的原始版本。

    为了解决这个问题,我从 __FD_ELT 执行检查之前挖掘了旧版本的 sys/select.h 和 bits/select.h,并将它们放入 httperf 的 src/ 目录(在 sys/ 和 bits 中)。这样,httperf 使用不执行导致中止的检查的旧 FD_SET 宏。我使用了 glibc-2.14.1,但任何没有 bits/select2.h 的版本都可以解决问题。

    我的一个朋友正在https://github.com/klueska/httperfhttps://github.com/klueska/httperf 收集 httperf 的这个和其他修复供我们自己使用(和你的!)

    【讨论】:

      【解决方案3】:

      我遇到了类似的崩溃,在我的情况下,用 epoll() 替换 select() 解决了它。

      我只能在发布编译中重现该问题(我使用 eclipse 作为 devenv,我让 eclipse 为调试和发布设置编译器选项)。

      这是崩溃:

      1354813976/SBNotificationServer terminated
      06/12 21:13:54 > ======= Backtrace: =========
      06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f4e10f90807]
      06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(+0x109700)[0x7f4e10f8f700]
      06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(+0x10a7be)[0x7f4e10f907be]
      06/12 21:13:54 > 1354813976/SBNotificationServer[0x49db90]
      06/12 21:13:54 > 1354813976/SBNotificationServer[0x49de05]
      06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a4b07]
      06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a5318]
      06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a2628]
      06/12 21:13:54 > /lib/x86_64-linux-gnu/libpthread.so.0(+0x7e9a)[0x7f4e10c70e9a]
      06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(clone+0x6d)[0x7f4e10f79cbd]
      06/12 21:13:54 > ======= Memory map: ========
      06/12 21:13:54 > 00400000-00507000 r-xp 00000000 ca:01 141328         /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
      06/12 21:13:54 > 00706000-00707000 r--p 00106000 ca:01 141328 /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
      06/12 21:13:54 > 00707000-00708000 rw-p 00107000 ca:01 141328     /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
      06/12 21:13:54 > 00708000-0070d000 rw-p 00000000 00:00 0
      06/12 21:13:54 > 0120d000-01314000 rw-p 00000000 00:00 0 [heap]
      06/12 21:13:54 > 7f49f8000000-7f49f8021000 rw-p 00000000 00:00 0
      .......
      .......
      

      在此之后,我能够生成核心转储并得到:

      warning: Can't read pathname for load map: Input/output error.
      [Thread debugging using libthread_db enabled]
      Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
      Core was generated by `./SBNotificationServer ../config.xml'.
      Program terminated with signal 6, Aborted.
      0 0x00007feb90d67425 in raise () from /lib/x86_64-linux-gnu/libc.so.6
      (gdb) 
      (gdb) bt
      0 0x00007feb90d67425 in raise () from /lib/x86_64-linux-gnu/libc.so.6
      1 0x00007feb90d6ab10 in abort () from /lib/x86_64-linux-gnu/libc.so.6
      2 0x00007feb90da539e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
      3 0x00007feb90e3b807 in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
      4 0x00007feb90e3a700 in __chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
      5 0x00007feb90e3b7be in __fdelt_warn () from /lib/x86_64-linux-gnu/libc.so.6
      6 0x000000000049e290 in CPhpResponseReader::WaitUntilReadable(int, int&, bool&) ()
      7 0x000000000049e505 in CPhpResponseReader::Read(CReallocableBuffer&, int) ()
      8 0x00000000004a5207 in CHttpPostBufferInterface::Flush() ()
      9 0x00000000004a5a18 in CPhpRequestJob::Execute() ()
      10 0x00000000004a2d28 in CThreadPool::Worker(void*) ()
      11 0x00007feb90b1be9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
      12 0x00007feb90e24cbd in clone () from /lib/x86_64-linux-gnu/libc.so.6
      

      更多详情:

      WaitUntilReadable(见回溯)是一个基本上使用 select() 来等待从网络服务器读取一些数据的函数。我改用 epoll() 再次重写了该函数。

      我的程序是一个与数千个客户端保持连接的服务器。客户端向服务器发出请求,然后服务器将这些请求传递给网络服务器,并将响应发送回客户端。 对于对网络服务器的请求,我的服务器有一个线程池。 所以这里出现了另一个重要的细节:如果我将线程池的线程数设置为很高的数字,就会发生崩溃,这意味着同时进行了大量的 select() 调用(我将其复制为 1024 个线程)。然后我启动了一个我为测试而制作的客户端模拟器,并启动了 1000 个客户端线程,尽可能快地发出请求。这使得它在使用 select() 并在 release 中编译时快速崩溃,但在引入 epoll() 后没有问题。

      请注意,池中的每个线程都在使用 1 个 fd 进行 select() 调用。

      这是我为解决此问题而更改的函数:

      bool WaitUntilReadableDeprecated(int timeoutMSecs, int& elapsedMSecs, bool& closed)
      {
          CTimeLapsus t;
          fd_set fileDescriptorSet;
          struct timeval timeStruct;
          closed = false;
      
          timeStruct.tv_sec = timeoutMSecs / 1000;
          timeStruct.tv_usec = 0;
      
          FD_ZERO(&fileDescriptorSet);
          FD_SET(m_socket, &fileDescriptorSet);
      
          int sel = select(m_socket + 1, &fileDescriptorSet, NULL, NULL, &timeStruct);
      
          if(sel == 0)
          {
              LogDebug("[CPhpResponseReader::WaitUntilReadable] select() returned 0, no data available");
              elapsedMSecs = t.GetElapsedMilis();
              return false;
          }
          else if(sel == -1)
          {
              if(errno ==  EBADF)
              {
                  LogDebug("[CPhpResponseReader::WaitUntilReadable] select() returned -1, errno is EBADF, connection reset by host?");
                  closed = true;
                  elapsedMSecs = t.GetElapsedMilis();
                  return true;
              }
      
              throw "CPhpResponseReader::WaitUntilReadable select error";
          }
      
          elapsedMSecs = t.GetElapsedMilis();
          return true;
      }
      
      bool WaitUntilReadableEpoll(int timeoutMSecs, int& elapsedMSecs, bool& closed)
      {
          CIoPoller poller(8);
          CTimeLapsus t;
          closed = false;
      
          if(poller.Add(m_socket, EPOLLIN) == -1)
              LogError("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Add(%d, EPOLLIN) failed", m_socket);
      
          int nfds = poller.Wait(timeoutMSecs);
          if (nfds > 0)
          {
              int theSocket = poller.GetEvents()[0].data.fd;
              uint32_t event = poller.GetEvents()[0].events;
              if(theSocket != m_socket)
              {
                  LogError("[CPhpResponseReader::WaitUntilReadableEpoll] socket is different than expected", m_socket);
                  elapsedMSecs = t.GetElapsedMilis();
                  return false;
              }
              if((event & EPOLLERR) || (event & EPOLLHUP))
              {
                  LogWarning("[CPhpResponseReader::WaitUntilReadableEpoll] Disconnected socket %d (event %d)", m_socket, event);
                  elapsedMSecs = t.GetElapsedMilis();
                  closed = true;
                  return false;
              }
              if(event & EPOLLIN)
              {
                  // ok
              }
          }
          else if (nfds == -1)
          {
              if(errno ==  EBADF)
              {
                  LogWarning("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() returned -1, errno is EBADF, maybe connection reset by host");
                  closed = true;
                  elapsedMSecs = t.GetElapsedMilis();
                  return true;
              }
              LogError("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() failed");
              elapsedMSecs = t.GetElapsedMilis();
              closed = true;
              return false;
          }
          else
          {
              LogDebug("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() returned 0, no data available");
              elapsedMSecs = t.GetElapsedMilis();
              return false;
          }
      
          elapsedMSecs = t.GetElapsedMilis();
          return true;
      }
      

      CIoPoller 只是 epoll 的 c++ 包装器。

      Ubuntu 版本:

      Distributor ID: Ubuntu
      Description:    Ubuntu 12.04.1 LTS
      Release:    12.04
      

      【讨论】:

        【解决方案4】:

        我遇到同样的缓冲区溢出情况:

        httperf --hog --wsess=1,3000,1 --rate 1 --burst-length=3000 --max-piped-calls 1 --max-connections=3000 --timeout 20 --server 127.0.0.1 --port 8888 --uri /
        

        看:https://gist.github.com/diyism/9586262

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-07-02
          • 1970-01-01
          • 2012-05-19
          • 2012-08-30
          • 1970-01-01
          • 2019-07-15
          • 2015-12-07
          相关资源
          最近更新 更多