Wie kann ich UBSAN-Berichte in gdb unterbrechen und fortfahren?

Lesezeit: 11 Minuten

Benutzeravatar von Lekensteyn
Lekenstein

Neuere Versionen von GCC und Clang verfügen über Undefined Behavior Sanitizer (UBSan), ein Kompilier-Flag (-fsanitize=undefined), die Laufzeitinstrumentierungscode hinzufügt. Bei Fehlern wird eine Warnung wie diese angezeigt:

packet-ber.c:1917:23: Laufzeitfehler: Linksverschiebung von 54645397829836991 um 8 Stellen im Typ ‘long int’ nicht darstellbar

Jetzt möchte ich dies debuggen und eine Debug-Unterbrechung in dieser Zeile erhalten. Für Address Sanitizer (ASAN) gibt es ASAN_OPTIONS=abort_on_error=1 was zu einem schwerwiegenden Fehler führt, der abgefangen werden kann. Die einzige brauchbare UBSAN-Option ist UBSAN_OPTIONS=print_stacktrace=1 was zu einem Call-Trace-Dump für Berichte führt. Dies erlaubt mir jedoch nicht, die lokalen Variablen zu inspizieren und dann das Programm fortzusetzen. Gebrauch von -fsanitize-undefined-trap-on-error daher nicht möglich.

Wie sollte ich gdb in UBSAN-Berichten unterbrechen? Während break __sanitizer::SharedPrintfCode scheint zu funktionieren, der Name sieht ziemlich nach innen aus.

  • Ich denke, bis eine API implementiert und dokumentiert ist, ist es eine gute Möglichkeit, einen Aufruf an die UBSan-Laufzeitbibliothek mit der Absicht zu fangen, Ihr Programm fortzusetzen rbreak ^__ubsan_handle_, die die Ausführung stoppt, bevor die Bibliothek in das C++-Territorium vordringt, wo sie Instanzen der Diag-Klasse zuweist. Stöbere herum, was du willst, und tippe dann return um Ihr Programm fortzusetzen.

    – Markus Plotnick

    15. Juni 2015 um 19:03 Uhr


  • Zum späteren Nachschlagen, abort_on_error scheint für UBSAN nicht implementiert zu sein. Verwenden Sie stattdessen Folgendes: UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1

    – Lekenstein

    7. Juli 2015 um 17:08 Uhr

  • Siehe auch Wie bricht man den Debugger ein, wenn -fsanitize=undefined etwas ausgibt? auf der Clang Dev-Mailingliste.

    – jww

    16. Juli 2015 um 11:53 Uhr

  • Welche GCC/Clang-Version verwenden Sie genau?

    – Ich werde nicht existieren Ich werde nicht existieren

    16. Juli 2015 um 21:01 Uhr


  • @IwillnotexistIdonotexist Ganz aktuelle Versionen auf Arch Linux, GCC 5.1.0, Clang 3.6.1.

    – Lekenstein

    16. Juli 2015 um 23:51 Uhr

Benutzeravatar von Lekensteyn
Lekenstein

Während das Unterbrechen der Erkennungsfunktionen (wie von @Mark Plotnick und @Iwillnotexist Idonotexist beschrieben) eine Option ist, ist ein besserer Ansatz das Unterbrechen der Funktionen, die diese Probleme nach der Erkennung melden. Dieser Ansatz wird auch für ASAN verwendet, wo man aufbrechen würde __asan_report_error.

Zusammenfassung: Sie können einen ubsan-Bericht über einen Haltepunkt anhalten __ubsan::ScopedReport::~ScopedReport oder __ubsan::Diag::~Diag. Dies sind private Implementierungsdetails, die sich jedoch in Zukunft ändern können. Getestet mit GCC 4.9, 5.1.0, 5.2.0 und Clang 3.3, 3.4, 3.6.2.

Für GCC 4.9.2 ab ppa:ubuntu-toolchain-r/testdu brauchst libubsan0-dbg um die oben genannten Haltepunkte verfügbar zu machen. Ubuntu 14.04 mit Clang 3.3 und 3.4 unterstützen das nicht __ubsan::ScopedReport::~ScopedReport Haltepunkte, so dass Sie nur vor dem Drucken der Nachricht mit unterbrechen können __ubsan::Diag::~Diag.

Beispiel für fehlerhaften Quellcode und eine gdb-Sitzung:

$ 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

