diff options
Diffstat (limited to 'src/logging.c')
| -rw-r--r-- | src/logging.c | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..cdaa6bb --- /dev/null +++ b/src/logging.c @@ -0,0 +1,227 @@ +/* + * Tuxánci 2 - A first person shooter + * Copyright (C) 2007-2010 Tuxánci Development Team + * Copyright (C) 2025-2026 Connor Thomson + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#include "ta_sdl.h" + +#ifdef UNIX_LIKE +#define UNW_LOCAL_ONLY +#include <libunwind.h> +#include <elfutils/libdwfl.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <time.h> +#include <sys/time.h> + +#define LOG_TIMESTAMP_SIZE 30 + +static Dwfl *ta_dwfl_open_self(void) { + static char *debuginfo_path; + static const Dwfl_Callbacks callbacks = { + .find_elf = dwfl_linux_proc_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + }; + + Dwfl *dwfl = dwfl_begin(&callbacks); + if (dwfl == NULL) { + return NULL; + } + + if (dwfl_linux_proc_report(dwfl, getpid()) != 0) { + dwfl_end(dwfl); + return NULL; + } + + if (dwfl_report_end(dwfl, NULL, NULL) != 0) { + dwfl_end(dwfl); + return NULL; + } + + return dwfl; +} + +static char *ta_get_proc_name(unw_cursor_t *cursor) { + char *buf = NULL; + size_t cap = 0; + unw_word_t proc_offset = 0; + + while (1) { + size_t grow_to = (cap == 0) ? 64 : (cap * 2); + char *grown = realloc(buf, grow_to); + if (grown == NULL) { + free(buf); + return NULL; + } + buf = grown; + cap = grow_to; + + int ret = unw_get_proc_name(cursor, buf, cap, &proc_offset); + if (ret == 0) { + return buf; + } + if (ret != -UNW_ENOMEM) { + free(buf); + return NULL; + } + } +} + +static void ta_get_timestamp(char *buf, size_t buf_size) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + struct tm tm_info; + localtime_r(&ts.tv_sec, &tm_info); + + size_t len = strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", &tm_info); + snprintf(buf + len, buf_size - len, ".%03ld", ts.tv_nsec / 1000000); +} + +static char *ta_format_message(const char *format, va_list args) { + va_list args_copy; + va_copy(args_copy, args); + int needed = vsnprintf(NULL, 0, format, args_copy); + va_end(args_copy); + + if (needed < 0) { + return NULL; + } + + char *message = malloc((size_t)needed + 1); + if (message == NULL) { + return NULL; + } + + vsnprintf(message, (size_t)needed + 1, format, args); + return message; +} + +void ta_log(const char *format, ...) { + unw_cursor_t cursor; + unw_context_t context; + int code; + + code = unw_getcontext(&context); + if (code != 0) { + SDL_Log("unw_getcontext failed with code %d\n", code); + return; + } + + code = unw_init_local(&cursor, &context); + if (code != 0) { + SDL_Log("unw_init_local failed with code %d\n", code); + return; + } + + code = unw_step(&cursor); + if (code <= 0) { + SDL_Log("unw_step failed with code %d\n", code); + return; + } + + unw_word_t ip = 0; + if (unw_get_reg(&cursor, UNW_REG_IP, &ip) != 0 || ip == 0) { + SDL_Log("unw_get_reg failed to resolve caller IP\n"); + return; + } + + const char *file = NULL; + int src_line = 0; + const char *func_name = NULL; + char *proc_name_buf = NULL; + + Dwfl *dwfl = ta_dwfl_open_self(); + if (dwfl != NULL) { + Dwfl_Module *mod = dwfl_addrmodule(dwfl, (Dwarf_Addr)ip); + if (mod != NULL) { + func_name = dwfl_module_addrname(mod, (GElf_Addr)ip); + + Dwfl_Line *dwfl_line = dwfl_module_getsrc(mod, (Dwarf_Addr)ip); + if (dwfl_line != NULL) { + Dwarf_Addr src_addr; + file = dwfl_lineinfo(dwfl_line, &src_addr, &src_line, NULL, NULL, NULL); + } + } + } + + if (func_name == NULL) { + proc_name_buf = ta_get_proc_name(&cursor); + func_name = proc_name_buf; + } + + va_list args; + va_start(args, format); + char *message = ta_format_message(format, args); + va_end(args); + + const char *out_message = (message != NULL) ? message : format; + + /* + * PATCH: + * Remove leading '/' + * I cannot find a way to make it not do it with the compiler. + * This uses extra cpu cycles and makes the binary larger then it has to be. + * Make a script to trim it from the DWARF info. + */ + if (file != NULL) { + const char *basename = strrchr(file, '/'); + if (basename != NULL) { + file = basename + 1; + } + } + + char timestamp[LOG_TIMESTAMP_SIZE]; + ta_get_timestamp(timestamp, sizeof(timestamp)); + + if (file != NULL && src_line > 0 && func_name != NULL) { + printf("[%s] %s:%d:%s(): %s\n", timestamp, file, src_line, func_name, out_message); + } else if (func_name != NULL) { + printf("[%s] %s(): %s\n", timestamp, func_name, out_message); + } else { + printf("[%s] 0x%lx: %s\n", timestamp, (unsigned long)ip, out_message); + } + + free(message); + free(proc_name_buf); + + if (dwfl != NULL) { + dwfl_end(dwfl); + } +} + +#else // UNIX_LIKE + +void ta_log(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + SDL_LogMessageV( + SDL_LOG_CATEGORY_APPLICATION, + SDL_LOG_PRIORITY_INFO, + fmt, + args + ); + + va_end(args); +} + +#endif // UNIX_LIKE
\ No newline at end of file |