LCOV - code coverage report
Current view: top level - src - log.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 63 87 72.4 %
Date: 2025-03-29 09:04:10 Functions: 8 11 72.7 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 22 54 40.7 %

           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

Generated by: LCOV version 1.16