如何在gdb中打破UBSan报告并继续?

最新版本的GCC和Clang具有Undefined Behavior Sanitizer(UBSan),它是一个编译标志( -fsanitize=undefined ),用于添加运行时检测代码。 出现错误时,会显示如下警告:

packet-ber.c:1917:23:运行时错误:左移54645397829836991 8个位置无法在类型’long int’中表示

现在我想调试这个并在所述行上获得调试中断。 对于Address Sanitizer(ASAN), ASAN_OPTIONS=abort_on_error=1会导致可捕获的致命错误。 唯一可用的UBSan选项是UBSAN_OPTIONS=print_stacktrace=1 ,这会导致报告的调用跟踪转储。 但是,这不允许我检查局部变量,然后继续该程序。 因此无法使用-fsanitize-undefined-trap-on-error

我应该如何在UBSan报告中打破gdb? 虽然break __sanitizer::SharedPrintfCode似乎有效但名称看起来很内部。

虽然打破检测function(如@Mark Plotnick和@Iwillnotexist Idonotexist所述 )是一种选择,但更好的方法是打破检测后报告这些问题的function。 这种方法也用于ASAN,其中一个会破坏__asan_report_error

简介:您可以通过__ubsan::ScopedReport::~ScopedReport__ubsan::Diag::~Diag上的断点来停止ubsan报告。 这些是私有实现细节,但将来可能会发生变化。 使用GCC 4.9,5.1.0,5.2.0和Clang 3.3,3.4,3.6.2进行测试。

对于来自ppa:ubuntu-toolchain -r / test的 GCC 4.9.2,需要libubsan0-dbg才能使上述断点可用。 使用Clang 3.3和3.4的Ubuntu 14.04不支持__ubsan::ScopedReport::~ScopedReport断点,所以你只能在使用__ubsan::Diag::~Diag打印消息之前中断。

示例错误源代码和gdb会话:

 $ cat undef.c int main(void) { return 1 << 1000; } $ clang --version clang version 3.6.2 (tags/RELEASE_362/final) Target: x86_64-unknown-linux-gnu Thread model: posix $ clang -w -fsanitize=undefined undef.c -g $ gdb -q -ex break\ __ubsan::ScopedReport::~ScopedReport -ex r ./a.out Reading symbols from ./a.out...done. Breakpoint 1 at 0x428fb0 Starting program: ./a.out undef.c:1:27: runtime error: shift exponent 1000 is too large for 32-bit type 'int' Breakpoint 1, 0x0000000000428fb0 in __ubsan::ScopedReport::~ScopedReport() () (gdb) bt #0 0x0000000000428fb0 in __ubsan::ScopedReport::~ScopedReport() () #1 0x000000000042affb in handleShiftOutOfBoundsImpl(__ubsan::ShiftOutOfBoundsData*, unsigned long, unsigned long, __ubsan::ReportOptions) () #2 0x000000000042a952 in __ubsan_handle_shift_out_of_bounds () #3 0x000000000042d057 in main () at undef.c:1 

详细分析如下。 请注意,ASAN和ubsan都源自LLVM项目compiler-rt 。 这由Clang使用,最终也在GCC中使用。 以下部分中的链接指向编译器-rt项目代码,版本3.6。

