/* * 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 . */ #include "ta_sdl.h" #ifdef UNIX_LIKE #define UNW_LOCAL_ONLY #include #include #include #include #include #include #include #include #include #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