/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 | } |