xref: /llvm-project/compiler-rt/test/sanitizer_common/ios_commands/print_crashreport_for_pid.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
1"""
2Finds and prints the crash report associated with a specific (binary filename, process id).
3Waits (max_wait_time/attempts_remaining) between retries.
4By default, max_wait_time=5 and retry_count=10, which results in a total wait time of ~15s
5Errors if the report cannot be found after `retry_count` retries.
6"""
7import sys, os, argparse, re, glob, shutil, time
8
9
10def main():
11    parser = argparse.ArgumentParser()
12    parser.add_argument(
13        "--pid",
14        type=str,
15        required=True,
16        help="The process id of the process that crashed",
17    )
18    parser.add_argument(
19        "--binary-filename",
20        type=str,
21        required=True,
22        help="The name of the file that crashed",
23    )
24    parser.add_argument(
25        "--retry-count",
26        type=int,
27        nargs="?",
28        default=10,
29        help="The number of retries to make",
30    )
31    parser.add_argument(
32        "--max-wait-time",
33        type=float,
34        nargs="?",
35        default=5.0,
36        help="The max amount of seconds to wait between tries",
37    )
38
39    parser.add_argument(
40        "--dir",
41        nargs="?",
42        type=str,
43        default="~/Library/Logs/DiagnosticReports",
44        help="The directory to look for the crash report",
45    )
46    parser.add_argument(
47        "--outfile",
48        nargs="?",
49        type=argparse.FileType("r"),
50        default=sys.stdout,
51        help="Where to write the result",
52    )
53    args = parser.parse_args()
54
55    assert args.pid, "pid can't be empty"
56    assert args.binary_filename, "binary-filename can't be empty"
57
58    os.chdir(os.path.expanduser(args.dir))
59    output_report_with_retries(
60        args.outfile,
61        args.pid.strip(),
62        args.binary_filename,
63        args.retry_count,
64        args.max_wait_time,
65    )
66
67
68def output_report_with_retries(
69    outfile, pid, filename, attempts_remaining, max_wait_time
70):
71    report_name = find_report_in_cur_dir(pid, filename)
72    if report_name:
73        with open(report_name, "r") as f:
74            shutil.copyfileobj(f, outfile)
75        return
76    elif attempts_remaining > 0:
77        # As the number of attempts remaining decreases, increase the number of seconds waited
78        # if the max wait time is 2s and there are 10 attempts remaining, wait .2 seconds.
79        # if the max wait time is 2s and there are 2 attempts remaining, wait 1 second.
80        time.sleep(max_wait_time / attempts_remaining)
81        output_report_with_retries(
82            outfile, pid, filename, attempts_remaining - 1, max_wait_time
83        )
84    else:
85        raise RuntimeError("Report not found for ({}, {}).".format(filename, pid))
86
87
88def find_report_in_cur_dir(pid, filename):
89    for report_name in sorted(glob.glob("{}_*.crash".format(filename)), reverse=True):
90        # parse out pid from first line of report
91        # `Process:               filename [pid]``
92        with open(report_name) as cur_report:
93            pattern = re.compile(r"Process: *{} \[([0-9]*)\]".format(filename))
94            cur_report_pid = pattern.search(cur_report.readline()).group(1)
95
96        assert cur_report_pid and cur_report_pid.isdigit()
97        if cur_report_pid == pid:
98            return report_name
99
100    # did not find the crash report
101    return None
102
103
104if __name__ == "__main__":
105    main()
106