Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <stdio.h>
19 : : #include <locale.h>
20 : :
21 : : #include "common.h"
22 : : #include "log.h"
23 : : #include "pl_thread.h"
24 : :
25 : : struct priv {
26 : : pl_mutex lock;
27 : : enum pl_log_level log_level_cap;
28 : : pl_str logbuffer;
29 : : };
30 : :
31 : 11 : pl_log pl_log_create(int api_ver, const struct pl_log_params *params)
32 : : {
33 : : (void) api_ver;
34 : 11 : struct pl_log_t *log = pl_zalloc_obj(NULL, log, struct priv);
35 : 11 : struct priv *p = PL_PRIV(log);
36 [ - + ]: 11 : log->params = *PL_DEF(params, &pl_log_default_params);
37 [ - + ]: 11 : pl_mutex_init(&p->lock);
38 : 11 : pl_info(log, "Initialized libplacebo %s (API v%d)", PL_VERSION, PL_API_VER);
39 : 11 : return log;
40 : : }
41 : :
42 : : const struct pl_log_params pl_log_default_params = {0};
43 : :
44 : 11 : void pl_log_destroy(pl_log *plog)
45 : : {
46 : 11 : pl_log log = *plog;
47 [ + - ]: 11 : if (!log)
48 : : return;
49 : :
50 : 11 : struct priv *p = PL_PRIV(log);
51 : 11 : pl_mutex_destroy(&p->lock);
52 : 11 : pl_free((void *) log);
53 : 11 : *plog = NULL;
54 : : }
55 : :
56 : 1 : struct pl_log_params pl_log_update(pl_log ptr, const struct pl_log_params *params)
57 : : {
58 : : struct pl_log_t *log = (struct pl_log_t *) ptr;
59 [ - + ]: 1 : if (!log)
60 : 0 : return pl_log_default_params;
61 : :
62 : 1 : struct priv *p = PL_PRIV(log);
63 : 1 : pl_mutex_lock(&p->lock);
64 : 1 : struct pl_log_params prev_params = log->params;
65 [ + - ]: 1 : log->params = *PL_DEF(params, &pl_log_default_params);
66 : 1 : pl_mutex_unlock(&p->lock);
67 : :
68 : 1 : return prev_params;
69 : : }
70 : :
71 : 5 : enum pl_log_level pl_log_level_update(pl_log ptr, enum pl_log_level level)
72 : : {
73 : : struct pl_log_t *log = (struct pl_log_t *) ptr;
74 [ + - ]: 5 : if (!log)
75 : : return PL_LOG_NONE;
76 : :
77 : 5 : struct priv *p = PL_PRIV(log);
78 : 5 : pl_mutex_lock(&p->lock);
79 : 5 : enum pl_log_level prev_level = log->params.log_level;
80 : 5 : log->params.log_level = level;
81 : 5 : pl_mutex_unlock(&p->lock);
82 : :
83 : 5 : return prev_level;
84 : : }
85 : :
86 : 724 : void pl_log_level_cap(pl_log log, enum pl_log_level cap)
87 : : {
88 [ + - ]: 724 : if (!log)
89 : : return;
90 : :
91 : 724 : struct priv *p = PL_PRIV(log);
92 : 724 : pl_mutex_lock(&p->lock);
93 : 724 : p->log_level_cap = cap;
94 : 724 : pl_mutex_unlock(&p->lock);
95 : : }
96 : :
97 : : static FILE *default_stream(void *stream, enum pl_log_level level)
98 : : {
99 [ # # # # ]: 0 : return PL_DEF(stream, level <= PL_LOG_WARN ? stderr : stdout);
100 : : }
101 : :
102 [ # # ]: 0 : void pl_log_simple(void *stream, enum pl_log_level level, const char *msg)
103 : : {
104 : : static const char *prefix[] = {
105 : : [PL_LOG_FATAL] = "fatal",
106 : : [PL_LOG_ERR] = "error",
107 : : [PL_LOG_WARN] = "warn",
108 : : [PL_LOG_INFO] = "info",
109 : : [PL_LOG_DEBUG] = "debug",
110 : : [PL_LOG_TRACE] = "trace",
111 : : };
112 : :
113 : : FILE *h = default_stream(stream, level);
114 : 0 : fprintf(h, "%5s: %s\n", prefix[level], msg);
115 [ # # ]: 0 : if (level <= PL_LOG_WARN)
116 : 0 : fflush(h);
117 : 0 : }
118 : :
119 [ # # ]: 0 : void pl_log_color(void *stream, enum pl_log_level level, const char *msg)
120 : : {
121 : : static const char *color[] = {
122 : : [PL_LOG_FATAL] = "31;1", // bright red
123 : : [PL_LOG_ERR] = "31", // red
124 : : [PL_LOG_WARN] = "33", // yellow/orange
125 : : [PL_LOG_INFO] = "32", // green
126 : : [PL_LOG_DEBUG] = "34", // blue
127 : : [PL_LOG_TRACE] = "30;1", // bright black
128 : : };
129 : :
130 : : FILE *h = default_stream(stream, level);
131 : 0 : fprintf(h, "\033[%sm%s\033[0m\n", color[level], msg);
132 [ # # ]: 0 : if (level <= PL_LOG_WARN)
133 : 0 : fflush(h);
134 : 0 : }
135 : :
136 [ + + ]: 97228 : static void pl_msg_va(pl_log log, enum pl_log_level lev,
137 : : const char *fmt, va_list va)
138 : : {
139 : : // Test log message without taking the lock, to avoid thrashing the
140 : : // lock for thousands of trace messages unless those are actually
141 : : // enabled. This may be a false negative, in which case log messages may
142 : : // be lost as a result. But this shouldn't be a big deal, since any
143 : : // situation leading to lost log messages would itself be a race condition.
144 [ + + ]: 97228 : if (!pl_msg_test(log, lev))
145 : : return;
146 : :
147 : : // Re-test the log message level with held lock to avoid false positives,
148 : : // which would be a considerably bigger deal than false negatives
149 : 71583 : struct priv *p = PL_PRIV(log);
150 : 71583 : pl_mutex_lock(&p->lock);
151 : :
152 : : // Apply this cap before re-testing the log level, to avoid giving users
153 : : // messages that should have been dropped by the log level.
154 [ + - ]: 71583 : lev = PL_MAX(lev, p->log_level_cap);
155 [ + + ]: 71583 : if (!pl_msg_test(log, lev))
156 : 56 : goto done;
157 : :
158 : 71527 : p->logbuffer.len = 0;
159 : 71527 : pl_str_append_vasprintf((void *) log, &p->logbuffer, fmt, va);
160 : 71527 : log->params.log_cb(log->params.log_priv, lev, (char *) p->logbuffer.buf);
161 : :
162 : 71583 : done:
163 : 71583 : pl_mutex_unlock(&p->lock);
164 : : }
165 : :
166 : 97228 : void pl_msg(pl_log log, enum pl_log_level lev, const char *fmt, ...)
167 : : {
168 : : va_list va;
169 : 97228 : va_start(va, fmt);
170 : 97228 : pl_msg_va(log, lev, fmt, va);
171 : 97228 : va_end(va);
172 : 97228 : }
173 : :
174 [ + - ]: 528 : void pl_msg_source(pl_log log, enum pl_log_level lev, const char *src)
175 : : {
176 [ + + + - ]: 528 : if (!pl_msg_test(log, lev) || !src)
177 : : return;
178 : :
179 : : int line = 1;
180 [ + + ]: 63553 : while (*src) {
181 : 63547 : const char *end = strchr(src, '\n');
182 [ + + ]: 63547 : if (!end) {
183 : 518 : pl_msg(log, lev, "[%3d] %s", line, src);
184 : 518 : break;
185 : : }
186 : :
187 : 63029 : pl_msg(log, lev, "[%3d] %.*s", line, (int)(end - src), src);
188 : 63029 : src = end + 1;
189 : 63029 : line++;
190 : : }
191 : : }
192 : :
193 : : #ifdef PL_HAVE_DBGHELP
194 : :
195 : : #include <windows.h>
196 : : #include <dbghelp.h>
197 : : #include <shlwapi.h>
198 : :
199 : : // https://github.com/llvm/llvm-project/blob/f03cd763384bbb67ddfa12957859ed58841d4b34/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h#L85-L106
200 : : static inline uintptr_t get_prev_inst_pc(uintptr_t pc) {
201 : : #if defined(__arm__)
202 : : // T32 (Thumb) branch instructions might be 16 or 32 bit long,
203 : : // so we return (pc-2) in that case in order to be safe.
204 : : // For A32 mode we return (pc-4) because all instructions are 32 bit long.
205 : : return (pc - 3) & (~1);
206 : : #elif defined(__x86_64__) || defined(__i386__)
207 : : return pc - 1;
208 : : #else
209 : : return pc - 4;
210 : : #endif
211 : : }
212 : :
213 : : static DWORD64 get_preferred_base(const char *module)
214 : : {
215 : : DWORD64 image_base = 0;
216 : : HANDLE file_mapping = NULL;
217 : : HANDLE file_view = NULL;
218 : :
219 : : HANDLE file = CreateFile(module, GENERIC_READ, FILE_SHARE_READ, NULL,
220 : : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
221 : : if (file == INVALID_HANDLE_VALUE)
222 : : goto done;
223 : :
224 : : file_mapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
225 : : if (file_mapping == NULL)
226 : : goto done;
227 : :
228 : : file_view = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, 0);
229 : : if (file_view == NULL)
230 : : goto done;
231 : :
232 : : PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER) file_view;
233 : : if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
234 : : goto done;
235 : :
236 : : PIMAGE_NT_HEADERS pe_header = (PIMAGE_NT_HEADERS) ((char *) file_view +
237 : : dos_header->e_lfanew);
238 : : if (pe_header->Signature != IMAGE_NT_SIGNATURE)
239 : : goto done;
240 : :
241 : : if (pe_header->FileHeader.SizeOfOptionalHeader != sizeof(pe_header->OptionalHeader))
242 : : goto done;
243 : :
244 : : image_base = pe_header->OptionalHeader.ImageBase;
245 : :
246 : : done:
247 : : if (file_view)
248 : : UnmapViewOfFile(file_view);
249 : : if (file_mapping)
250 : : CloseHandle(file_mapping);
251 : : if (file != INVALID_HANDLE_VALUE)
252 : : CloseHandle(file);
253 : :
254 : : return image_base;
255 : : }
256 : :
257 : : void pl_log_stack_trace(pl_log log, enum pl_log_level lev)
258 : : {
259 : : if (!pl_msg_test(log, lev))
260 : : return;
261 : :
262 : : void *tmp = pl_tmp(NULL);
263 : : PL_ARRAY(void *) frames = {0};
264 : :
265 : : size_t capacity = 16;
266 : : do {
267 : : capacity *= 2;
268 : : PL_ARRAY_RESIZE(tmp, frames, capacity);
269 : : // Skip first frame, we don't care about this function
270 : : frames.num = CaptureStackBackTrace(1, capacity, frames.elem, NULL);
271 : : } while (capacity == frames.num);
272 : :
273 : : if (!frames.num) {
274 : : pl_free(tmp);
275 : : return;
276 : : }
277 : :
278 : : // Load dbghelp on demand. While it is available on all Windows versions,
279 : : // no need to keep it loaded all the time as stack trace printing function,
280 : : // in theory should be used repetitively rarely.
281 : : HANDLE process = GetCurrentProcess();
282 : : HMODULE dbghelp = LoadLibrary("dbghelp.dll");
283 : : DWORD options;
284 : : SYMBOL_INFO *symbol = NULL;
285 : : BOOL use_dbghelp = !!dbghelp;
286 : :
287 : : #define DBGHELP_SYM(sym) \
288 : : __typeof__(&sym) p##sym = (__typeof__(&sym))(void *) GetProcAddress(dbghelp, #sym); \
289 : : use_dbghelp &= !!p##sym
290 : :
291 : : DBGHELP_SYM(SymCleanup);
292 : : DBGHELP_SYM(SymFromAddr);
293 : : DBGHELP_SYM(SymGetLineFromAddr64);
294 : : DBGHELP_SYM(SymGetModuleInfo64);
295 : : DBGHELP_SYM(SymGetOptions);
296 : : DBGHELP_SYM(SymGetSearchPathW);
297 : : DBGHELP_SYM(SymInitialize);
298 : : DBGHELP_SYM(SymSetOptions);
299 : : DBGHELP_SYM(SymSetSearchPathW);
300 : :
301 : : #undef DBGHELP_SYM
302 : :
303 : : struct priv *p = PL_PRIV(log);
304 : : PL_ARRAY(wchar_t) base_search = { .num = 1024 };
305 : :
306 : : if (use_dbghelp) {
307 : : // DbgHelp is not thread-safe. Note that on Windows mutex is recursive,
308 : : // so no need to unlock before calling pl_msg.
309 : : pl_mutex_lock(&p->lock);
310 : :
311 : : options = pSymGetOptions();
312 : : pSymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS |
313 : : SYMOPT_LOAD_LINES | SYMOPT_FAVOR_COMPRESSED);
314 : : use_dbghelp &= pSymInitialize(process, NULL, TRUE);
315 : :
316 : : if (use_dbghelp) {
317 : : symbol = pl_alloc(tmp, sizeof(SYMBOL_INFO) + 512);
318 : : symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
319 : : symbol->MaxNameLen = 512;
320 : :
321 : : PL_ARRAY_RESIZE(tmp, base_search, base_search.num);
322 : : BOOL ret = pSymGetSearchPathW(process, base_search.elem,
323 : : base_search.num);
324 : : base_search.num = ret ? wcslen(base_search.elem) : 0;
325 : : PL_ARRAY_APPEND(tmp, base_search, L'\0');
326 : : } else {
327 : : pSymSetOptions(options);
328 : : pl_mutex_unlock(&p->lock);
329 : : }
330 : : }
331 : :
332 : : pl_msg(log, lev, " Backtrace:");
333 : : for (int n = 0; n < frames.num; n++) {
334 : : uintptr_t pc = get_prev_inst_pc((uintptr_t) frames.elem[n]);
335 : : pl_str out = {0};
336 : : pl_str_append_asprintf(tmp, &out, " #%-2d 0x%"PRIxPTR, n, pc);
337 : :
338 : : MEMORY_BASIC_INFORMATION meminfo = {0};
339 : : char module_path[MAX_PATH] = {0};
340 : : if (VirtualQuery((LPCVOID) pc, &meminfo, sizeof(meminfo))) {
341 : : DWORD sz = GetModuleFileNameA(meminfo.AllocationBase, module_path,
342 : : sizeof(module_path));
343 : : if (sz == sizeof(module_path))
344 : : pl_msg(log, PL_LOG_ERR, "module path truncated");
345 : :
346 : : if (use_dbghelp) {
347 : : // According to documentation it should search in "The directory
348 : : // that contains the corresponding module.", but it doesn't appear
349 : : // to work, so manually set the path to module path.
350 : : // https://learn.microsoft.com/windows/win32/debug/symbol-paths
351 : : PL_ARRAY(wchar_t) mod_search = { .num = MAX_PATH };
352 : : PL_ARRAY_RESIZE(tmp, mod_search, mod_search.num);
353 : :
354 : : sz = GetModuleFileNameW(meminfo.AllocationBase,
355 : : mod_search.elem, mod_search.num);
356 : :
357 : : if (sz > 0 && sz != MAX_PATH &&
358 : : // TODO: Replace with PathCchRemoveFileSpec once mingw-w64
359 : : // >= 8.0.1 is commonly available, at the time of writing
360 : : // there are a few high profile Linux distributions that ship
361 : : // 8.0.0.
362 : : PathRemoveFileSpecW(mod_search.elem))
363 : : {
364 : : mod_search.num = wcslen(mod_search.elem);
365 : : PL_ARRAY_APPEND(tmp, mod_search, L';');
366 : : PL_ARRAY_CONCAT(tmp, mod_search, base_search);
367 : : pSymSetSearchPathW(process, mod_search.elem);
368 : : }
369 : : }
370 : : }
371 : :
372 : : DWORD64 sym_displacement;
373 : : if (use_dbghelp && pSymFromAddr(process, pc, &sym_displacement, symbol))
374 : : pl_str_append_asprintf(tmp, &out, " in %s+0x%llx",
375 : : symbol->Name, sym_displacement);
376 : :
377 : : DWORD line_displacement;
378 : : IMAGEHLP_LINE64 line = {sizeof(line)};
379 : : if (use_dbghelp &&
380 : : pSymGetLineFromAddr64(process, pc, &line_displacement, &line))
381 : : {
382 : : pl_str_append_asprintf(tmp, &out, " %s:%lu+0x%lx", line.FileName,
383 : : line.LineNumber, line_displacement);
384 : : goto done;
385 : : }
386 : :
387 : : // LLVM tools by convention use absolute addresses with "prefered" base
388 : : // image offset. We need to read this offset from binary, because due to
389 : : // ASLR we are not loaded at this base. While Windows tools like WinDbg
390 : : // expect relative offset to image base. So to be able to easily use it
391 : : // with both worlds, print both values.
392 : : DWORD64 module_base = get_preferred_base(module_path);
393 : : pl_str_append_asprintf(tmp, &out, " (%s+0x%"PRIxPTR") (0x%llx)", module_path,
394 : : pc - (uintptr_t) meminfo.AllocationBase,
395 : : module_base + (pc - (uintptr_t) meminfo.AllocationBase));
396 : :
397 : : done:
398 : : pl_msg(log, lev, "%s", out.buf);
399 : : }
400 : :
401 : : if (use_dbghelp) {
402 : : pSymSetOptions(options);
403 : : pSymCleanup(process);
404 : : pl_mutex_unlock(&p->lock);
405 : : }
406 : : // Unload dbghelp. Maybe it is better to keep it loaded?
407 : : if (dbghelp)
408 : : FreeLibrary(dbghelp);
409 : : pl_free(tmp);
410 : : }
411 : :
412 : : #elif defined(PL_HAVE_UNWIND)
413 : : #define UNW_LOCAL_ONLY
414 : : #include <libunwind.h>
415 : : #include <dlfcn.h>
416 : :
417 : : void pl_log_stack_trace(pl_log log, enum pl_log_level lev)
418 : : {
419 : : if (!pl_msg_test(log, lev))
420 : : return;
421 : :
422 : : unw_cursor_t cursor;
423 : : unw_context_t uc;
424 : : unw_word_t ip, off;
425 : : unw_getcontext(&uc);
426 : : unw_init_local(&cursor, &uc);
427 : :
428 : : int depth = 0;
429 : : pl_msg(log, lev, " Backtrace:");
430 : : while (unw_step(&cursor) > 0) {
431 : : char symbol[256] = "<unknown>";
432 : : Dl_info info = {
433 : : .dli_fname = "<unknown>",
434 : : };
435 : :
436 : : unw_get_reg(&cursor, UNW_REG_IP, &ip);
437 : : unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off);
438 : : dladdr((void *) (uintptr_t) ip, &info);
439 : : pl_msg(log, lev, " #%-2d 0x%016" PRIxPTR " in %s+0x%" PRIxPTR" at %s+0x%" PRIxPTR,
440 : : depth++, ip, symbol, off, info.dli_fname, ip - (uintptr_t) info.dli_fbase);
441 : : }
442 : : }
443 : :
444 : : #elif defined(PL_HAVE_EXECINFO)
445 : : #include <execinfo.h>
446 : :
447 [ # # ]: 0 : void pl_log_stack_trace(pl_log log, enum pl_log_level lev)
448 : : {
449 [ # # ]: 0 : if (!pl_msg_test(log, lev))
450 : : return;
451 : :
452 : : PL_ARRAY(void *) buf = {0};
453 : : size_t buf_avail = 16;
454 : : do {
455 : 0 : buf_avail *= 2;
456 [ # # ]: 0 : PL_ARRAY_RESIZE(NULL, buf, buf_avail);
457 : 0 : buf.num = backtrace(buf.elem, buf_avail);
458 [ # # ]: 0 : } while (buf.num == buf_avail);
459 : :
460 : 0 : pl_msg(log, lev, " Backtrace:");
461 : 0 : char **strings = backtrace_symbols(buf.elem, buf.num);
462 [ # # ]: 0 : for (int i = 1; i < buf.num; i++)
463 : 0 : pl_msg(log, lev, " #%-2d %s", i - 1, strings[i]);
464 : :
465 : 0 : free(strings);
466 : 0 : pl_free(buf.elem);
467 : : }
468 : :
469 : : #else
470 : : void pl_log_stack_trace(pl_log log, enum pl_log_level lev) { }
471 : : #endif
|