44

Recent versions of GCC and Clang feature Undefined Behavior Sanitizer (UBSan) which is a compile flag (-fsanitize=undefined) that adds runtime instrumentation code. On errors, a warning such as this one is shown:

packet-ber.c:1917:23: runtime error: left shift of 54645397829836991 by 8 places cannot be represented in type 'long int'

Now I would like to debug this and get a debug break on said line. For Address Sanitizer (ASAN) there is ASAN_OPTIONS=abort_on_error=1 which results in a fatal error that is catchable. The only UBSan option that seems usable is UBSAN_OPTIONS=print_stacktrace=1 which results in a call trace dump for reports. This however does not allow me to inspect the local variables and then continue the program. Use of -fsanitize-undefined-trap-on-error therefore not possible.

How should I break in gdb on UBSan reports? While break __sanitizer::SharedPrintfCode seems to work, the name looks quite internal.

jww
  • 97,681
  • 90
  • 411
  • 885
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • 4
    I think that until an API is implemented and documented, a good way to catch a call to the UBSan runtime library with the intent of continuing your program is to do `rbreak ^__ubsan_handle_`, which will stop execution before the library ventures into C++ territory wherein it allocates instances of the Diag class. Poke around all you want, then type `return` to continue your program. – Mark Plotnick Jun 15 '15 at 19:03
  • 11
    For future reference, `abort_on_error` seems unimplemented for UBSAN. Use this instead: `UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1` – Lekensteyn Jul 07 '15 at 17:08
  • 1
    Which GCC/Clang version are you using, precisely? – Iwillnotexist Idonotexist Jul 16 '15 at 21:01
  • 2
    @IwillnotexistIdonotexist Very recent versions on Arch Linux, GCC 5.1.0, Clang 3.6.1. – Lekensteyn Jul 16 '15 at 23:51
  • @MarkPlotnick I don't understand the comment. What is the exact GDB breakpoint command required to achieve the OP's question? – intrigued_66 Jan 22 '23 at 00:48

3 Answers3

29

While breaking on the detection functions (as described by @Mark Plotnick and @Iwillnotexist Idonotexist) is one option, a better approach is breaking on the functions that report these issues after detection. This approach is also used for ASAN where one would break on __asan_report_error.

Summary: You can stop on an ubsan report via a breakpoint on __ubsan::ScopedReport::~ScopedReport or __ubsan::Diag::~Diag. These are private implementation details which might change in the future though. Tested with GCC 4.9, 5.1.0, 5.2.0 and Clang 3.3, 3.4, 3.6.2.

For GCC 4.9.2 from ppa:ubuntu-toolchain-r/test, you need libubsan0-dbg to make the above breakpoints available. Ubuntu 14.04 with Clang 3.3 and 3.4 do not support the __ubsan::ScopedReport::~ScopedReport breakpoints, so you can only break before printing the message using __ubsan::Diag::~Diag.

Example buggy source code and a gdb session:

$ 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

Detailled analysis follows. Note that both ASAN and ubsan both originate from a LLVM project, compiler-rt. This is used by Clang and ends up in GCC as well. Links in the following sections point to the compiler-rt project code, release 3.6.

ASAN has made its internal __asan_report_error part of the documented public interface. This function gets called whenever a violation is detected, its flow continues in 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 on the other hand has no public interface, but its current implementation is also much simpler and limited (less options). On errors, a stacktrace can be printed when the UBSAN_OPTIONS=print_stacktrace=1 environment variable is set. Thus, by searching the source code for print_stacktrace, one finds function MaybePrintStackTrace which is called though the ScopedReport destructor:

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

As you can see, there is a method to kill the program on errors, but unfortunately there is no builtin mechanism to trigger a debugger trap. Let's find a suitable breakpoint then.

