在Linux上,在断言失败后继续调试?

16

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

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

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

但是尽管GDB(通过KDevelop)在正确的点上停止了,但我似乎无法继续跳过这个信号,在GDB内手动发送信号只会让我陷入僵局,既无法控制GDB也无法控制正在调试的进程。

5个回答

19

您真的想重新创建DebugBreak的行为。这将停止调试器中的程序。

我在谷歌上搜索"DebugBreak linux",发现有几个参考文献提到此内联汇编代码可以实现相同效果。

#define DEBUG_BREAK asm("int $3")

那么您的断言可以变成这样:

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

根据Andomar的说法,int 3会导致CPU触发中断3。而根据drpepper的说法,更加可移植的方法是调用:

 raise(SIGTRAP);

1
它将导致CPU引发中断3(http://faydoc.tripod.com/cpu/int3.htm)。调试器已为中断3注册了中断处理程序,并将中断程序。 - Andomar
太完美了!它捕获了一个SIGTRAP事件,立即停止,然后让我继续!非常感谢。 - drpepper
1
为了使其更加便携,我用等效的c代码替换了汇编代码:raise(SIGTRAP);效果很好。 - drpepper
你的宏在存在周围的 else 时出现了故障。 - Lightness Races in Orbit
值得注意的是,int 3 是英特尔特定的,而 raise(SIGTRAP) 在我在 iOS 和 Android 上的 ARM32/64 和 MIPS 上都可以工作,我认为它也因为是标准库的一部分而在任何地方都能工作。 - jheriko

10
您可以配置gdb以不同的方式处理特定信号。例如,以下内容将导致SIGSTOP不被视为可停止事件。 handle SIGSTOP nostop noprint pass 在gdb中输入help handle将提供更多信息。

2
更好的可用性可以通过以下方式实现:
/*!
 * \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

1

您可以将 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();
}

哦,不错。当NDBUG未定义时,Posix在指定中止行为时真的失误了。谁会认为自我诱导的崩溃适用于调试和诊断呢…… - jww
“pause”函数声明在哪里? - Brent
@Brent,干得好!pause()在man. sec 2中定义,这是libc _et amici_部分。在我的副本(Debian 11)中,man pause.2将兼容性列为“POSIX.1-2001、POSIX.1-2008、SVr4、4.3BSD”,这就说明了API的古老和广泛兼容性。示例代码只是缺少#include <unistd.h>,我会进行编辑,感谢您的注意。 - kkm

1
你尝试过向进程发送SIGCONT信号吗?
kill -s SIGCONT <pid>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接