1/*
2 * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16#include <aws/common/system_info.h>
17
18#include <aws/common/byte_buf.h>
19#include <aws/common/logging.h>
20
21#if defined(__FreeBSD__) || defined(__NetBSD__)
22# define __BSD_VISIBLE 1
23#endif
24
25#include <unistd.h>
26
27#if defined(HAVE_SYSCONF)
28size_t aws_system_info_processor_count(void) {
29 long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
30 if (AWS_LIKELY(nprocs >= 0)) {
31 return (size_t)nprocs;
32 }
33
34 AWS_FATAL_POSTCONDITION(nprocs >= 0);
35 return 0;
36}
37#else
38size_t aws_system_info_processor_count(void) {
39# if defined(AWS_NUM_CPU_CORES)
40 AWS_FATAL_PRECONDITION(AWS_NUM_CPU_CORES > 0);
41 return AWS_NUM_CPU_CORES;
42# else
43 return 1;
44# endif
45}
46#endif
47
48#include <ctype.h>
49#include <fcntl.h>
50
51bool aws_is_debugger_present(void) {
52 /* Open the status file */
53 const int status_fd = open("/proc/self/status", O_RDONLY);
54 if (status_fd == -1) {
55 return false;
56 }
57
58 /* Read its contents */
59 char buf[4096];
60 const ssize_t num_read = read(status_fd, buf, sizeof(buf) - 1);
61 close(status_fd);
62 if (num_read <= 0) {
63 return false;
64 }
65 buf[num_read] = '\0';
66
67 /* Search for the TracerPid field, which will indicate the debugger process */
68 const char tracerPidString[] = "TracerPid:";
69 const char *tracer_pid = strstr(buf, tracerPidString);
70 if (!tracer_pid) {
71 return false;
72 }
73
74 /* If it's not 0, then there's a debugger */
75 for (const char *cur = tracer_pid + sizeof(tracerPidString) - 1; cur <= buf + num_read; ++cur) {
76 if (!isspace(*cur)) {
77 return isdigit(*cur) != 0 && *cur != '0';
78 }
79 }
80
81 return false;
82}
83
84#include <signal.h>
85
86#ifndef __has_builtin
87# define __has_builtin(x) 0
88#endif
89
90void aws_debug_break(void) {
91#ifdef DEBUG_BUILD
92 if (aws_is_debugger_present()) {
93# if __has_builtin(__builtin_debugtrap)
94 __builtin_debugtrap();
95# else
96 raise(SIGTRAP);
97# endif
98 }
99#endif /* DEBUG_BUILD */
100}
101
102#if defined(AWS_HAVE_EXECINFO)
103# include <execinfo.h>
104# include <limits.h>
105
106# define AWS_BACKTRACE_DEPTH 128
107
108struct aws_stack_frame_info {
109 char exe[PATH_MAX];
110 char addr[32];
111 char base[32]; /* base addr for dylib/exe */
112 char function[128];
113};
114
115/* Ensure only safe characters in a path buffer in case someone tries to
116 rename the exe and trigger shell execution via the sub commands used to
117 resolve symbols */
118char *s_whitelist_chars(char *path) {
119 char *cur = path;
120 while (*cur) {
121 bool whitelisted =
122 isalnum(*cur) || isspace(*cur) || *cur == '/' || *cur == '_' || *cur == '.' || (cur > path && *cur == '-');
123 if (!whitelisted) {
124 *cur = '_';
125 }
126 ++cur;
127 }
128 return path;
129}
130
131# if defined(__APPLE__)
132# include <ctype.h>
133# include <dlfcn.h>
134# include <mach-o/dyld.h>
135static char s_exe_path[PATH_MAX];
136const char *s_get_executable_path() {
137 static const char *s_exe = NULL;
138 if (AWS_LIKELY(s_exe)) {
139 return s_exe;
140 }
141 uint32_t len = sizeof(s_exe_path);
142 if (!_NSGetExecutablePath(s_exe_path, &len)) {
143 s_exe = s_exe_path;
144 }
145 return s_exe;
146}
147int s_parse_symbol(const char *symbol, void *addr, struct aws_stack_frame_info *frame) {
148 /* symbols look like: <frame_idx> <exe-or-shared-lib> <addr> <function> + <offset>
149 */
150 const char *current_exe = s_get_executable_path();
151 /* parse exe/shared lib */
152 const char *exe_start = strstr(symbol, " ");
153 while (isspace(*exe_start)) {
154 ++exe_start;
155 }
156 const char *exe_end = strstr(exe_start, " ");
157 strncpy(frame->exe, exe_start, exe_end - exe_start);
158 /* executables get basename'd, so restore the path */
159 if (strstr(current_exe, frame->exe)) {
160 strncpy(frame->exe, current_exe, strlen(current_exe));
161 }
162 s_whitelist_chars(frame->exe);
163
164 /* parse addr */
165 const char *addr_start = strstr(exe_end, "0x");
166 const char *addr_end = strstr(addr_start, " ");
167 strncpy(frame->addr, addr_start, addr_end - addr_start);
168
169 /* parse function */
170 const char *function_start = strstr(addr_end, " ") + 1;
171 const char *function_end = strstr(function_start, " ");
172 /* truncate function name if needed */
173 size_t function_len = function_end - function_start;
174 if (function_len >= (sizeof(frame->function) - 1)) {
175 function_len = sizeof(frame->function) - 1;
176 }
177 strncpy(frame->function, function_start, function_end - function_start);
178
179 /* find base addr for library/exe */
180 Dl_info addr_info;
181 dladdr(addr, &addr_info);
182 snprintf(frame->base, sizeof(frame->base), "0x%p", addr_info.dli_fbase);
183
184 return AWS_OP_SUCCESS;
185}
186
187void s_resolve_cmd(char *cmd, size_t len, struct aws_stack_frame_info *frame) {
188 snprintf(cmd, len, "atos -o %s -l %s %s", frame->exe, frame->base, frame->addr);
189}
190# else
191int s_parse_symbol(const char *symbol, void *addr, struct aws_stack_frame_info *frame) {
192 /* symbols look like: <exe-or-shared-lib>(<function>+<addr>) [0x<addr>]
193 * or: <exe-or-shared-lib> [0x<addr>]
194 * or: [0x<addr>]
195 */
196 (void)addr;
197 const char *open_paren = strstr(symbol, "(");
198 const char *close_paren = strstr(symbol, ")");
199 const char *exe_end = open_paren;
200 /* there may not be a function in parens, or parens at all */
201 if (open_paren == NULL || close_paren == NULL) {
202 exe_end = strstr(symbol, "[");
203 if (!exe_end) {
204 return AWS_OP_ERR;
205 }
206 /* if exe_end == symbol, there's no exe */
207 if (exe_end != symbol) {
208 exe_end -= 1;
209 }
210 }
211
212 ptrdiff_t exe_len = exe_end - symbol;
213 if (exe_len > 0) {
214 strncpy(frame->exe, symbol, exe_len);
215 }
216 s_whitelist_chars(frame->exe);
217
218 long function_len = (open_paren && close_paren) ? close_paren - open_paren - 1 : 0;
219 if (function_len > 0) { /* dynamic symbol was found */
220 /* there might be (<function>+<addr>) or just (<function>) */
221 const char *function_start = open_paren + 1;
222 const char *plus = strstr(function_start, "+");
223 const char *function_end = (plus) ? plus : close_paren;
224 if (function_end > function_start) {
225 function_len = function_end - function_start;
226 strncpy(frame->function, function_start, function_len);
227 } else if (plus) {
228 long addr_len = close_paren - plus - 1;
229 strncpy(frame->addr, plus + 1, addr_len);
230 }
231 }
232 if (frame->addr[0] == 0) {
233 /* use the address in []'s, since it's all we have */
234 const char *addr_start = strstr(exe_end, "[") + 1;
235 char *addr_end = strstr(addr_start, "]");
236 if (!addr_end) {
237 return AWS_OP_ERR;
238 }
239 strncpy(frame->addr, addr_start, addr_end - addr_start);
240 }
241
242 return AWS_OP_SUCCESS;
243}
244void s_resolve_cmd(char *cmd, size_t len, struct aws_stack_frame_info *frame) {
245 snprintf(cmd, len, "addr2line -afips -e %s %s", frame->exe, frame->addr);
246}
247# endif
248
249size_t aws_backtrace(void **frames, size_t num_frames) {
250 return backtrace(frames, num_frames);
251}
252
253char **aws_backtrace_symbols(void *const *frames, size_t stack_depth) {
254 return backtrace_symbols(frames, stack_depth);
255}
256
257char **aws_backtrace_addr2line(void *const *stack_frames, size_t stack_depth) {
258 char **symbols = aws_backtrace_symbols(stack_frames, stack_depth);
259 AWS_FATAL_ASSERT(symbols);
260 struct aws_byte_buf lines;
261 aws_byte_buf_init(&lines, aws_default_allocator(), stack_depth * 256);
262
263 /* insert pointers for each stack entry */
264 memset(lines.buffer, 0, stack_depth * sizeof(void *));
265 lines.len += stack_depth * sizeof(void *);
266
267 /* symbols look like: <exe-or-shared-lib>(<function>+<addr>) [0x<addr>]
268 * or: <exe-or-shared-lib> [0x<addr>]
269 * start at 1 to skip the current frame (this function) */
270 for (size_t frame_idx = 0; frame_idx < stack_depth; ++frame_idx) {
271 struct aws_stack_frame_info frame;
272 AWS_ZERO_STRUCT(frame);
273 const char *symbol = symbols[frame_idx];
274 if (s_parse_symbol(symbol, stack_frames[frame_idx], &frame)) {
275 goto parse_failed;
276 }
277
278 /* TODO: Emulate libunwind */
279 char cmd[sizeof(struct aws_stack_frame_info)] = {0};
280 s_resolve_cmd(cmd, sizeof(cmd), &frame);
281 FILE *out = popen(cmd, "r");
282 if (!out) {
283 goto parse_failed;
284 }
285 char output[1024];
286 if (fgets(output, sizeof(output), out)) {
287 /* if addr2line or atos don't know what to do with an address, they just echo it */
288 /* if there are spaces in the output, then they resolved something */
289 if (strstr(output, " ")) {
290 symbol = output;
291 }
292 }
293 pclose(out);
294
295 parse_failed:
296 /* record the pointer to where the symbol will be */
297 *((char **)&lines.buffer[frame_idx * sizeof(void *)]) = (char *)lines.buffer + lines.len;
298 struct aws_byte_cursor line_cursor = aws_byte_cursor_from_c_str(symbol);
299 line_cursor.len += 1; /* strings must be null terminated, make sure we copy the null */
300 aws_byte_buf_append_dynamic(&lines, &line_cursor);
301 }
302 free(symbols);
303 return (char **)lines.buffer; /* caller is responsible for freeing */
304}
305
306void aws_backtrace_print(FILE *fp, void *call_site_data) {
307 siginfo_t *siginfo = call_site_data;
308 if (siginfo) {
309 fprintf(fp, "Signal received: %d, errno: %d\n", siginfo->si_signo, siginfo->si_errno);
310 if (siginfo->si_signo == SIGSEGV) {
311 fprintf(fp, " SIGSEGV @ 0x%p\n", siginfo->si_addr);
312 }
313 }
314
315 void *stack_frames[AWS_BACKTRACE_DEPTH];
316 size_t stack_depth = aws_backtrace(stack_frames, AWS_BACKTRACE_DEPTH);
317 char **symbols = aws_backtrace_symbols(stack_frames, stack_depth);
318 if (symbols == NULL) {
319 fprintf(fp, "Unable to decode backtrace via backtrace_symbols\n");
320 return;
321 }
322
323 fprintf(fp, "################################################################################\n");
324 fprintf(fp, "Resolved stacktrace:\n");
325 fprintf(fp, "################################################################################\n");
326 /* symbols look like: <exe-or-shared-lib>(<function>+<addr>) [0x<addr>]
327 * or: <exe-or-shared-lib> [0x<addr>]
328 * or: [0x<addr>]
329 * start at 1 to skip the current frame (this function) */
330 for (size_t frame_idx = 1; frame_idx < stack_depth; ++frame_idx) {
331 struct aws_stack_frame_info frame;
332 AWS_ZERO_STRUCT(frame);
333 const char *symbol = symbols[frame_idx];
334 if (s_parse_symbol(symbol, stack_frames[frame_idx], &frame)) {
335 goto parse_failed;
336 }
337
338 /* TODO: Emulate libunwind */
339 char cmd[sizeof(struct aws_stack_frame_info)] = {0};
340 s_resolve_cmd(cmd, sizeof(cmd), &frame);
341 FILE *out = popen(cmd, "r");
342 if (!out) {
343 goto parse_failed;
344 }
345 char output[1024];
346 if (fgets(output, sizeof(output), out)) {
347 /* if addr2line or atos don't know what to do with an address, they just echo it */
348 /* if there are spaces in the output, then they resolved something */
349 if (strstr(output, " ")) {
350 symbol = output;
351 }
352 }
353 pclose(out);
354
355 parse_failed:
356 fprintf(fp, "%s%s", symbol, (symbol == symbols[frame_idx]) ? "\n" : "");
357 }
358
359 fprintf(fp, "################################################################################\n");
360 fprintf(fp, "Raw stacktrace:\n");
361 fprintf(fp, "################################################################################\n");
362 for (size_t frame_idx = 1; frame_idx < stack_depth; ++frame_idx) {
363 const char *symbol = symbols[frame_idx];
364 fprintf(fp, "%s\n", symbol);
365 }
366 fflush(fp);
367
368 free(symbols);
369}
370
371#else
372void aws_backtrace_print(FILE *fp, void *call_site_data) {
373 (void)call_site_data;
374 fprintf(fp, "No call stack information available\n");
375}
376
377size_t aws_backtrace(void **frames, size_t size) {
378 (void)frames;
379 (void)size;
380 return 0;
381}
382
383char **aws_backtrace_symbols(void *const *frames, size_t stack_depth) {
384 (void)frames;
385 (void)stack_depth;
386 return NULL;
387}
388
389char **aws_backtrace_addr2line(void *const *stack_frames, size_t stack_depth) {
390 (void)stack_frames;
391 (void)stack_depth;
392 return NULL;
393}
394#endif /* AWS_HAVE_EXECINFO */
395
396void aws_backtrace_log() {
397 void *stack[1024];
398 size_t num_frames = aws_backtrace(stack, 1024);
399 if (!num_frames) {
400 return;
401 }
402 char **symbols = aws_backtrace_addr2line(stack, num_frames);
403 for (size_t line = 0; line < num_frames; ++line) {
404 const char *symbol = symbols[line];
405 AWS_LOGF_TRACE(AWS_LS_COMMON_GENERAL, "%s", symbol);
406 }
407 free(symbols);
408}
409