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
38using namespace std;
39
40static 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
49bool is_prefix(const char* s, const char* of)
50{
51 return strstr(s, of) == s;
52}
53
54std::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
66static 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
110static 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
167static 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
178enum CompareOp{
179 None = 0,
180 LT,
181 LTE,
182 EQ,
183 GTE,
184 GT,
185};
186
187static 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
219static 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
261static 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
282static 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
302static 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
412static 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
430static 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
457int 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
482int 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
544int 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