2015-06-12 60 views
24

最近版本的GCC和Clang功能未定义的行为Sanitizer(UBSan)是一个编译标志(-fsanitize=undefined),它添加了运行时检测代码。在出现错误时,会显示如下这样的警告:如何在gdb中打破UBSan报告并继续?

packet-ber.c:1917:23: runtime error: left shift of 54645397829836991 by 8 places cannot be represented in type '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似乎工作,名称看起来很内部。

+4

我认为,直到一个API被实现并记录在案,一个好的方法来捕获对UBSan运行时库的调用,以继续你的程序的目的是做'rbreak^__ ubsan_handle_',这将在图书馆事业之前停止执行到其分配Diag类的实例的C++领土。打开所有你想要的,然后输入'return'继续你的程序。 –

+6

为了将来的参考,'abort_on_error'似乎对UBSAN未实现。请改为使用它:'UBSAN_OPTIONS = print_stacktrace = 1:halt_on_error = 1' – Lekensteyn

+1

另请参阅[当-fsanitize = undefined打印某些内容时如何在调试器中断开](http://clang-developers.42468.n3.nabble.com/ How to-break-in-debugger-when-fsanitize-undefined-prints-something-td4032345.html)在Clang Dev邮件列表中。 – jww

回答

14

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

摘要:您可以通过断点上ubsan报告停止__ubsan::ScopedReport::~ScopedReport__ubsan::Diag::~Diag。这些是未来可能会改变的私人实施细节。用GCC 4.9,5.1.0,5.2.0和Clang 3.3,3.4,3.6.2进行测试。

对于GCC 4.9.2从ppa:ubuntu-toolchain-r/test,您需要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的一部分documented public interface。只要检测到违反此函数被调用,它的流量继续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环境变量时打印堆栈跟踪。因此,通过搜索源代码print_stacktrace,人们发现功能MaybePrintStackTrace被称为虽然ScopedReport destructor

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

正如你所看到的,有杀死在错误程序的方法,但遗憾的是没有内置机制来触发调试器陷阱。那么我们找一个合适的断点。

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

+0

+1。它让我想起你的问题暴露了一种需求,如果卫生洗涤剂具有强制 - 非 - 内在 - ,外部 - 连接,空白 - 返回空的内部功能,可以通过一个调试器,并且在报告错误时调用此参数对调试器有用。 [类似于JIT注册界面](https://sourceware.org/gdb/onlinedocs/gdb/Registering-Code.html#Registering-Code)。 –

+0

如果对实际问题的回答更加突出,这个答案会更有用。 – xaxxon

+0

@xaxxon我很乐意提供建议,您能否澄清哪些部分需要改进? – Lekensteyn

11

由于@Mark Plotnick points out,这样做的方式是在UBSan的处理程序处断点。

UBSan有一些处理程序或魔术功能入口点,这些处理程序被称为未定义的行为。编译器仪器通过适当注入检查来进行编码;如果检查代码检测到UB,它会调用这些处理程序。他们都以__ubsan_handle_开头,并在libsanitizer/ubsan/ubsan_handlers.h中定义。这是一个link to GCC's copy of 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) 

阿三更容易。如果您在libsanitizer/include/sanitizer/asan_interface.h看,你应该浏览here,您可以阅读评论的大破绽:在这个头

// 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的其他标题here。那里有很多好东西。

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

这将断点处理程序,并finish随即:


断点UBSan和阿三可以如下补充。这允许打印报告,但调试器在打印后立即得到控制权。

+3

我希望为此存在单个断点或环境变量。无论如何,你可以在'gdb'中显示一个使用这个特性的例子吗?有一种行为像ASAN的方法(或者简单的'gdbinit'宏)会很棒。也就是说,显示消息并中断。 – Lekensteyn

+0

@Lekensteyn环境变量不能注入断点;并且为了存在单个可突破的函数,UBSan将不得不使用单个多路复用可变参数函数(这不一定是好的设计,并且会让您只能在某些UB类型上高效地断点)。关于'gdb' /'gdbinit',当然,我会在几个小时内添加。 –

+0

@Lekensteyn看起来像'(gdb)rbreak __asan_report_error'和'(gdb)rbreak^__ ubsan'直截了当地断定与这些正则表达式匹配的所有函数。 '__asan_report_error'返回后没有函数调用,所以在我看来,在碰到这些断点之一后,你必须编程'gdb'立即'(gdb)finish'。 –