2015-06-12 58 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'。 –