1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | #include <assert.h> |
5 | #include <elf.h> |
6 | #include <link.h> |
7 | #include <unistd.h> |
8 | #include <fcntl.h> |
9 | #include <string.h> |
10 | #include <errno.h> |
11 | #include <stdio.h> |
12 | #include <stdlib.h> |
13 | #include <limits.h> |
14 | #include <dlfcn.h> |
15 | #include <dirent.h> |
16 | #include <sys/auxv.h> |
17 | #include <sys/mman.h> |
18 | #include <sys/prctl.h> |
19 | #include <sys/ptrace.h> |
20 | #include <sys/stat.h> |
21 | #include <sys/syscall.h> |
22 | #include <sys/types.h> |
23 | #include <sys/user.h> |
24 | #include <sys/wait.h> |
25 | |
26 | #include <iostream> |
27 | #include <memory> |
28 | #include <map> |
29 | #include <string> |
30 | #include <algorithm> |
31 | #include <iomanip> |
32 | |
33 | #include "event_man.h" |
34 | #include "easylogging++.h" |
35 | #include "replay.h" |
36 | #include "linenoise.h" |
37 | |
38 | using namespace std; |
39 | |
40 | static void get_program_file(char* linkname) |
41 | { |
42 | FILE* pf = fopen(linkname, "rb" ); |
43 | if (pf) { |
44 | fscanf(pf, "%s" , linkname); |
45 | fclose(pf); |
46 | } |
47 | } |
48 | |
49 | bool is_prefix(const char* s, const char* of) |
50 | { |
51 | return strstr(s, of) == s; |
52 | } |
53 | |
54 | std::vector<std::string> split(const std::string &s, char delimiter) { |
55 | std::vector<std::string> out{}; |
56 | std::stringstream ss {s}; |
57 | std::string item; |
58 | |
59 | while (std::getline(ss,item,delimiter)) { |
60 | out.push_back(item); |
61 | } |
62 | |
63 | return out; |
64 | } |
65 | |
66 | static void run_coredump(const char* program_file, const char* core_file, int verbose) |
67 | { |
68 | #if 1 |
69 | char cmdline[1024]; |
70 | if (verbose) { |
71 | snprintf(cmdline, sizeof(cmdline), "gdb %s %s" , |
72 | program_file, core_file); |
73 | } |
74 | else { |
75 | snprintf(cmdline, sizeof(cmdline), "gdb -q %s %s" , |
76 | program_file, core_file); |
77 | } |
78 | system(cmdline); |
79 | #else |
80 | // TODO: run gdb --interpret=mi2 |
81 | pid_t pid = fork(); |
82 | if (0 == pid) { |
83 | char* argv[4]; |
84 | vector<char*> env; |
85 | |
86 | char** envp = environ; |
87 | for (; *envp; ++envp) { |
88 | env.push_back(*envp); |
89 | } |
90 | |
91 | argv[0] = (char *)"gdb" ; |
92 | argv[1] = (char *)program_file; |
93 | argv[2] = (char *)core_file; |
94 | argv[3] = nullptr; |
95 | |
96 | execve(argv[0], argv, &env[0]); |
97 | // never return. |
98 | } |
99 | |
100 | int status = 0; |
101 | while (pid == waitpid(-1, &status, __WALL)) |
102 | { |
103 | if (WIFEXITED(status) || WIFSTOPPED(status)) { |
104 | break; |
105 | } |
106 | } |
107 | #endif |
108 | } |
109 | |
110 | static void print_event(void* timeline, const EventEntry& entry, int i) |
111 | { |
112 | char info[EVENT_EXTRA_INFO_SIZE]; |
113 | if (entry.type < DUMP_REASON_signal) { |
114 | // syscall |
115 | if (entry.syscall_result < 0xffff) { |
116 | info[0] = 0; |
117 | if (entry.syscall_result < 0) { |
118 | snprintf(info, sizeof(info), "(%s)" , |
119 | errno_name(-entry.syscall_result)); |
120 | } |
121 | cerr << setw(8) << i |
122 | << ": " << setw(16) << get_event_name(entry.type) |
123 | << " ths=" << setw(2) << entry.thread_num |
124 | << " tid=" << setw(5) << entry.tid |
125 | << " result=" << entry.syscall_result << info |
126 | << endl; |
127 | } else { |
128 | cerr << setw(8) << i |
129 | << ": " << setw(16) << get_event_name(entry.type) |
130 | << " ths=" << setw(2) << entry.thread_num |
131 | << " tid=" << setw(5) << entry.tid |
132 | << " result=" << HEX(entry.syscall_result) << endl; |
133 | } |
134 | |
135 | if (get_event_extra_info(timeline, i, info, sizeof(info)) > 0) { |
136 | cerr << "\t\t\t" << info; |
137 | } |
138 | } |
139 | else if (entry.type < DUMP_REASON_dbus) { |
140 | // signal |
141 | cerr << setw(8) << i |
142 | << ": " << setw(16) << get_event_name(entry.type) |
143 | << " ths=" << setw(2) << entry.thread_num |
144 | << " tid=" << setw(5) << entry.tid << endl; |
145 | } |
146 | else if (entry.type < DUMP_REASON_x11) { |
147 | // dbus |
148 | get_event_extra_info(timeline, i, info, sizeof(info)); |
149 | cerr << setw(8) << i |
150 | << ": " << setw(16) << get_event_name(entry.type) |
151 | << " ths=" << setw(2) << entry.thread_num |
152 | << " tid=" << setw(5) << entry.tid |
153 | << " " << info << endl; |
154 | } |
155 | else { |
156 | info[0] = 0; |
157 | // x11 |
158 | get_event_extra_info(timeline, i, info, sizeof(info)); |
159 | cerr << setw(8) << i |
160 | << ": " << setw(16) << get_event_name(entry.type) |
161 | << " ths=" << setw(2) << entry.thread_num |
162 | << " tid=" << setw(5) << entry.tid |
163 | << " " << info << endl; |
164 | } |
165 | } |
166 | |
167 | static void list_event(void* timeline, int count, int begin, int end) |
168 | { |
169 | EventEntry entry; |
170 | if (end >= count -1) end = count -1; |
171 | |
172 | for (int i = begin; i<=end; ++i) { |
173 | get_event(timeline, i, &entry); |
174 | print_event(timeline, entry, i); |
175 | } |
176 | } |
177 | |
178 | enum CompareOp{ |
179 | None = 0, |
180 | LT, |
181 | LTE, |
182 | EQ, |
183 | GTE, |
184 | GT, |
185 | }; |
186 | |
187 | static CompareOp parse_compare_op(const char* op, long* value) |
188 | { |
189 | bool hex = (strstr(op, "0x" ) || strstr(op, "0X" )); |
190 | int base = hex ? 16:10; |
191 | if (op[0] == '<') { |
192 | if (op[1] == '=') { |
193 | *value = strtol(op + 3, nullptr, base); |
194 | return LTE; |
195 | } |
196 | |
197 | *value = strtol(op + 2, nullptr, base); |
198 | return LT; |
199 | } |
200 | else if (op[0] == '=') { |
201 | if (op[1] == '=') { |
202 | *value = strtol(op + 3, nullptr, base); |
203 | return EQ; |
204 | } |
205 | } |
206 | else if (op[0] == '>') { |
207 | if (op[1] == '=') { |
208 | *value = strtol(op + 3, nullptr, base); |
209 | return GTE; |
210 | } |
211 | |
212 | *value = strtol(op + 2, nullptr, base); |
213 | return GT; |
214 | } |
215 | |
216 | return None; |
217 | } |
218 | |
219 | static void search_event(void* timeline, int count, int type_begin, |
220 | int type_end, CompareOp op = None, long param = 0) |
221 | { |
222 | EventEntry entry; |
223 | |
224 | for (int i = 0, j = 0; i<count; ++i) { |
225 | get_event(timeline, i, &entry); |
226 | if (entry.type >= type_begin && entry.type <= type_end) { |
227 | switch (op) { |
228 | case LT: |
229 | if (entry.syscall_result >= param) continue; |
230 | break; |
231 | case LTE: |
232 | if (entry.syscall_result > param) continue; |
233 | break; |
234 | case EQ: |
235 | if (entry.syscall_result != param) continue; |
236 | break; |
237 | case GTE: |
238 | if (entry.syscall_result < param) continue; |
239 | break; |
240 | case GT: |
241 | if (entry.syscall_result <= param) continue; |
242 | break; |
243 | default: |
244 | break; |
245 | } |
246 | |
247 | print_event(timeline, entry, i); |
248 | ++j; |
249 | if (0 == (j & 31)) { |
250 | cerr << "---Type c <return> to continue, or q <return> to quit---" << endl; |
251 | char in; |
252 | cin >> in; |
253 | if ('q' == in) { |
254 | break; |
255 | } |
256 | } |
257 | } |
258 | } |
259 | } |
260 | |
261 | static void search_event_by_tid(void* timeline, int count, int tid) |
262 | { |
263 | EventEntry entry; |
264 | |
265 | for (int i = 0, j = 0; i<count; ++i) { |
266 | get_event(timeline, i, &entry); |
267 | if (entry.tid == tid) { |
268 | print_event(timeline, entry, i); |
269 | ++j; |
270 | if (0 == (j & 31)) { |
271 | cerr << "---Type c <return> to continue, or q <return> to quit---" << endl; |
272 | char in; |
273 | cin >> in; |
274 | if ('q' == in) { |
275 | break; |
276 | } |
277 | } |
278 | } |
279 | } |
280 | } |
281 | |
282 | static bool get_range(const char* range, int *begin, int* end) |
283 | { |
284 | if (0 == range[-1] || 0 == range[0]) { |
285 | cerr << "Miss parameter." << endl; |
286 | return false; |
287 | } |
288 | |
289 | char* stop = nullptr; |
290 | *begin = strtol(range, &stop, 10); |
291 | if (*stop > 0x20) { |
292 | *end = strtol(stop + 1, &stop, 10); |
293 | if (*end < *begin) *end = *begin; |
294 | } |
295 | else { |
296 | *end = *begin; |
297 | } |
298 | |
299 | return (*end >= *begin) && (*begin >= 0); |
300 | } |
301 | |
302 | static bool handle_command(void* timeline, int count, |
303 | int& current, int& verbose, const char* command) |
304 | { |
305 | bool can_run = false; |
306 | |
307 | if (is_prefix(command, "p" )) { |
308 | /*prev*/ |
309 | if (current > 0) { |
310 | --current; |
311 | can_run = true; |
312 | } |
313 | else { |
314 | cerr << "Reach the head!" << endl; |
315 | } |
316 | cerr << "Current-index:" << current << endl; |
317 | } |
318 | else if (is_prefix(command, "n" )) { |
319 | /*next*/ |
320 | if (current + 1 < count) { |
321 | ++current; |
322 | can_run = true; |
323 | } |
324 | else { |
325 | cerr << "Reach the tail!" << endl; |
326 | } |
327 | cerr << "Current-index:" << current << endl; |
328 | } |
329 | else if (isdigit(command[0])) { |
330 | int n = atoi(command); |
331 | if (n >= 0 && n < count) { |
332 | current = n; |
333 | can_run = true; |
334 | } |
335 | else { |
336 | cerr << "Index is not valid! max-index=" << count -1 << endl; |
337 | } |
338 | cerr << "Current-index:" << current << endl; |
339 | } |
340 | else if(is_prefix(command, "h" )) { |
341 | cerr << "All commands:" |
342 | "\nh\t\t\t\t# show this help." |
343 | "\np\t\t\t\t# view previous event within gdb" |
344 | "\nn\t\t\t\t# view next event within gdb" |
345 | "\nq\t\t\t\t# quit" |
346 | "\nlog 0/1\t\t\t\t# 1:verbose;0:silent" |
347 | "\nlist xxx,yyy\t\t\t# list event in range [xxx,yyy]" |
348 | "\nsys xxx[,yyy] [ret op imm]\t# search syscall xxx or [xxx,yyy] event" |
349 | "\n \t# op is one of >, >=, ==, <=, <;ret is syscall result" |
350 | "\nsig xxx[,yyy]\t\t\t# search signal xxx or [xxx,yyy] event" |
351 | "\nx11 xxx[,yyy]\t\t\t# search x11 xxx or [xxx,yyy] event" |
352 | "\ndbus xxx[,yyy]\t\t\t# search dbus xxx or [xxx,yyy] event" |
353 | "\ntid xxx \t\t\t# list all event of thread xxx" |
354 | "\nevent-index (0,1,2,..." |
355 | << count - 1 << ")\t# view specified event within gdb" << endl; |
356 | } |
357 | else if(is_prefix(command, "log" )) { |
358 | verbose = (command[4] == '1'); |
359 | cerr << verbose << endl; |
360 | } |
361 | else if(is_prefix(command, "list" )) { |
362 | int begin = -1, end = -1; |
363 | if (get_range(command + 5, &begin, &end)) { |
364 | list_event(timeline, count, begin, end); |
365 | } |
366 | } |
367 | else if(is_prefix(command, "sys" )) { |
368 | int begin = -1, end = -1; |
369 | if (get_range(command + 4, &begin, &end)) { |
370 | long value = 0; |
371 | CompareOp op = None; |
372 | const char* cond = strstr(command + 4, "ret " ); |
373 | if (cond) { |
374 | op = parse_compare_op(cond + 4, &value); |
375 | } |
376 | search_event(timeline, count, begin, end, op, value); |
377 | } |
378 | } |
379 | else if(is_prefix(command, "sig" )) { |
380 | int begin = -1, end = -1; |
381 | if (get_range(command + 4, &begin, &end)) |
382 | search_event(timeline, count, |
383 | begin + DUMP_REASON_signal, |
384 | end + DUMP_REASON_signal); |
385 | } |
386 | else if(is_prefix(command, "x11" )) { |
387 | int begin = -1, end = -1; |
388 | if (get_range(command + 4, &begin, &end)) |
389 | search_event(timeline, count, |
390 | begin + DUMP_REASON_x11, |
391 | end + DUMP_REASON_x11); |
392 | } |
393 | else if(is_prefix(command, "tid" )) { |
394 | int begin = -1, end = -1; |
395 | if (get_range(command + 4, &begin, &end)) |
396 | search_event_by_tid(timeline, count, begin); |
397 | } |
398 | else if(is_prefix(command, "dbus" )) { |
399 | int begin = -1, end = -1; |
400 | if (get_range(command + 5, &begin, &end)) |
401 | search_event(timeline, count, |
402 | begin + DUMP_REASON_dbus, |
403 | end + DUMP_REASON_dbus); |
404 | } |
405 | else { |
406 | cerr << "Unknown command, press h for more help!\n" ; |
407 | } |
408 | |
409 | return can_run; |
410 | } |
411 | |
412 | static int find_dump(const char* parent_dir) |
413 | { |
414 | int pid = 0; |
415 | struct dirent *dir = nullptr; |
416 | DIR* d = opendir(parent_dir); |
417 | if (d) { |
418 | int len = strlen(MAP_FILE_NAME); |
419 | while ((dir = readdir(d)) != nullptr) { |
420 | if (!memcmp(dir->d_name, MAP_FILE_NAME, len)) { |
421 | pid = atoi(dir->d_name + len); |
422 | break; |
423 | } |
424 | } |
425 | closedir(d); |
426 | } |
427 | return pid; |
428 | } |
429 | |
430 | static string get_trace_dir(const char* parent_dir, const char* trace_dir) |
431 | { |
432 | string dir; |
433 | if (nullptr == trace_dir) { |
434 | string linkname = parent_dir; |
435 | linkname += LATEST_TRACE_NAME; |
436 | dir = parent_dir; |
437 | int pos = dir.size(); |
438 | dir.resize(512, 0); |
439 | int len = readlink(linkname.data(), |
440 | (char *)dir.data() + pos, 512 - pos); |
441 | if (len < 0) { |
442 | fprintf(stderr, "failed to readlink %s, errno=%d\n" , |
443 | linkname.data(), errno); |
444 | return ("" ); |
445 | } |
446 | dir.resize(len + pos); |
447 | assert(dir[dir.size()] == 0); |
448 | } |
449 | else { |
450 | dir = trace_dir; |
451 | } |
452 | if ('/' != dir[dir.size()-1]) dir += '/'; |
453 | |
454 | return dir; |
455 | } |
456 | |
457 | int list_pid(const char* parent_dir, const char* trace_dir) |
458 | { |
459 | string dirname = get_trace_dir(parent_dir, trace_dir); |
460 | struct dirent *dir = nullptr; |
461 | DIR* d = opendir(dirname.data()); |
462 | vector<int> allpid; |
463 | if (d) { |
464 | int len = strlen(MAP_FILE_NAME); |
465 | while ((dir = readdir(d)) != nullptr) { |
466 | if (!memcmp(dir->d_name, MAP_FILE_NAME, len)) { |
467 | allpid.push_back(atoi(dir->d_name + len)); |
468 | } |
469 | } |
470 | closedir(d); |
471 | } |
472 | |
473 | sort(allpid.begin(), allpid.end()); |
474 | |
475 | for (const auto& i:allpid) { |
476 | printf("\t%d\n" , i); |
477 | } |
478 | |
479 | return 0; |
480 | } |
481 | |
482 | int replay(const char* parent_dir, const char* trace_dir, int pid) |
483 | { |
484 | char maps_file[512]; |
485 | char context_file[512]; |
486 | char program_file[512]; |
487 | char core_file[512]; |
488 | void* timeline = nullptr; |
489 | string dir = get_trace_dir(parent_dir, trace_dir); |
490 | |
491 | if (0 == pid) { |
492 | pid = find_dump(dir.data()); |
493 | if (pid < 1) { |
494 | fprintf(stderr, "failed to find pid in %s\n" , dir.data()); |
495 | return 0; |
496 | } |
497 | } |
498 | |
499 | snprintf(maps_file, sizeof(maps_file), |
500 | "%s%s%d" , dir.data(), MAP_FILE_NAME, pid); |
501 | snprintf(context_file, sizeof(context_file), |
502 | "%s%s%d" , dir.data(), CONTEXT_FILE_NAME, pid); |
503 | snprintf(program_file, sizeof(program_file), |
504 | "%s%s%d" , dir.data(), EXEC_FILE_NAME, pid); |
505 | get_program_file(program_file); |
506 | fprintf(stderr, "start replay:%s\n\t%s\n\t%s\n\t%s\n" , |
507 | dir.data(), maps_file, context_file, program_file); |
508 | int count = create_timeline(maps_file, context_file, &timeline); |
509 | if (nullptr == timeline) { |
510 | fprintf(stderr, "Failed to create timeline, error=%d\n" , count); |
511 | return 0; |
512 | } |
513 | |
514 | snprintf(core_file, sizeof(core_file), "/tmp/corefile-%d.core" , pid); |
515 | |
516 | int verbose = 0; |
517 | int current = -1; |
518 | char* line = nullptr; |
519 | |
520 | cerr << "press h for more help, event max index is:" |
521 | << count - 1 << endl; |
522 | |
523 | while ((line = linenoise("emd> " )) != nullptr) { |
524 | if (is_prefix(line, "quit" ) || is_prefix(line, "q" )) { |
525 | break; |
526 | } |
527 | |
528 | if (handle_command(timeline, count, current, verbose, line)) { |
529 | if (0 == generate_coredump(timeline, current, core_file, verbose)) { |
530 | run_coredump(program_file, core_file, verbose); |
531 | } |
532 | else { |
533 | cerr << "Failed to create coredump file" << endl; |
534 | } |
535 | } |
536 | |
537 | linenoiseHistoryAdd(line); |
538 | linenoiseFree(line); |
539 | } |
540 | |
541 | return 0; |
542 | } |
543 | |
544 | int dump(const char* parent_dir, const char* trace_dir, int pid) |
545 | { |
546 | char maps_file[512]; |
547 | char context_file[512]; |
548 | char program_file[512]; |
549 | void* timeline = nullptr; |
550 | string dir = get_trace_dir(parent_dir, trace_dir); |
551 | |
552 | if (0 == pid) { |
553 | pid = find_dump(dir.data()); |
554 | if (pid < 1) { |
555 | fprintf(stderr, "failed to find pid in %s\n" , dir.data()); |
556 | return 0; |
557 | } |
558 | } |
559 | |
560 | snprintf(maps_file, sizeof(maps_file), |
561 | "%s%s%d" , dir.data(), MAP_FILE_NAME, pid); |
562 | snprintf(context_file, sizeof(context_file), |
563 | "%s%s%d" , dir.data(), CONTEXT_FILE_NAME, pid); |
564 | snprintf(program_file, sizeof(program_file), |
565 | "%s%s%d" , dir.data(), EXEC_FILE_NAME, pid); |
566 | get_program_file(program_file); |
567 | fprintf(stderr, "start dump:%s\n\t%s\n\t%s\n\t%s\n" , |
568 | dir.data(), maps_file, context_file, program_file); |
569 | int count = create_timeline(maps_file, context_file, &timeline); |
570 | if (nullptr == timeline) { |
571 | fprintf(stderr, "Failed to create timeline, error=%d\n" , count); |
572 | return 0; |
573 | } |
574 | |
575 | list_event(timeline, count, 0, count - 1); |
576 | |
577 | destroy_timeline(timeline); |
578 | |
579 | return 0; |
580 | } |
581 | |