Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/winpr/libwinpr/utils/unwind/debug.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * WinPR Debugging helpers
4
 *
5
 * Copyright 2022 Armin Novak <armin.novak@thincast.com>
6
 * Copyright 2022 Thincast Technologies GmbH
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
#ifndef _GNU_SOURCE
22
#define _GNU_SOURCE // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
23
#endif
24
25
#include <assert.h>
26
#include <stdlib.h>
27
#include <unwind.h>
28
29
#include <winpr/string.h>
30
#include "debug.h"
31
32
#include <winpr/wlog.h>
33
#include "../log.h"
34
35
#include <dlfcn.h>
36
37
#define TAG WINPR_TAG("utils.unwind")
38
39
0
#define UNWIND_MAX_LINE_SIZE 1024ULL
40
41
typedef struct
42
{
43
  union
44
  {
45
    uintptr_t uw;
46
    void* pv;
47
  } pc;
48
  union
49
  {
50
    uintptr_t uptr;
51
    void* pv;
52
  } langSpecificData;
53
} unwind_info_t;
54
55
typedef struct
56
{
57
  size_t pos;
58
  size_t size;
59
  unwind_info_t* info;
60
} unwind_context_t;
61
62
static const char* unwind_reason_str(_Unwind_Reason_Code code)
63
0
{
64
0
  switch (code)
65
0
  {
66
#if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && !defined(__ARM_DWARF_EH__) && \
67
    !defined(__SEH__)
68
    case _URC_OK:
69
      return "_URC_OK";
70
#else
71
0
    case _URC_NO_REASON:
72
0
      return "_URC_NO_REASON";
73
0
    case _URC_FATAL_PHASE2_ERROR:
74
0
      return "_URC_FATAL_PHASE2_ERROR";
75
0
    case _URC_FATAL_PHASE1_ERROR:
76
0
      return "_URC_FATAL_PHASE1_ERROR";
77
0
    case _URC_NORMAL_STOP:
78
0
      return "_URC_NORMAL_STOP";
79
0
#endif
80
0
    case _URC_FOREIGN_EXCEPTION_CAUGHT:
81
0
      return "_URC_FOREIGN_EXCEPTION_CAUGHT";
82
0
    case _URC_END_OF_STACK:
83
0
      return "_URC_END_OF_STACK";
84
0
    case _URC_HANDLER_FOUND:
85
0
      return "_URC_HANDLER_FOUND";
86
0
    case _URC_INSTALL_CONTEXT:
87
0
      return "_URC_INSTALL_CONTEXT";
88
0
    case _URC_CONTINUE_UNWIND:
89
0
      return "_URC_CONTINUE_UNWIND";
90
#if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && !defined(__ARM_DWARF_EH__) && \
91
    !defined(__SEH__)
92
    case _URC_FAILURE:
93
      return "_URC_FAILURE";
94
#endif
95
0
    default:
96
0
      return "_URC_UNKNOWN";
97
0
  }
98
0
}
99
100
static const char* unwind_reason_str_buffer(_Unwind_Reason_Code code, char* buffer, size_t size)
101
0
{
102
0
  const char* str = unwind_reason_str(code);
103
0
  (void)_snprintf(buffer, size, "%s [0x%02x]", str, code);
104
0
  return buffer;
105
0
}
106
107
static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg)
108
0
{
109
0
  unwind_context_t* ctx = arg;
110
111
0
  assert(ctx);
112
113
0
  if (ctx->pos < ctx->size)
114
0
  {
115
0
    unwind_info_t* info = &ctx->info[ctx->pos++];
116
0
    info->pc.uw = _Unwind_GetIP(context);
117
118
    /* _Unwind_GetLanguageSpecificData has various return value definitions,
119
     * cast to the type we expect and disable linter warnings
120
     */
121
    // NOLINTNEXTLINE(google-readability-casting,readability-redundant-casting)
122
0
    info->langSpecificData.pv = (void*)_Unwind_GetLanguageSpecificData(context);
123
0
  }
124
125
0
  return _URC_NO_REASON;
126
0
}
127
128
void* winpr_unwind_backtrace(DWORD size)
129
0
{
130
0
  _Unwind_Reason_Code rc = _URC_FOREIGN_EXCEPTION_CAUGHT;
131
0
  unwind_context_t* ctx = calloc(1, sizeof(unwind_context_t));
132
0
  if (!ctx)
133
0
    goto fail;
134
0
  ctx->size = size;
135
0
  ctx->info = calloc(size, sizeof(unwind_info_t));
136
0
  if (!ctx->info)
137
0
    goto fail;
138
139
0
  rc = _Unwind_Backtrace(unwind_backtrace_callback, ctx);
140
0
  if (rc != _URC_END_OF_STACK)
141
0
  {
142
    /* https://github.com/FreeRDP/FreeRDP/issues/11490
143
     *
144
     * there seems to be no consensus on what to return from this function.
145
     * so we just warn about unexpected return codes and return the context regardless.
146
     */
147
0
    char buffer[64] = { 0 };
148
0
    WLog_WARN(TAG, "_Unwind_Backtrace failed with %s",
149
0
              unwind_reason_str_buffer(rc, buffer, sizeof(buffer)));
150
0
  }
151
152
0
  return ctx;
153
0
fail:
154
0
  winpr_unwind_backtrace_free(ctx);
155
0
  return NULL;
156
0
}
157
158
void winpr_unwind_backtrace_free(void* buffer)
159
0
{
160
0
  unwind_context_t* ctx = buffer;
161
0
  if (!ctx)
162
0
    return;
163
0
  free(ctx->info);
164
0
  free(ctx);
165
0
}
166
167
char** winpr_unwind_backtrace_symbols(void* buffer, size_t* used)
168
0
{
169
0
  union
170
0
  {
171
0
    void* pv;
172
0
    char* cp;
173
0
    char** cpp;
174
0
  } cnv;
175
0
  unwind_context_t* ctx = buffer;
176
0
  cnv.cpp = NULL;
177
178
0
  if (!ctx)
179
0
    return NULL;
180
181
0
  cnv.pv = calloc(ctx->pos * (sizeof(char*) + UNWIND_MAX_LINE_SIZE), sizeof(char*));
182
0
  if (!cnv.pv)
183
0
    return NULL;
184
185
0
  if (used)
186
0
    *used = ctx->pos;
187
188
0
  for (size_t x = 0; x < ctx->pos; x++)
189
0
  {
190
0
    char* msg = cnv.cp + ctx->pos * sizeof(char*) + x * UNWIND_MAX_LINE_SIZE;
191
0
    const unwind_info_t* info = &ctx->info[x];
192
0
    Dl_info dlinfo = { 0 };
193
0
    int rc = dladdr(info->pc.pv, &dlinfo);
194
195
0
    cnv.cpp[x] = msg;
196
197
0
    if (rc == 0)
198
0
      (void)_snprintf(msg, UNWIND_MAX_LINE_SIZE, "unresolvable, address=%p", info->pc.pv);
199
0
    else
200
0
      (void)_snprintf(msg, UNWIND_MAX_LINE_SIZE, "dli_fname=%s [%p], dli_sname=%s [%p]",
201
0
                      dlinfo.dli_fname, dlinfo.dli_fbase, dlinfo.dli_sname, dlinfo.dli_saddr);
202
0
  }
203
204
  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): function is an allocator
205
0
  return cnv.cpp;
206
0
}