Eine detaillierte Analyse folgt. Beachten Sie, dass sowohl ASAN als auch ubsan aus einem LLVM-Projekt stammen. Compiler-rt. Dies wird von Clang verwendet und landet auch in GCC. Links in den folgenden Abschnitten verweisen auf den Compiler-RT-Projektcode, Version 3.6.

ASAN hat es intern gemacht __asan_report_error Teil von dokumentierte öffentliche Schnittstelle. Diese Funktion wird immer dann aufgerufen, wenn eine Verletzung erkannt wird, ihr Ablauf wird fortgesetzt 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 hingegen hat keine öffentliche Schnittstelle, aber seine aktuelle Implementierung ist auch viel einfacher und begrenzter (weniger Optionen). Bei Fehlern kann ein Stacktrace ausgegeben werden, wenn die UBSAN_OPTIONS=print_stacktrace=1 Umgebungsvariable gesetzt. Durchsuchen Sie also den Quellcode nach print_stacktracefindet man Funktion VielleichtPrintStackTrace das heißt aber die ScopedReport-Destruktor:

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

Wie Sie sehen können, gibt es eine Methode, um das Programm bei Fehlern zu beenden, aber leider gibt es keinen eingebauten Mechanismus, um eine Debugger-Falle auszulösen. Lassen Sie uns dann einen geeigneten Haltepunkt finden.

Der GDB-Befehl info functions <function name> ermöglichte eine Identifizierung MaybePrintStackTrace als Funktion, auf die ein Haltepunkt gesetzt werden kann. Hinrichtung von info functions ScopedReport::~ScopedReport gab eine andere Funktion: __ubsan::ScopedReport::~ScopedReport. Wenn keine dieser Funktionen verfügbar zu sein scheint (selbst wenn Debugging-Symbole installiert sind), können Sie es versuchen info functions ubsan oder info functions sanitizer um alle (UndefinedBehavior)Sanitizer-bezogenen Funktionen zu erhalten.

  • +1. Mir kommt in den Sinn, dass Ihre Frage einen Bedarf aufdeckt, und dieser Bedarf würde erfüllt, wenn die Desinfektionsmittel ein Force-no-inline, extern-Verknüpfung, void– Rückgabe einer leeren internen Funktion, die von einem Debugger mit einem Haltepunkt versehen werden könnte, und dass diese Funktion mit Argumenten aufgerufen wird, die für den Debugger nützlich sind, wenn er Wahnsinn meldet. Etwas ähnlich der JIT-Registrierungsschnittstelle.

    – Ich werde nicht existieren Ich werde nicht existieren

    23. Juli 2015 um 19:03 Uhr


  • Diese Antwort wäre nützlicher, wenn die Antwort auf die eigentliche Frage stärker hervorgehoben würde.

    – xaxxon

    9. März 2018 um 6:08 Uhr


  • @xaxxon Ich bin offen für Vorschläge, könnten Sie klären, welcher Teil verbessert werden muss?

    – Lekenstein

    9. März 2018 um 12:55 Uhr

  • Klare spezifische Schritte für das, was nicht mit einer Menge unzusammenhängender Dinge über Asan durchsetzt ist.

    – xaxxon

    9. März 2018 um 18:26 Uhr

  • @xaxxon Der erste Teil enthält alle Informationen, die Sie benötigen, um UBSAN-Berichte zu unterbrechen. Der zweite Teil ist nur informativ, Sie können ihn ignorieren, wenn Sie nicht verstehen müssen, wie die Dinge funktionieren. Sollte sich die interne Implementierung in Zukunft ändern, soll der zweite Teil dem Leser helfen, herauszufinden, wie er sich anpassen kann. ASAN und UBSan sind eng miteinander verwandt, daher hielt ich es für sinnvoll, beide zu vergleichen.

    – Lekenstein

    11. März 2018 um 15:41 Uhr

Iwillnotexist Benutzeravatar von Idonotexist
Ich werde nicht existieren Ich werde nicht existieren

Wie @Mark Plotnick betont, besteht der Weg dazu darin, einen Haltepunkt bei UBSan zu erreichen Handler.

UBSan hat eine Reihe von Handlern oder Einstiegspunkten für magische Funktionen, die für undefiniertes Verhalten aufgerufen werden. Der Compiler instrumentiert den Code, indem er gegebenenfalls Prüfungen einfügt; Wenn der Prüfcode UB erkennt, ruft er diese Handler auf. Sie alle beginnen mit __ubsan_handle_ und sind definiert in libsanitizer/ubsan/ubsan_handlers.h. Hier ist ein Link zur GCC-Kopie von ubsan_handlers.h.

