libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
log.c
1/*
2 * CDDL HEADER START
3 *
4 * This file and its contents are supplied under the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may only use this file in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source. A copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14 */
15/*
16 * Copyright 2017 Saso Kiselkov. All rights reserved.
17 */
18
19#include <stdlib.h>
20#include <string.h>
21#include <stdio.h>
22#include <time.h>
23
24#if IBM
25#include <windows.h>
26#include <dbghelp.h>
27#include <psapi.h>
28#else
29#include <execinfo.h> /* used for stack tracing */
30#endif /* !IBM */
31
32#include <XPLMUtilities.h>
33
34#include <acfutils/assert.h>
35#include <acfutils/helpers.h>
36#include <acfutils/log.h>
37#include <acfutils/thread.h>
38
39#define DATE_FMT "%Y-%m-%d %H:%M:%S"
40#define PREFIX_FMT "%s %s[%s:%d]: ", timedate, log_prefix, filename, line
41
42#define MAX_STACK_FRAMES 128
43#define MAX_MODULES 1024
44#define BACKTRACE_STR "Backtrace is:\n"
45#if defined(__GNUC__) || defined(__clang__)
46#define BACKTRACE_STRLEN __builtin_strlen(BACKTRACE_STR)
47#else /* !__GNUC__ && !__clang__ */
48#define BACKTRACE_STRLEN strlen(BACKTRACE_STR)
49#endif /* !__GNUC__ && !__clang__ */
50
51#if IBM
52/*
53 * Since while dumping stack we are most likely in a fairly compromised
54 * state, we statically pre-allocate these buffers to try and avoid having
55 * to call into the VM subsystem.
56 */
57#define MAX_SYM_NAME_LEN 4096
58#define MAX_BACKTRACE_LEN (64 * 1024)
59static char backtrace_buf[MAX_BACKTRACE_LEN] = { 0 };
60static char symbol_buf[sizeof (SYMBOL_INFO) +
61 MAX_SYM_NAME_LEN * sizeof (TCHAR)];
62static char line_buf[sizeof (IMAGEHLP_LINE64)];
63/* DbgHelp is not thread-safe, so avoid concurrency */
64static mutex_t backtrace_lock;
65
66static HMODULE modules[MAX_MODULES];
67static MODULEINFO mi[MAX_MODULES];
68static DWORD num_modules;
69
70#define SYMNAME_MAXLEN 4095 /* C++ symbols can be HUUUUGE */
71#endif /* IBM */
72
73static logfunc_t log_func = NULL;
74static char *log_prefix = NULL;
75
96void
97log_init(logfunc_t func, const char *prefix)
98{
99 /* Can't use VERIFY here, since it uses this logging interface. */
100 if (func == NULL || prefix == NULL)
101 abort();
102 log_func = func;
103 log_prefix = safe_strdup(prefix);
104#if IBM
105 mutex_init(&backtrace_lock);
106#endif
107}
108
114void
116{
117 free(log_prefix);
118 log_prefix = NULL;
119#if IBM
120 mutex_destroy(&backtrace_lock);
121#endif
122}
123
129logfunc_t
131{
132 return (log_func);
133}
134
139void
140log_impl(const char *filename, int line, const char *fmt, ...)
141{
142 va_list ap;
143 va_start(ap, fmt);
144 log_impl_v(filename, line, fmt, ap);
145 va_end(ap);
146}
147
152void
153log_impl_v(const char *filename, int line, const char *fmt, va_list ap)
154{
155 va_list ap_copy;
156 char timedate[32];
157 char *buf;
158 size_t prefix_len, len;
159 struct tm *tm;
160 time_t t;
161
162 t = time(NULL);
163 tm = localtime(&t);
164 VERIFY(strftime(timedate, sizeof (timedate), DATE_FMT, tm) != 0);
165
166 /* Can't use VERIFY here, since it uses this logging interface. */
167 if (log_func == NULL || log_prefix == NULL)
168 abort();
169
170 prefix_len = snprintf(NULL, 0, PREFIX_FMT);
171 va_copy(ap_copy, ap);
172 len = vsnprintf(NULL, 0, fmt, ap_copy);
173
174 buf = (char *)malloc(prefix_len + len + 2);
175
176 (void) snprintf(buf, prefix_len + 1, PREFIX_FMT);
177 (void) vsnprintf(&buf[prefix_len], len + 1, fmt, ap);
178 (void) sprintf(&buf[strlen(buf)], "\n");
179 log_func(buf);
180
181 free(buf);
182}
183
223#if IBM
224
225/*
226 * Given a module path in `filename' and a relative module address in `addr',
227 * attempts to resolve the symbol name and relative symbol address. This is
228 * done by looking for a syms.txt file in the same directory as the module's
229 * filename.
230 * If found, the symbol name + symbol relative address is placed into
231 * `symname' in the "symbol+offset" format.
232 * This function is deliberately designed to be simple and use as little
233 * memory as possible, because when called from an exception handler, the
234 * process' memory state can be assumed to be quite broken already.
235 */
236static void
237find_symbol(const char *filename, void *addr, char *symname,
238 size_t symname_cap)
239{
240 /*
241 * Note the `static' here is deliberate to cause these to become
242 * BSS-allocated variables instead of stack-allocated. When parsing
243 * through a stack trace we are in a pretty precarious state, so we
244 * can't rely on having much stack space available.
245 */
246 static char symstxtname[MAX_PATH];
247 static char prevsym[SYMNAME_MAXLEN + 1];
248 static const char *sep;
249 static FILE *fp;
250 static void *prevptr = NULL;
251
252 *symname = 0;
253 *prevsym = 0;
254
255 sep = strrchr(filename, DIRSEP);
256 if (sep == NULL)
257 return;
258 lacf_strlcpy(symstxtname, filename, MIN((uintptr_t)(sep - filename) + 1,
259 sizeof (symstxtname)));
260 strncat(symstxtname, DIRSEP_S "syms.txt", sizeof (symstxtname));
261 fp = fopen(symstxtname, "rb");
262 if (fp == NULL)
263 return;
264
265 while (!feof(fp)) {
266 static char unused_c;
267 static void *ptr;
268 static char sym[SYMNAME_MAXLEN + 1];
269
270 if (fscanf(fp, "%p %c %" SCANF_STR_AUTOLEN(SYMNAME_MAXLEN) "s",
271 &ptr, &unused_c, sym) != 3) {
272 /*
273 * This might fail if we hit a MASSIVE symbol name
274 * which is longer than SYMNAME_MAXLEN. In that case,
275 * we want to skip until the next newline.
276 */
277 int c;
278 do {
279 c = fgetc(fp);
280 } while (c != '\n' && c != '\r' && c != EOF);
281 if (c != EOF) {
282 continue;
283 } else {
284 break;
285 }
286 }
287 if (addr >= prevptr && addr < ptr) {
288 snprintf(symname, symname_cap, "%s+%x", prevsym,
289 (unsigned)(addr - prevptr));
290 break;
291 }
292 prevptr = ptr;
293 lacf_strlcpy(prevsym, sym, sizeof (prevsym));
294 }
295 fclose(fp);
296}
297
298static HMODULE
299find_module(LPVOID pc, DWORD64 *module_base)
300{
301 static DWORD i;
302 for (i = 0; i < num_modules; i++) {
303 static LPVOID start, end;
304 start = mi[i].lpBaseOfDll;
305 end = start + mi[i].SizeOfImage;
306 if (start <= pc && end > pc) {
307 *module_base = (DWORD64)start;
308 return (modules[i]);
309 }
310 }
311 *module_base = 0;
312 return (NULL);
313}
314
315static void
316gather_module_info(void)
317{
318 HANDLE process = GetCurrentProcess();
319
320 EnumProcessModules(process, modules, sizeof (HMODULE) * MAX_MODULES,
321 &num_modules);
322 num_modules = MIN(num_modules, MAX_MODULES);
323 for (DWORD i = 0; i < num_modules; i++)
324 GetModuleInformation(process, modules[i], &mi[i], sizeof (*mi));
325}
326
327void
328log_backtrace(int skip_frames)
329{
330 static unsigned frames;
331 static void *stack[MAX_STACK_FRAMES];
332 static SYMBOL_INFO *symbol;
333 static HANDLE process;
334 static DWORD displacement;
335 static IMAGEHLP_LINE64 *line;
336 static char filename[MAX_PATH];
337
338 mutex_enter(&backtrace_lock);
339
340 frames = RtlCaptureStackBackTrace(skip_frames + 1, MAX_STACK_FRAMES,
341 stack, NULL);
342
343 process = GetCurrentProcess();
344
345 SymInitialize(process, NULL, TRUE);
346 SymSetOptions(SYMOPT_LOAD_LINES);
347
348 gather_module_info();
349
350 memset(symbol_buf, 0, sizeof (symbol_buf));
351 memset(line_buf, 0, sizeof (line_buf));
352
353 symbol = (SYMBOL_INFO *)symbol_buf;
354 symbol->MaxNameLen = MAX_SYM_NAME_LEN - 1;
355 symbol->SizeOfStruct = sizeof (SYMBOL_INFO);
356
357 line = (IMAGEHLP_LINE64 *)line_buf;
358 line->SizeOfStruct = sizeof (*line);
359
360 backtrace_buf[0] = '\0';
361 lacf_strlcpy(backtrace_buf, BACKTRACE_STR, sizeof (backtrace_buf));
362
363 for (unsigned frame_nr = 0; frame_nr < frames; frame_nr++) {
364 static DWORD64 address;
365 static int fill;
366
367 address = (DWORD64)(uintptr_t)stack[frame_nr];
368 fill = strlen(backtrace_buf);
369
370 memset(symbol_buf, 0, sizeof (symbol_buf));
371 /*
372 * Try to grab the symbol name from the stored %rip data.
373 */
374 if (!SymFromAddr(process, address, 0, symbol)) {
375 static DWORD64 start;
376 static HMODULE module;
377
378 module = find_module((void *)address, &start);
379 if (module != NULL) {
380 static char symname[SYMNAME_MAXLEN + 1];
381
382 GetModuleFileNameA(module, filename,
383 sizeof (filename));
384 find_symbol(filename, stack[frame_nr] - start,
385 symname, sizeof (symname));
386 fill += snprintf(&backtrace_buf[fill],
387 sizeof (backtrace_buf) - fill,
388 "%d %p %s+%p (%s)\n", frame_nr,
389 stack[frame_nr], filename,
390 stack[frame_nr] - start, symname);
391 } else {
392 fill += snprintf(&backtrace_buf[fill],
393 sizeof (backtrace_buf) - fill,
394 "%d %p <unknown module>\n", frame_nr,
395 stack[frame_nr]);
396 }
397 continue;
398 }
399 /*
400 * See if we have debug info available with file names and
401 * line numbers.
402 */
403 if (SymGetLineFromAddr64(process, address, &displacement,
404 line)) {
405 snprintf(&backtrace_buf[fill], sizeof (backtrace_buf) -
406 fill, "%d: %s (0x%lx) [%s:%d]\n", frame_nr,
407 symbol->Name, (unsigned long)symbol->Address,
408 line->FileName, (int)line->LineNumber);
409 } else {
410 snprintf(&backtrace_buf[fill], sizeof (backtrace_buf) -
411 fill, "%d: %s - 0x%lx\n", frame_nr, symbol->Name,
412 (unsigned long)symbol->Address);
413 }
414 }
415
416 if (log_func == NULL)
417 abort();
418 log_func(backtrace_buf);
419 fputs(backtrace_buf, stderr);
420 fflush(stderr);
421 SymCleanup(process);
422
423 mutex_exit(&backtrace_lock);
424}
425
426void
427log_backtrace_sw64(PCONTEXT ctx)
428{
429 static char filename[MAX_PATH];
430 static DWORD64 pcs[MAX_STACK_FRAMES];
431 static unsigned num_stack_frames;
432 static STACKFRAME64 sf;
433 static HANDLE process, thread;
434 static DWORD machine;
435
436 mutex_enter(&backtrace_lock);
437
438 process = GetCurrentProcess();
439 thread = GetCurrentThread();
440
441 SymInitialize(process, NULL, TRUE);
442 SymSetOptions(SYMOPT_LOAD_LINES);
443
444 gather_module_info();
445
446 memset(&sf, 0, sizeof (sf));
447 sf.AddrPC.Mode = AddrModeFlat;
448 sf.AddrStack.Mode = AddrModeFlat;
449 sf.AddrFrame.Mode = AddrModeFlat;
450#if defined(_M_IX86)
451 machine = IMAGE_FILE_MACHINE_I386;
452 sf.AddrPC.Offset = ctx->Eip;
453 sf.AddrStack.Offset = ctx->Esp;
454 sf.AddrFrame.Offset = ctx->Ebp;
455#elif defined(_M_X64)
456 machine = IMAGE_FILE_MACHINE_AMD64;
457 sf.AddrPC.Offset = ctx->Rip;
458 sf.AddrStack.Offset = ctx->Rsp;
459 sf.AddrFrame.Offset = ctx->Rbp;
460#elif defined(_M_IA64)
461 machine = IMAGE_FILE_MACHINE_IA64;
462 sf.AddrPC.Offset = ctx->StIIP;
463 sf.AddrFrame.Offset = ctx->IntSp;
464 sf.AddrBStore.Offset = ctx->RsBSP;
465 sf.AddrBStore.Mode = AddrModeFlat;
466 sf.AddrStack.Offset = ctx->IntSp;
467#else
468#error "Unsupported architecture"
469#endif /* _M_X64 */
470
471 for (num_stack_frames = 0; num_stack_frames < MAX_STACK_FRAMES;
472 num_stack_frames++) {
473 if (!StackWalk64(machine, process, thread, &sf, ctx, NULL,
474 SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
475 break;
476 }
477 pcs[num_stack_frames] = sf.AddrPC.Offset;
478 }
479
480 backtrace_buf[0] = '\0';
481 lacf_strlcpy(backtrace_buf, BACKTRACE_STR, sizeof (backtrace_buf));
482
483 for (unsigned i = 0; i < num_stack_frames; i++) {
484 static int fill;
485 static DWORD64 pc;
486 static char symname[SYMNAME_MAXLEN + 1];
487 static HMODULE module;
488 static DWORD64 mbase;
489
490 fill = strlen(backtrace_buf);
491 pc = pcs[i];
492
493 module = find_module((LPVOID)pc, &mbase);
494 GetModuleFileNameA(module, filename, sizeof (filename));
495 find_symbol(filename, (void *)(pc - mbase),
496 symname, sizeof (symname));
497 fill += snprintf(&backtrace_buf[fill],
498 sizeof (backtrace_buf) - fill,
499 "%d %p %s+%p (%s)\n", i, (void *)pc, filename,
500 (void *)(pc - mbase), symname);
501 }
502
503 if (log_func == NULL)
504 abort();
505 log_func(backtrace_buf);
506 fputs(backtrace_buf, stderr);
507 fflush(stderr);
508 SymCleanup(process);
509
510 mutex_exit(&backtrace_lock);
511}
512
513#else /* !IBM */
514
515void
516log_backtrace(int skip_frames)
517{
518 static char *msg;
519 static size_t msg_len;
520 static void *trace[MAX_STACK_FRAMES];
521 static size_t i, j, sz;
522 static char **fnames;
523
524 sz = backtrace(trace, MAX_STACK_FRAMES);
525 fnames = backtrace_symbols(trace, sz);
526
527 for (i = 1 + skip_frames, msg_len = BACKTRACE_STRLEN; i < sz; i++)
528 msg_len += snprintf(NULL, 0, "%s\n", fnames[i]);
529
530 msg = (char *)malloc(msg_len + 1);
531 strcpy(msg, BACKTRACE_STR);
532 for (i = 1 + skip_frames, j = BACKTRACE_STRLEN; i < sz; i++)
533 j += sprintf(&msg[j], "%s\n", fnames[i]);
534
535 if (log_func == NULL)
536 abort();
537 log_func(msg);
538 fputs(msg, stderr);
539
540 free(msg);
541 free(fnames);
542}
543
544#endif /* !IBM */
#define VERIFY(x)
Definition assert.h:78
void lacf_strlcpy(char *dest, const char *src, size_t cap)
Definition helpers.c:1667
void log_impl_v(const char *filename, int line, const char *fmt, va_list ap)
Definition log.c:153
void log_impl(const char *filename, int line, const char *fmt,...)
Definition log.c:140
void log_fini(void)
Definition log.c:115
void log_init(logfunc_t func, const char *prefix)
Definition log.c:97
void log_backtrace(int skip_frames)
Definition log.c:516
logfunc_t log_get_logfunc(void)
Definition log.c:130
static char * safe_strdup(const char *str2)
Definition safe_alloc.h:201
static void mutex_destroy(mutex_t *mtx)
Definition thread.h:499
static void mutex_enter(mutex_t *mtx)
Definition thread.h:530
static void mutex_exit(mutex_t *mtx)
Definition thread.h:556
static void mutex_init(mutex_t *mtx)
Definition thread.h:488