【问题标题】:Continue to debug after failed assertion on Linux?在 Linux 上断言失败后继续调试?
【发布时间】:2010-12-15 20:17:57
【问题描述】:

当在 Windows 上使用 Visual C++ 的断言失败时,调试器会停止,显示消息,然后让您继续(或者,如果没有正在运行的调试会话,则提供为您启动 Visual Studio)。

在 Linux 上,assert() 的默认行为似乎是显示错误并退出程序。由于我所有的断言都通过宏,我尝试使用信号来解决这个问题,比如

#define ASSERT(TEST) if(!(TEST)) raise(SIGSTOP);

但尽管GDB(通过KDevelop)停在正确的点,我似乎无法继续越过信号,在 GDB 中手动发送信号只会让我悬而未决,既无法控制 GDB,也无法控制调试过程。

【问题讨论】:

    标签: c++ c linux gdb assert


    【解决方案1】:

    你真的想重现DebugBreak 的行为。这会停止调试器中的程序。

    我在谷歌上搜索的“DebugBreak linux”已经出现severalreferences 到这个应该做同样事情的内联程序集。

    #define DEBUG_BREAK asm("int $3")
    

    那么你的断言就可以变成

    #define ASSERT(TEST) if(!(TEST)) asm("int $3");
    

    根据 Andomar int 3 导致 cpu 引发中断 3。根据 drpepper,更便携的方法是调用:

     raise(SIGTRAP);
    

    【讨论】:

    • 它会导致 CPU 产生中断 3 (faydoc.tripod.com/cpu/int3.htm) 调试器有一个为中断 3 注册的中断处理程序,并且会中断程序。
    • 完美!它捕捉到一个 SIGTRAP 事件,停在一角钱上,然后让我继续!非常感谢。
    • 为了让它更便携,我用等效的 c 代码替换了程序集:raise(SIGTRAP);效果很好。
    • 您的宏在周围存在else 时已损坏
    • 值得注意的是 int 3 是特定于 intel 的,因为 raise(SIGTRAP) 在 iOS 和 Android 上的 ARM32/64 和 MIPS 上为我工作,我怀疑由于参与其中而在任何地方都可以工作标准库也是如此。
    【解决方案2】:

    您可以配置 gdb 以不同方式处理特定信号。例如,以下将导致 SIGSTOP 不被视为可停止事件。

    handle SIGSTOP nostop noprint pass

    gdb 中的help handle 将为您提供更多信息。

    【讨论】:

      【解决方案3】:

      使用

      可以实现更好的可用性
      /*!
       * \file: assert_x.h
       * \brief: Usability Improving Extensions to assert.h.
       * \author: Per Nordlöw
       */
      
      #pragma once
      
      #include <errno.h>
      #include <signal.h>
      #include <assert.h>
      
      #ifdef __cplusplus
      extern "C" {
      #endif
      
      #if !defined(NDEBUG)
      #  define passert(expr)                                                 \
        if (!(expr)) {                                                        \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' failed.",                \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(expr)); raise(SIGTRAP); \
        }
      #  define passert_with(expr, sig)                                       \
        if (!(expr)) {                                                        \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' failed.",                \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(expr)); raise(sig); \
        }
      #  define passert_eq(expected, actual)                                  \
        if (!(expected == actual)) {                                          \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' == `%s' failed.",        \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(expected), __STRING(actual)); raise(SIGTRAP); \
        }
      #  define passert_neq(expected, actual)                                 \
        if (!(expected != actual)) {                                          \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' != `%s' failed.",        \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(expected), __STRING(actual)); raise(SIGTRAP); \
        }
      #  define passert_lt(lhs, rhs)                                          \
        if (!(lhs < rhs)) {                                                   \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' < `%s' failed.",         \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(lhs), __STRING(rhs)); raise(SIGTRAP); \
        }
      #  define passert_gt(lhs, rhs)                                          \
        if (!(lhs > rhs)) {                                                   \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' < `%s' failed.",         \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(lhs), __STRING(rhs)); raise(SIGTRAP); \
        }
      #  define passert_lte(lhs, rhs)                                         \
        if (!(lhs <= rhs)) {                                                  \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' <= `%s' failed.",        \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(lhs), __STRING(rhs)); raise(SIGTRAP); \
        }
      #  define passert_gte(lhs, rhs)                                         \
        if (!(lhs >= rhs)) {                                                  \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' >= `%s' failed.",        \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(lhs), __STRING(rhs)); raise(SIGTRAP); \
        }
      #  define passert_zero(expr)                                            \
        if (!(expr == 0)) {                                                   \
          fprintf(stderr, "%s:%d: %s: Assertion `%s' is zero failed.",        \
                  __FILE__, __LINE__, __ASSERT_FUNCTION, __STRING(expr)); raise(SIGTRAP); \
        }
      #else
      #  define passert(expr)
      #  define passert_with(expr, sig)
      #  define passert_eq(expected, actual)
      #  define passert_lt(lhs, rhs)
      #  define passert_gt(lhs, rhs)
      #  define passert_lte(lhs, rhs)
      #  define passert_gte(lhs, rhs)
      #  define passert_zero(expr)
      #endif
      
      #ifdef __cplusplus
      }
      #endif
      

      【讨论】:

        【解决方案4】:

        您是否尝试向进程发送 SIGCONT 信号?

        kill -s SIGCONT <pid>
        

        【讨论】:

          【解决方案5】:

          您可以将assert 替换为您自己的版本,该版本调用pause() 而不是abort()。当断言失败时,程序将暂停,您可以运行gdb --pid $(pidof program) 来检查调用堆栈和变量。这种方法的一个优点是program 不需要在 GDB 下启动。

          头文件(基于/usr/include/assert.h):

          #include <assert.h>
          
          #ifndef NDEBUG
              void assert_fail(const char *assertion, const char *file, unsigned line, const char *function)
              __attribute__ ((noreturn));
              #undef assert
              #define assert(expr)            \
                  ((expr)                     \
                  ? __ASSERT_VOID_CAST (0)    \
                  : assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
          #endif /* NDEBUG */
          

          assert_fail的实现(基于glibc中的assert.c):

          #include <stdio.h>   /* for stderr, fprintf() */
          #include <stdlib.h>  /* for abort() */
          #include <unistd.h>  /* for pause() */
          
          void assert_fail(const char *assertion, const char *file, unsigned line, const char *function) {
              extern const char *__progname;
              fprintf(stderr, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
                  __progname,
                  __progname[0] ? ": " : "",
                  file,
                  line,
                  function ? function : "",
                  function ? ": " : "",
                  assertion
              );
              pause();
              abort();
          }
          

          【讨论】:

          • 哦,很好。当 Posix 在未定义 NDBUG 时指定中止行为时,他们真的丢球了。谁有正确的想法认为自我引发的崩溃适合调试和诊断......
          • “暂停”函数在哪里声明?
          • @Brent,好收获! pause() 在 man 中定义。 sec 2.,这是 libc et amici 部分。在我的副本(Debian 11)中,man pause.2 将兼容性列为“POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD”,这说明了 API 的悠久历史和广泛的兼容性。示例代码只是缺少#include &lt;unistd.h&gt;,我会编辑,感谢您的注意。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-04-08
          • 2015-12-18
          • 1970-01-01
          • 2011-10-13
          • 1970-01-01
          • 2011-06-11
          相关资源
          最近更新 更多