Hier sind die relevanten Bits des UBSan-Headers (Haltepunkt auf einem davon):

#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 ist noch einfacher. Wenn du reinschaust libsanitizer/include/sanitizer/asan_interface.hdie Sie durchsuchen sollten hierkönnen Sie ein totes Zeichen eines Kommentars lesen:

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

Zahlreiche andere Funktionen in diesem Header sind ausdrücklich als öffentlich kommentiert, damit sie von einem Debugger aufgerufen werden können.

Ich rate Ihnen auf jeden Fall, andere Header von zu erkunden libsanitizer/include/sanitizer hier. Dort gibt es zahlreiche Leckereien.


Haltepunkte für UBSan und ASan können wie folgt hinzugefügt werden:

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

Dies wird auf den Handlern und einen Haltepunkt setzen finish direkt danach. Dadurch kann der Bericht gedruckt werden, aber der Debugger übernimmt direkt nach dem Drucken die Kontrolle.

  • Ich hatte gehofft, dass dafür ein einzelner Haltepunkt oder eine Umgebungsvariable existiert. Wie auch immer, können Sie ein Beispiel für die Verwendung dieser Funktion mit zeigen gdb? Es wäre toll, einen Ansatz zu haben (oder einfach gdbinit Makro), das sich wie ASAN verhält. Das heißt, die Nachricht anzeigen und unterbrechen.

    – Lekenstein

    16. Juli 2015 um 19:12 Uhr

  • @Lekensteyn Umgebungsvariablen können keine Haltepunkte einfügen; und damit eine einzelne haltepunktfähige Funktion existiert, müsste UBSan eine einzelne multiplexierende varargs-Funktion verwenden (was nicht unbedingt ein gutes Design ist und Sie die Fähigkeit verliert, nur bei einigen UB-Typen effizient einen Haltepunkt zu setzen). In Gedenken an gdb/gdbinitsicher, ich werde das in ein paar Stunden hinzufügen.

    – Ich werde nicht existieren Ich werde nicht existieren

    16. Juli 2015 um 20:44 Uhr

  • @Lekensteyn Scheint so (gdb) rbreak __asan_report_error und (gdb) rbreak ^__ubsan alle Funktionen, die mit diesen regulären Ausdrücken übereinstimmen, unverblümt brechen. Es gibt keine Funktion, die danach aufgerufen wird __asan_report_error zurückgibt, also scheint es mir, dass Sie irgendwie programmieren müssten gdb zu sofort (gdb) finish nachdem Sie einen dieser Haltepunkte erreicht haben.

    – Ich werde nicht existieren Ich werde nicht existieren

    16. Juli 2015 um 21:57 Uhr


  • Es gibt Env-Variablen wie z UBSAN_OPTIONS die von libsanitizer (nicht gdb oder dergleichen) unterstützt werden, diese Variablen, auf die ich mich mit “Umgebungsvariablen” beziehe. Was den Haltepunkt betrifft, habe ich es versucht rbreak ^__ubsan_handle_ commands finish endaber irgendwie verhält sich das bei einem ganz einfachen Testprogramm komisch (Funktion hängt noch in der __ubsan_handle_... Frame) während einer folgenden info breakpoints löst irgendwie den Druck aus und ändert den Rahmen. (GDB 7.9.1)

    – Lekenstein

    16. Juli 2015 um 23:55 Uhr

  • UBSan hat eine Prüfung auf unsigned-integer-overflow. Gibt es eine Möglichkeit zu sagen, dass ein bestimmter Ausdruck mit unsigned-integer-overflow in Ordnung ist? (Damit es vom Bericht ignoriert wird?).

    – gnzlbg

    26. Oktober 2015 um 17:23 Uhr

Ein Haltepunkt auf gesetzt __asan_report_error wird bei mir nicht getroffen und das Programm existiert nach dem Drucken der Diagnose einfach, ohne dass der Debugger auslöst. __asan::ReportGenericError vor dem Drucken der Diagnose und __sanitizer::Die Nach dem Drucken wird die Diagnose wie in beschrieben getroffen Das Asan-Wiki.

1400320cookie-checkWie kann ich UBSAN-Berichte in gdb unterbrechen und fortfahren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy