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) |
28 | size_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 |
38 | size_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 | |
51 | bool 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 | |
90 | void 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 | |
108 | struct 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 */ |
118 | char *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> |
135 | static char s_exe_path[PATH_MAX]; |
136 | const 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 | } |
147 | int 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 | |
187 | void 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 |
191 | int 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 | } |
244 | void 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 | |
249 | size_t aws_backtrace(void **frames, size_t num_frames) { |
250 | return backtrace(frames, num_frames); |
251 | } |
252 | |
253 | char **aws_backtrace_symbols(void *const *frames, size_t stack_depth) { |
254 | return backtrace_symbols(frames, stack_depth); |
255 | } |
256 | |
257 | char **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 | |
306 | void 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 |
372 | void 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 | |
377 | size_t aws_backtrace(void **frames, size_t size) { |
378 | (void)frames; |
379 | (void)size; |
380 | return 0; |
381 | } |
382 | |
383 | char **aws_backtrace_symbols(void *const *frames, size_t stack_depth) { |
384 | (void)frames; |
385 | (void)stack_depth; |
386 | return NULL; |
387 | } |
388 | |
389 | char **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 | |
396 | void 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 | |