The GDB command info functions <function name> made it possible to identify MaybePrintStackTrace as function on which a breakpoint can be set. Execution of info functions ScopedReport::~ScopedReport gave another function: __ubsan::ScopedReport::~ScopedReport. If none of these functions seem available (even with debugging symbols installed), you can try info functions ubsan or info functions sanitizer to get all (UndefinedBehavior)Sanitizer-related functions.

Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • +1. It springs to my mind that your question exposes a need, and this need would be met if the sanitizers had a force-no-`inline`, `extern`-linkage, `void`-returning empty internal function that could be breakpointed by a debugger, and that this function be called with arguments useful to the debugger when reporting insanity. Something [similar to the JIT registration interface](https://sourceware.org/gdb/onlinedocs/gdb/Registering-Code.html#Registering-Code). – Iwillnotexist Idonotexist Jul 23 '15 at 19:03
  • 1
    This answer would be more useful if the answer to the actual question were made more prominent. – xaxxon Mar 09 '18 at 06:08
  • @xaxxon I am open for suggestions, could you clarify what part needs improvement? – Lekensteyn Mar 09 '18 at 12:55
  • clear specific steps for what do do not interspersed with a ton of unrelated stuff about asan. – xaxxon Mar 09 '18 at 18:26
  • @xaxxon The first part contains all information you need to set break on UBSAN reports. The second part is informational only, you can ignore it if you do not need to understand how things work. Should the internal implementation change in the future, then the second part should help the reader to figure out how to adapt. ASAN and UBSan are closely related, so I thought it would make sense to compare both. – Lekensteyn Mar 11 '18 at 15:41
  • Asan and ubsan don't appear to be closely related. They can't even agree on common options, which is why ` ASAN_OPTIONS=abort_on_error=1` doesn't work if you change `ASAN_` to` UBSAN_`. If they were closely related, they would share common configurations. – Kaz Feb 22 '22 at 20:11
19

As @Mark Plotnick points out, the way to do so is to breakpoint at UBSan's handlers.

UBSan has a number of handlers, or magic function entry points, that are called for undefined behaviour. The compiler instruments code by injecting checks as appropriate; If the check code detects UB, it calls these handlers. They all start with __ubsan_handle_ and are defined in libsanitizer/ubsan/ubsan_handlers.h. Here's a link to GCC's copy of ubsan_handlers.h.

Here's the relevant bits of the UBSan header (breakpoint on any of these):

#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 is even easier. If you look in libsanitizer/include/sanitizer/asan_interface.h, which you should browse here, you can read a dead giveaway of a comment:

  // 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);

Numerous other functions in this header are explicitly commented as having been made public so as to be callable from a debugger.

I definitely advise you to explore other headers of libsanitizer/include/sanitizer here. There are numerous goodies to be had there.


Breakpoints for UBSan and ASan can be added as follows:

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

This will breakpoint on the handlers, and finish immediately afterwards. This allows the report to be printed, but the debugger gets control right after it gets printed.

Community
  • 1
  • 1
Iwillnotexist Idonotexist
  • 13,297
  • 4
  • 43
  • 66
  • 3
    I was hoping that a single breakpoint or environment variable existed for this. Anyway, can you show an example on using this feature with `gdb`? It would be great to have an approach (or simple `gdbinit` macro) that acts like ASAN. That is, display the message and break. – Lekensteyn Jul 16 '15 at 19:12
  • @Lekensteyn Environment variables can't inject breakpoints; and for a single breakpointable function to exist, UBSan would have to use a single, multiplexing, varargs function (which is not necessarily good design, and loses you the ability to efficiently breakpoint only on some UB types). With respect to `gdb`/`gdbinit`, sure, I'll add that in a couple hours. – Iwillnotexist Idonotexist Jul 16 '15 at 20:44
  • @Lekensteyn Seems like `(gdb) rbreak __asan_report_error` and `(gdb) rbreak ^__ubsan` bluntly breakpoint all functions matching those regexes. There is no function called after `__asan_report_error` returns, so it would seem to me that you'd somehow have to program `gdb` to immediately `(gdb) finish` after hitting one of these breakpoints. – Iwillnotexist Idonotexist Jul 16 '15 at 21:57
  • There are env vars such as `UBSAN_OPTIONS` that are supported by libsanitizer (not gdb or such), those vars I am referring to by "environment variables". As for the breakpoint, I tried `rbreak ^__ubsan_handle_` `commands` `finish` `end`, but somehow this behaves strange with a very simple test program (function is still stuck in the `__ubsan_handle_...` frame) while a following `info breakpoints` somehow triggers the print and changes the frame. (GDB 7.9.1) – Lekensteyn Jul 16 '15 at 23:55
  • UBSan has an unsigned-integer-overflow check. Is there a way to say that a given expression with unsigned-integer-overflow is ok? (So that it is ignored from the report?). – gnzlbg Oct 26 '15 at 17:23
  • @gnzlbg I'm shocked. Unsigned overflow is explicitly defined, and is definitely not UB; It's defined to be modulo `UINTMAX+1`. But maybe you could try `-fno-sanitize=unsigned-integer-overflow`? I don't think it's possible to specify that _a particular_ statement be ignored unless you extract it into a function and compile it in a different file with different flags. – Iwillnotexist Idonotexist Oct 26 '15 at 17:43
  • @IwillnotexistIdonotexist Yes, your are completely correct, it is not undefined behavior. However, most of the time it is still a bug, and the sanitizers offer an opt-in `unsigned-integer-overflow` check that you can use to check your code. This check has found tons of bugs in our applications. Just because its behavior is defined doesn't mean that it does what you think it does. Unsigned integer arithmetic is really full of gotchas and corner cases. – gnzlbg Oct 26 '15 at 18:05
  • @IwillnotexistIdonotexist in case it interests you, I posted a question and got an answer here: http://stackoverflow.com/questions/33351891/undefined-behavior-sanitizer-how-to-suppress-some-unsigned-integer-overflow-err/33352208#33352208 – gnzlbg Oct 26 '15 at 18:10
  • @gnzlbg Cool; Instead of separating out the function into a separate file, you used `__attribute__` to modify the compile flags for that specific function. I forgot about that! – Iwillnotexist Idonotexist Oct 26 '15 at 18:13
  • For the record, not all of the errors generated by UBSAN have an obvious mapping to `__ubsan_handle*`. By setting multiple breakpoints, I found out that binding a reference to `nullptr` triggers `__ubsan_handle_type_mismatch`, for example. – Arne Vogel Mar 13 '18 at 12:22
  • I don't undesrstand why all these handlers can't all call a single function like `void __ubsan_break(void) { }` on which the programmer can set a breakpoint. If your program only has a very low number of errors, as little as one, why should you have to care about the internal handlers for this or that. – Kaz Feb 22 '22 at 20:02
  • I'm using `rbreak ^__ubsan_handle_ __asan_report_error`, `commands`, `finish`, `end` but it's not working. Just exists on detection – intrigued_66 Jan 22 '23 at 00:53
5

A breakpoint set at __asan_report_error is not hit for me and the program simply exists after printing the diagnostics without the debugger triggering. __asan::ReportGenericError before printing the diagnostics and __sanitizer::Die after printing the diagnostics do get hit as described in the asan wiki.

nwp
  • 9,623
  • 5
  • 38
  • 68