libdrgn: add simple logging framework

Exceptions aren't enough to debug complicated code paths like debug info
discovery or stack unwinding. We really need logs for that, so let's add
a small logging framework. By default, we log to stderr, but we also
provide a way to direct logs to a different file, or even an arbitrary
callback so that logs can be directed to the application's logging
library of choice.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2023-07-14 21:23:00 -07:00
parent fa82071618
commit c1a2792e6a
7 changed files with 289 additions and 0 deletions

View File

@ -74,6 +74,8 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
linux_kernel.c \
linux_kernel.h \
linux_kernel_helpers.c \
log.c \
log.h \
memory_reader.c \
memory_reader.h \
minmax.h \

View File

@ -18,6 +18,7 @@
#include <assert.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
@ -237,6 +238,15 @@ struct drgn_error *drgn_error_format_fault(uint64_t address,
struct drgn_error *drgn_error_copy(struct drgn_error *src)
__attribute__((__returns_nonnull__));
/**
* Return a string representation of a @ref drgn_error.
*
* @param[in] err Error to write.
* @return Returned string, or @c NULL if memory could not be allocated. On
* success, must be freed with @c free().
*/
char *drgn_error_string(struct drgn_error *err);
/**
* Write a @ref drgn_error followed by a newline to a @c stdio stream.
*
@ -922,6 +932,91 @@ struct drgn_error *drgn_program_element_info(struct drgn_program *prog,
/** @} */
/**
* @defgroup Logging Logging
*
* Logging configuration.
*
* drgn can log to a file (@ref drgn_program_set_log_file()) or an arbitrary
* callback (@ref drgn_program_set_log_callback()). Messages can be filtered
* based on the log level (@ref drgn_program_set_log_level()).
*
* By default, the log file is set to `stderr` and the log level is @ref
* DRGN_LOG_NONE, so logging is disabled.
*
* @{
*/
/** Log levels. */
enum drgn_log_level {
DRGN_LOG_DEBUG = 0,
DRGN_LOG_INFO = 1,
DRGN_LOG_WARNING = 2,
DRGN_LOG_ERROR = 3,
DRGN_LOG_CRITICAL = 4,
};
/** Don't log anything. */
#define DRGN_LOG_NONE (DRGN_LOG_CRITICAL + 1)
/**
* Set the minimum log level.
*
* Messages below this level will not be logged.
*
* @param[in] level Minimum @ref drgn_log_level to log, or @ref DRGN_LOG_NONE to
* disable logging.
*/
void drgn_program_set_log_level(struct drgn_program *prog, int level);
/**
* Get the minimum log level.
*
* @return Minimum @ref drgn_log_level being logged, or @ref DRGN_LOG_NONE if
* logging is disabled.
*/
int drgn_program_get_log_level(struct drgn_program *prog);
/** Write logs to the given file. */
void drgn_program_set_log_file(struct drgn_program *prog, FILE *file);
/**
* Log callback.
*
* @param[in] prog Program message was logged to.
* @param[in] arg `callback_arg` passed to @ref drgn_program_set_log_callback().
* @param[in] level Message level.
* @param[in] format printf-style format of message.
* @param[in] ap Arguments for @p format.
* @param[in] err Error to append after formatted message if non-@c NULL. This
* can be formatted with @ref drgn_error_string(), @ref drgn_error_fwrite(), or
* @ref drgn_error_dwrite().
*/
typedef void drgn_log_fn(struct drgn_program *prog, void *arg,
enum drgn_log_level level, const char *format,
va_list ap, struct drgn_error *err);
/**
* Set a callback to log to.
*
* @param[in] callback Callback to call for each log message. This is only
* called if the message's level is at least the current log level.
* @param[in] callback_arg Argument to pass to callback.
*/
void drgn_program_set_log_callback(struct drgn_program *prog,
drgn_log_fn *callback, void *callback_arg);
/**
* Get the current log callback.
*
* @param[out] callback_ret Returned callback.
* @param[out] callback_arg_ret Returned callback argument.
*/
void drgn_program_get_log_callback(struct drgn_program *prog,
drgn_log_fn **callback_ret,
void **callback_arg_ret);
/** @} */
/**
* @defgroup Embedding Embedding
*

View File

@ -225,6 +225,16 @@ bool string_builder_append_error(struct string_builder *sb,
#undef emit_error_format
}
LIBDRGN_PUBLIC char *drgn_error_string(struct drgn_error *err)
{
char *tmp;
#define emit_error_format(...) (asprintf(&tmp, __VA_ARGS__) < 0 ? NULL : tmp)
#define emit_error_string(s) strdup(s)
return emit_error(err);
#undef emit_error_string
#undef emit_error_format
}
LIBDRGN_PUBLIC int drgn_error_fwrite(FILE *file, struct drgn_error *err)
{
#define emit_error_format(format, ...) fprintf(file, format "\n", __VA_ARGS__)

84
libdrgn/log.c Normal file
View File

@ -0,0 +1,84 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "log.h"
#include "program.h"
#include "util.h"
LIBDRGN_PUBLIC void drgn_program_set_log_level(struct drgn_program *prog,
int level)
{
prog->log_level = level;
}
LIBDRGN_PUBLIC int drgn_program_get_log_level(struct drgn_program *prog)
{
return prog->log_level;
}
static void drgn_file_log_fn(struct drgn_program *prog, void *arg,
enum drgn_log_level level, const char *format,
va_list ap, struct drgn_error *err)
{
FILE *file = arg;
flockfile(file);
static const char * const prefix[] = {
[DRGN_LOG_DEBUG] = "debug: ",
[DRGN_LOG_INFO] = "info: ",
[DRGN_LOG_WARNING] = "warning: ",
[DRGN_LOG_ERROR] = "error: ",
[DRGN_LOG_CRITICAL] = "critical: ",
};
fputs(prefix[level], file);
vfprintf(file, format, ap);
if (err)
drgn_error_fwrite(file, err);
else
putc('\n', file);
funlockfile(file);
}
LIBDRGN_PUBLIC void drgn_program_set_log_file(struct drgn_program *prog,
FILE *file)
{
drgn_program_set_log_callback(prog, drgn_file_log_fn, file);
}
LIBDRGN_PUBLIC void drgn_program_set_log_callback(struct drgn_program *prog,
drgn_log_fn *callback,
void *callback_arg)
{
prog->log_fn = callback;
prog->log_arg = callback_arg;
}
LIBDRGN_PUBLIC void drgn_program_get_log_callback(struct drgn_program *prog,
drgn_log_fn **callback_ret,
void **callback_arg_ret)
{
*callback_ret = prog->log_fn;
*callback_arg_ret = prog->log_arg;
}
bool drgn_log_is_enabled(struct drgn_program *prog, enum drgn_log_level level)
{
return level >= prog->log_level;
}
void drgn_error_log(enum drgn_log_level level, struct drgn_program *prog,
struct drgn_error *err, const char *format, ...)
{
if (!drgn_log_is_enabled(prog, level))
return;
va_list ap;
va_start(ap, format);
prog->log_fn(prog, prog->log_arg, level, format, ap, err);
va_end(ap);
}

89
libdrgn/log.h Normal file
View File

@ -0,0 +1,89 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* @file
*
* Logging.
*
* See @ref LoggingInternals.
*/
#ifndef DRGN_LOG_H
#define DRGN_LOG_H
#include "drgn.h"
/**
* @ingroup Internals
*
* @defgroup LoggingInternals Logging
*
* Logging functions.
*
* @{
*/
/**
* Return whether the given log level is enabled.
*
* This can be used to avoid expensive computations that are only needed for
* logging.
*/
bool drgn_log_is_enabled(struct drgn_program *prog, enum drgn_log_level level);
/**
* @{
*
* @name Logging Functions
*/
#ifdef DOXYGEN
/** Log a printf-style message at the given level. */
void drgn_log(enum drgn_log_level level, struct drgn_program *prog,
const char *format, ...);
#else
#define drgn_log(level, prog, ...) drgn_error_log(level, prog, NULL, __VA_ARGS__)
#endif
/** Log a critical message. */
#define drgn_log_critical(...) drgn_log(DRGN_LOG_CRITICAL, __VA_ARGS__)
/** Log an error message. */
#define drgn_log_error(...) drgn_log(DRGN_LOG_ERROR, __VA_ARGS__)
/** Log a warning message. */
#define drgn_log_warning(...) drgn_log(DRGN_LOG_WARNING, __VA_ARGS__)
/** Log an informational message. */
#define drgn_log_info(...) drgn_log(DRGN_LOG_INFO, __VA_ARGS__)
/** Log a debug message. */
#define drgn_log_debug(...) drgn_log(DRGN_LOG_DEBUG, __VA_ARGS__)
/**
* @}
*
* @{
*
* @name Error Logging Functions
*/
/**
* Log a printf-style message followed by a @ref drgn_error at the given level.
*/
__attribute__((__format__(__printf__, 4, 5)))
void drgn_error_log(enum drgn_log_level level, struct drgn_program *prog,
struct drgn_error *err, const char *format, ...);
/** Log a critical message followed by a @ref drgn_error. */
#define drgn_error_log_critical(...) drgn_error_log(DRGN_LOG_CRIT, __VA_ARGS__)
/** Log an error message followed by a @ref drgn_error. */
#define drgn_error_log_error(...) drgn_error_log(DRGN_LOG_ERR, __VA_ARGS__)
/** Log a warning message followed by a @ref drgn_error. */
#define drgn_error_log_warning(...) drgn_error_log(DRGN_LOG_WARNING, __VA_ARGS__)
/** Log an informational message followed by a @ref drgn_error. */
#define drgn_error_log_info(...) drgn_error_log(DRGN_LOG_INFO, __VA_ARGS__)
/** Log a debug message followed by a @ref drgn_error. */
#define drgn_error_log_debug(...) drgn_error_log(DRGN_LOG_DEBUG, __VA_ARGS__)
/**
* @}
* @}
*/
#endif /* DRGN_LOG_H */

View File

@ -104,6 +104,8 @@ void drgn_program_init(struct drgn_program *prog,
drgn_program_set_platform(prog, platform);
char *env = getenv("DRGN_PREFER_ORC_UNWINDER");
prog->prefer_orc_unwinder = env && atoi(env);
drgn_program_set_log_level(prog, DRGN_LOG_NONE);
drgn_program_set_log_file(prog, stderr);
drgn_object_init(&prog->vmemmap, prog);
}

View File

@ -186,6 +186,13 @@ struct drgn_program {
/* Whether @ref drgn_program::direct_mapping_offset has been cached. */
bool direct_mapping_offset_cached;
/*
* Logging.
*/
drgn_log_fn *log_fn;
void *log_arg;
enum drgn_log_level log_level;
/*
* Blocking callbacks.
*/