【问题标题】:Is there a portable equivalent to DebugBreak()/__debugbreak?是否有可移植的等价于 DebugBreak()/__debugbreak?
【发布时间】:2010-09-15 11:24:20
【问题描述】:

在 MSVC 中,DebugBreak()__debugbreak 会导致调试器中断。在 x86 上它相当于写“_asm int 3”,在 x64 上它是不同的。使用 gcc(或任何其他标准编译器)编译时,我也想中断调试器。是否有独立于平台的功能或内在功能?我看到了XCode question,但它似乎不够便携。

旁注:我主要想用它来实现 ASSERT,我知道我可以为此使用 assert(),但我也想在代码中编写 DEBUG_BREAK 或其他东西。

【问题讨论】:

    标签: c++ portability debugbreak


    【解决方案1】:

    FWIW,这些解决方案均不适用于使用 NRF Connect SDK 的 nRF9160。这是一个 SEGGER Embedded Studio for ARM(北欧版)环境,使用 arm-none-eabi-gcc 编译器。

    其他答案中提到的debug-trap.hdebugbreak.h__builtin_trap()都导致“未定义的操作码”和硬故障(或调试监视器故障,但结果相同)并且没有有用的程序计数器、堆栈帧或其他可调试信息。

    最后,这个替代方案确实奏效了。我是从其他一些神秘的北欧图书馆派生出来的,它被称为NRF_BREAKPOINT

    #if defined(__GNUC__)
        __asm__("BKPT 0");
    #else
        __BKPT(0)
    #endif
    

    在构建时,会包含__GNUC__ 路径,因此只需要__asm__("BKPT 0")

    【讨论】:

      【解决方案2】:

      我刚刚将a module 添加到portable-snippets(可移植代码的公共域sn-ps 集合)来执行此操作。它不是 100% 可移植的,但它应该非常健壮:

      • __builtin_debugtrap 用于某些版本的 clang(标识为 __has_builtin(__builtin_debugtrap)
      • 在 MSVC 和 Intel C/C++ 编译器上:__debugbreak
      • 对于 ARM C/C++ 编译器:__breakpoint(42)
      • 对于 x86/x86_64,程序集:int3
      • 对于 ARM Thumb,程序集:.inst 0xde01
      • 对于 ARM AArch64,程序集:.inst 0xd4200000
      • 对于其他 ARM,程序集:.inst 0xe7f001f0
      • 对于 Alpha,程序集:bpt
      • 对于带有 GCC 的非托管 C(或伪装成它的东西),__builtin_trap
      • 否则,包括signal.h
        • 如果defined(SIGTRAP)(即POSIX),raise(SIGTRAP)
        • 否则,raise(SIGABRT)

      将来,portable-sn-ps 中的模块可能会扩展以包含其他逻辑,我可能会忘记更新此答案,因此您应该在那里查找更新。它是公共领域 (CC0),所以请随意窃取代码。

      【讨论】:

      • 对于 x86(包括 x86-64)GAS 语法,最好写 int3 以明确表示您需要特殊情况调试中断指令,一个字节 CC 而不是 CD 03 ,适用于罕见的情况(代码大小和 v8086 模式)。 (felixcloutier.com/x86/intn:into:int3:int1)。使用 NASM,它们实际上组装方式不同,GAS 将两者都优化为 int3
      • __builtin_trap 通常编译为ud2 (x86) 或其他非法指令,而不是调试断点,并且也被视为 noreturn 即使使用调试器也无法继续。它不属于这个列表。例如在 C return x 语句之前使用它的简单函数中,在 ud2 之后没有 ret 指令。
      • 谢谢@PeterCordes,我已经更新了这个答案和我的代码以使用int3。 FWIW,GCC 和 clang 也生成 int3(至少使用 -O3),这在这里真正重要,因为它是关于 C++ 而不是汇编。不过,听起来int3 更正确,所以没有理由不“修复”它:)
      • 对于__debug_trap,我不确定这里真的有什么可以做的。在注释和链接代码中,它都处于回退区域的深处,只有在其他一切都失败时才调用并且它是一个非托管环境(在这种情况下 signal.h 将不可用)。 AFAICT 替代方案要么什么都没有,要么是编译时错误。如果您对其他可能的替代方案有建议,我当然会开放;我同意这是次优的(因此它是最后的手段)。
      • -O3 应该无关紧要,即使对于 clang 的内置汇编器也是如此。这就是将 C++ 转换为 asm 的优化级别。 Asm 到机器代码(包括来自asm("") 模板字符串的asm)对于gcc 来说是一个真正独立的过程,对于clang 来说在逻辑上是独立的。但是,是的,int3 是个好主意;这就是 0xCC 的反汇编方式,它更准确地表达了你想要的东西。
      【解决方案3】:

      如果您认为assert(x) 足够便携,assert(false) 似乎是解决您问题的明显便携解决方案。

      【讨论】:

      • 在大多数情况下很好,但对发布代码没有太大帮助。是的,有时我必须调试发布代码...
      • assert 根本不是一个合适的解决方案,因为它通常不允许程序继续执行。
      【解决方案4】:

      对于这个问题,这似乎是一个非常好的、可移植的解决方案: https://github.com/scottt/debugbreak

      引用的存储库中提供的标头 (debugbreak.h) 封装了 MSVC 的

          __debugbreak, 
      

          __asm__ volatile("int $0x03");
      

      在 i386 和 x86_64 上,并在 ARM 上实现

          __asm__ volatile(".inst 0xe7f001f0");
      

      以及记录标题中指出的问题的一些解决方法,用于单步越过 GDB 中的断点,以及用于在 stepicont 的平台上扩展 GDB 的 Python 脚本em> 卡住了。该脚本将 debugbreak-stepdebugbreak-continue 添加到 GDB。

      【讨论】:

        【解决方案5】:
        #define __debugbreak() \
        do \
        {       static bool b; \
                while (!b) \
                        sleep(1); \
                b = false; \
        } while (false)
        

        当进程处于休眠状态时,您可以将调试器附加到进程,更改变量 b 以中断循环并执行您的操作。此代码可能无法在优化的构建中运行!

        【讨论】:

        • 这是唯一允许将调试器附加到 debugbreak() 中阻止的进程的解决方案——其余解决方案都会导致程序中止。
        【解决方案6】:

        如果您尝试调试与崩溃相关的情况,老式的 abort() 将在大多数平台上为您提供调用堆栈。缺点是您无法从当前 PC 继续,您可能也不想这样做。

        http://www.cplusplus.com/reference/cstdlib/abort/

        【讨论】:

          【解决方案7】:

          这看起来像一个合适的兼容库https://github.com/scottt/debugbreak

          【讨论】:

            【解决方案8】:

            一种可移植到大多数 POSIX 系统的方法是:

            raise(SIGTRAP);
            

            【讨论】:

            • raise(SIGTRAP) 在 gcc/Linux 上非常适合我。 __builtin_trap() 引发了 SIGILL 信号。
            • 这可以在 OSX 上使用吗?我在 Xcode 6.1 中试过这个我说 SIGTRAP 是一个未声明的标识符。
            • @thomthom:你#include <signal.h>了吗?
            • 不——我错过了。后来想出来一个。忘记删除我的评论了。
            • 适合我:适用于 iOS、macOS、tvOS 和 Android。
            【解决方案9】:

            与其使用“正常”调试中断,不如使用以下方法之一,例如除以零:

            int iCrash = 13 / 0;
            

            或取消引用 NULL 指针:

            BYTE bCrash = *(BYTE *)(NULL);
            

            至少这在许多平台/架构上是可移植的。

            在许多调试器中,您可以指定要对哪些异常执行什么操作,以便在遇到上述任一情况(如暂停执行,也就是“int 3”指令)并生成异常时采取相应措施。

            【讨论】:

            • 我实际上有一块板子,它会很高兴地执行 NULL 指针取消引用。除以零可能更安全。
            • 有趣。当它发生时,如何从这种异常中继续?使用 int 3 VS 调试器知道如何继续,我只需要按 Go (F5),或者如果我想禁用该位置上的断言,我可以使用技巧 stackoverflow.com/questions/115237 - 这里有类似的东西吗?
            • 取消引用 NULL (== 0) 在大多数嵌入式系统上实际上并不是一个错误,因为地址 0 通常是一个真实的内存位置。在 ARM 内核上,它是向量表。
            • 请避免使用此解决方法。这是一个令人难以置信的安全风险,它使堆栈处于不一致的状态,并且根据应用程序的不同,它可以被用于漏洞利用!
            【解决方案10】:

            GCC 有一个名为 __builtin_trap 的内置函数,您可以看到 here,但是假设一旦达到此值,代码执行就会停止。

            应该确保__builtin_trap() 调用是有条件的,否则在它之后不会发出任何代码。

            这篇文章由 5 分钟的测试推动,YMMV。

            【讨论】:

            • __builtin_trap 通常编译为ud2 (x86) 或其他非法指令,而不是调试断点,并且也被视为 noreturn 即使使用调试器也无法继续。
            【解决方案11】:

            如何定义一个基于 #ifdef 的条件宏,该宏可以扩展为基于当前架构或平台的不同结构。

            类似:

            #ifdef _MSC_VER
            #define DEBUG_BREAK __debugbreak()
            #else
            ...
            #endif
            

            这将由预处理器根据编译代码的平台扩展正确的调试器中断指令。这样您就可以在代码中始终使用DEBUG_BREAK

            【讨论】:

              猜你喜欢
              • 2013-07-05
              • 1970-01-01
              • 2021-08-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-15
              相关资源
              最近更新 更多