ASAN已将其内部__asan_report_error作为记录的公共接口的一部分 。 每当检测到违规时,都会调用此函数,其流程在lib / asan / asan_report.c中继续:938 :

 void __asan_report_error(uptr pc, uptr bp, uptr sp, uptr addr, int is_write, uptr access_size) { // Determine the error type. const char *bug_descr = "unknown-crash"; ... ReportData report = { pc, sp, bp, addr, (bool)is_write, access_size, bug_descr }; ScopedInErrorReport in_report(&report); Decorator d; Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: %s on address " "%p at pc %p bp %p sp %p\n", bug_descr, (void*)addr, pc, bp, sp); Printf("%s", d.EndWarning()); u32 curr_tid = GetCurrentTidOrInvalid(); char tname[128]; Printf("%s%s of size %zu at %p thread T%d%s%s\n", d.Access(), access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", access_size, (void*)addr, curr_tid, ThreadNameWithParenthesis(curr_tid, tname, sizeof(tname)), d.EndAccess()); GET_STACK_TRACE_FATAL(pc, bp); stack.Print(); DescribeAddress(addr, access_size); ReportErrorSummary(bug_descr, &stack); PrintShadowMemoryForAddress(addr); } 

另一方面,ubsan没有公共接口,但它目前的实现也更简单和有限(更少的选项)。 在出现错误时,可以在设置UBSAN_OPTIONS=print_stacktrace=1环境变量时打印UBSAN_OPTIONS=print_stacktrace=1 。 因此,通过搜索print_stacktrace的源代码,可以找到通过ScopedReport析构函数调用的函数MaybePrintStackTrace :

 ScopedReport::~ScopedReport() { MaybePrintStackTrace(Opts.pc, Opts.bp); MaybeReportErrorSummary(SummaryLoc); CommonSanitizerReportMutex.Unlock(); if (Opts.DieAfterReport || flags()->halt_on_error) Die(); } 

如您所见,有一种方法可以在出错时终止程序,但不幸的是,没有内置机制来触发调试器陷阱。 那么让我们找一个合适的断点。

GDB命令info functions 使得可以将MaybePrintStackTrace标识为可以设置断点的函数。 执行info functions ScopedReport::~ScopedReport给了另一个函数: __ubsan::ScopedReport::~ScopedReport 。 如果这些函数都不可用(即使安装了调试符号),您可以尝试使用info functions ubsaninfo functions sanitizer来获取所有(UndefinedBehavior)与Sanitizer相关的函数。

正如@Mark Plotnick 指出的那样 ,这样做的方法是在UBSan的处理程序中断点。

UBSan有许多处理程序或魔术函数入口点,它们被称为未定义的行为。 编译器通过适当注入检查来编码; 如果检查代码检测到UB,则调用这些处理程序。 它们都以__ubsan_handle_开头,并在libsanitizer/ubsan/ubsan_handlers.h中定义。 这是GCC的ubsan_handlers.h副本的ubsan_handlers.h

这是UBSan头的相关位(任何一个断点):

 #define UNRECOVERABLE(checkname, ...) \ extern "C" SANITIZER_INTERFACE_ATTRIBUTE NORETURN \ void __ubsan_handle_ ## checkname( __VA_ARGS__ ); #define RECOVERABLE(checkname, ...) \ extern "C" SANITIZER_INTERFACE_ATTRIBUTE \ void __ubsan_handle_ ## checkname( __VA_ARGS__ ); \ extern "C" SANITIZER_INTERFACE_ATTRIBUTE NORETURN \ void __ubsan_handle_ ## checkname ## _abort( __VA_ARGS__ ); /// \brief Handle a runtime type check failure, caused by either a misaligned /// pointer, a null pointer, or a pointer to insufficient storage for the /// type. RECOVERABLE(type_mismatch, TypeMismatchData *Data, ValueHandle Pointer) /// \brief Handle an integer addition overflow. RECOVERABLE(add_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) /// \brief Handle an integer subtraction overflow. RECOVERABLE(sub_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) /// \brief Handle an integer multiplication overflow. RECOVERABLE(mul_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) /// \brief Handle a signed integer overflow for a unary negate operator. RECOVERABLE(negate_overflow, OverflowData *Data, ValueHandle OldVal) /// \brief Handle an INT_MIN/-1 overflow or division by zero. RECOVERABLE(divrem_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) /// \brief Handle a shift where the RHS is out of bounds or a left shift where /// the LHS is negative or overflows. RECOVERABLE(shift_out_of_bounds, ShiftOutOfBoundsData *Data, ValueHandle LHS, ValueHandle RHS) /// \brief Handle an array index out of bounds error. RECOVERABLE(out_of_bounds, OutOfBoundsData *Data, ValueHandle Index) /// \brief Handle a __builtin_unreachable which is reached. UNRECOVERABLE(builtin_unreachable, UnreachableData *Data) /// \brief Handle reaching the end of a value-returning function. UNRECOVERABLE(missing_return, UnreachableData *Data) /// \brief Handle a VLA with a non-positive bound. RECOVERABLE(vla_bound_not_positive, VLABoundData *Data, ValueHandle Bound) /// \brief Handle overflow in a conversion to or from a floating-point type. RECOVERABLE(float_cast_overflow, FloatCastOverflowData *Data, ValueHandle From) /// \brief Handle a load of an invalid value for the type. RECOVERABLE(load_invalid_value, InvalidValueData *Data, ValueHandle Val) RECOVERABLE(function_type_mismatch, FunctionTypeMismatchData *Data, ValueHandle Val) /// \brief Handle returning null from function with returns_nonnull attribute. RECOVERABLE(nonnull_return, NonNullReturnData *Data) /// \brief Handle passing null pointer to function with nonnull attribute. RECOVERABLE(nonnull_arg, NonNullArgData *Data) 

ASan更容易。 如果您查看libsanitizer/include/sanitizer/asan_interface.h (您应该在这里浏览),您可以阅读评论的死亡赠品:

  // This is an internal function that is called to report an error. // However it is still a part of the interface because users may want to // set a breakpoint on this function in a debugger. void __asan_report_error(void *pc, void *bp, void *sp, void *addr, int is_write, size_t access_size); 

此标头中的许多其他函数被明确注释为已公开,以便可以从调试器调用。

我绝对建议你在这里探索libsanitizer/include/sanitizer其他标题。 那里有很多好东西。


UBSan和ASan的断点可以添加如下:

 (gdb) rbreak ^__ubsan_handle_ __asan_report_error (gdb) commands (gdb) finish (gdb) end 

这将在处理程序上断点,然后立即finish 。 这允许打印报告,但调试器在打印后立即获得控制权。