summaryrefslogtreecommitdiffstats
path: root/src/util/debug_backtrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/debug_backtrace.c')
-rw-r--r--src/util/debug_backtrace.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/util/debug_backtrace.c b/src/util/debug_backtrace.c
new file mode 100644
index 0000000..e376f81
--- /dev/null
+++ b/src/util/debug_backtrace.c
@@ -0,0 +1,331 @@
+/*
+ Copyright (C) 2021 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <libintl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "util/debug.h"
+
+extern FILE *_sss_debug_file;
+
+
+static const unsigned SSS_DEBUG_BACKTRACE_DEFAULT_SIZE = 100*1024; /* bytes */
+static const unsigned SSS_DEBUG_BACKTRACE_LEVEL = SSSDBG_BE_FO;
+
+/* Size of locations history to keep to avoid duplicating backtraces */
+#define SSS_DEBUG_BACKTRACE_LOCATIONS 5
+
+
+/* -->
+ * ring buffer = [*******t...\n............e000]
+ * where:
+ * "t" - 'tail', "e" - 'end'
+ * "......" - "old" part of buffer
+ * "******" - "new" part of buffer
+ * "000" - unoccupied space
+ */
+static struct {
+ bool enabled;
+ bool initialized;
+ int size;
+ char *buffer; /* buffer start */
+ char *end; /* end data border */
+ char *tail; /* tail of "current" message */
+
+ /* locations where last backtraces happened */
+ struct {
+ const char *file;
+ long line;
+ } locations[SSS_DEBUG_BACKTRACE_LOCATIONS];
+ unsigned last_location_idx;
+} _bt;
+
+
+static inline bool _all_levels_enabled(void);
+static inline bool _backtrace_is_enabled(int level);
+static inline bool _is_trigger_level(int level);
+static void _store_location(const char *file, long line);
+static bool _is_recent_location(const char *file, long line);
+static void _backtrace_vprintf(const char *format, va_list ap);
+static void _backtrace_printf(const char *format, ...);
+static void _backtrace_dump(void);
+static inline void _debug_vprintf(const char *format, va_list ap);
+static inline void _debug_fwrite(const char *ptr, const char *end);
+static inline void _debug_fflush(void);
+
+
+void sss_debug_backtrace_init(void)
+{
+ _bt.size = SSS_DEBUG_BACKTRACE_DEFAULT_SIZE;
+ _bt.buffer = (char *)malloc(_bt.size);
+ if (!_bt.buffer) {
+ ERROR("Failed to allocate debug backtrace buffer, feature is off\n");
+ return;
+ }
+
+ _bt.end = _bt.buffer;
+ _bt.tail = _bt.buffer;
+
+ _bt.enabled = true;
+ _bt.initialized = true;
+
+ /* locations[] & last_location_idx are zero-initialized */
+
+ _backtrace_printf(" * ");
+}
+
+
+void sss_debug_backtrace_enable(bool enable)
+{
+ _bt.enabled = enable;
+}
+
+
+void sss_debug_backtrace_vprintf(int level, const char *format, va_list ap)
+{
+ va_list ap_copy;
+
+ /* Potential optimization: only print to file here if backtrace is disabled,
+ * otherwise always print message to backtrace only and then copy message
+ * from backtrace to file in sss_debug_backtrace_endmsg().
+ * This saves va_copy and another round of format parsing inside printf but
+ * results in a little bit less readable output.
+ */
+ if (DEBUG_IS_SET(level)) {
+ va_copy(ap_copy, ap);
+ _debug_vprintf(format, ap_copy);
+ va_end(ap_copy);
+ }
+
+ if (_backtrace_is_enabled(level)) {
+ _backtrace_vprintf(format, ap);
+ }
+}
+
+
+void sss_debug_backtrace_printf(int level, const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ sss_debug_backtrace_vprintf(level, format, ap);
+ va_end(ap);
+}
+
+
+void sss_debug_backtrace_endmsg(const char *file, long line, int level)
+{
+ if (DEBUG_IS_SET(level)) {
+ _debug_fflush();
+ }
+
+ if (_backtrace_is_enabled(level)) {
+ if (_is_trigger_level(level)) {
+ if (!_is_recent_location(file, line)) {
+ _backtrace_dump();
+ _store_location(file, line);
+ } else {
+ fprintf(_sss_debug_file ? _sss_debug_file : stderr,
+ " * ... skipping repetitive backtrace ...\n");
+ /* and reset */
+ _bt.end = _bt.buffer;
+ _bt.tail = _bt.buffer;
+ }
+ }
+ _backtrace_printf(" * ");
+ }
+}
+
+
+
+
+/* ********** Helpers ********** */
+
+
+static inline void _debug_vprintf(const char *format, va_list ap)
+{
+ vfprintf(_sss_debug_file ? _sss_debug_file : stderr, format, ap);
+}
+
+
+static inline void _debug_fwrite(const char *begin, const char *end)
+{
+ if (end <= begin) {
+ return;
+ }
+ size_t size = (end - begin);
+ fwrite_unlocked(begin, size, 1, _sss_debug_file ? _sss_debug_file : stderr);
+}
+
+
+static inline void _debug_fflush(void)
+{
+ fflush(_sss_debug_file ? _sss_debug_file : stderr);
+}
+
+
+ /* does 'level' trigger backtrace dump? */
+static inline bool _is_trigger_level(int level)
+{
+ return ((level <= SSSDBG_OP_FAILURE) &&
+ (level <= debug_level));
+}
+
+
+/* checks if global 'debug_level' has all levels up to 9 enabled */
+static inline bool _all_levels_enabled(void)
+{
+ static const unsigned all_levels =
+ SSSDBG_FATAL_FAILURE|SSSDBG_CRIT_FAILURE|SSSDBG_OP_FAILURE|
+ SSSDBG_MINOR_FAILURE|SSSDBG_CONF_SETTINGS|SSSDBG_FUNC_DATA|
+ SSSDBG_TRACE_FUNC|SSSDBG_TRACE_LIBS|SSSDBG_TRACE_INTERNAL|
+ SSSDBG_TRACE_ALL|SSSDBG_BE_FO;
+
+ return ((debug_level & all_levels) == all_levels);
+}
+
+
+/* should message of this 'level' go to backtrace? */
+static inline bool _backtrace_is_enabled(int level)
+{
+ /* Store message in backtrace buffer if: */
+ return (_bt.initialized && /* backtrace is initialized */
+ _bt.enabled && /* backtrace is enabled */
+ sss_logger != STDERR_LOGGER &&
+ !_all_levels_enabled() && /* generic log doesn't cover everything */
+ level <= SSS_DEBUG_BACKTRACE_LEVEL); /* skip SSSDBG_TRACE_LDB */
+}
+
+
+static void _store_location(const char *file, long line)
+{
+ _bt.last_location_idx = (_bt.last_location_idx + 1) % SSS_DEBUG_BACKTRACE_LOCATIONS;
+ /* __FILE__ is a character string literal with static storage duration. */
+ _bt.locations[_bt.last_location_idx].file = file;
+ _bt.locations[_bt.last_location_idx].line = line;
+}
+
+
+static bool _is_recent_location(const char *file, long line)
+{
+ for (unsigned idx = 0; idx < SSS_DEBUG_BACKTRACE_LOCATIONS; ++idx) {
+ if ((line == _bt.locations[idx].line) &&
+ (_bt.locations[idx].file != NULL) &&
+ (strcmp(file, _bt.locations[idx].file) == 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/* prints to buffer */
+static void _backtrace_vprintf(const char *format, va_list ap)
+{
+ int buff_tail_size = _bt.size - (_bt.tail - _bt.buffer);
+ int written;
+
+ /* make sure there is at least 1kb available to avoid truncation;
+ * putting a sane limit on the size of single message (1kb in a worst case)
+ * makes logic simpler and avoids performance hit
+ */
+ if (buff_tail_size < 1024) {
+ /* let's wrap */
+ _bt.end = _bt.tail;
+ _bt.tail = _bt.buffer;
+ buff_tail_size = _bt.size;
+ }
+
+ written = vsnprintf(_bt.tail, buff_tail_size, format, ap);
+ if (written >= buff_tail_size) {
+ /* message is > 1kb, just discard */
+ return;
+ }
+
+ _bt.tail += written;
+ if (_bt.tail > _bt.end) {
+ _bt.end = _bt.tail;
+ }
+}
+
+
+static void _backtrace_printf(const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ _backtrace_vprintf(format, ap);
+ va_end(ap);
+}
+
+
+static bool _bt_empty(const char *begin, const char *end)
+{
+ int counter = 0;
+
+ while (begin < end) {
+ if (*begin == '\n') {
+ counter++;
+ if (counter == 2) {
+ /* there is least one line in addition to trigger msg */
+ return false;
+ }
+ }
+ begin++;
+ }
+
+ return true;
+}
+
+
+static void _backtrace_dump(void)
+{
+ const char *start = NULL;
+ static const char *start_marker =
+ "********************** PREVIOUS MESSAGE WAS TRIGGERED BY THE FOLLOWING BACKTRACE:\n";
+ static const char *end_marker =
+ "********************** BACKTRACE DUMP ENDS HERE *********************************\n\n";
+
+ if (_bt.end > _bt.tail) {
+ /* there is something in the "old" part, but don't start mid message */
+ start = _bt.tail + 1;
+ while ((start < _bt.end) && (*start != '\n')) start++;
+ if (start >= _bt.end) start = NULL;
+ }
+
+ if (!start) {
+ /* do we have anything to dump at all? */
+ if (_bt_empty(_bt.buffer, _bt.tail)) {
+ return;
+ }
+ }
+
+ fprintf(_sss_debug_file ? _sss_debug_file : stderr, "%s", start_marker);
+
+ if (start) {
+ _debug_fwrite(start + 1, _bt.end); /* dump "old" part of buffer */
+ }
+ _debug_fwrite(_bt.buffer, _bt.tail); /* dump "new" part of buffer */
+
+ fprintf(_sss_debug_file ? _sss_debug_file : stderr, "%s", end_marker);
+ _debug_fflush();
+
+ _bt.end = _bt.buffer;
+ _bt.tail = _bt.buffer;
+}