Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/debug/crash_debugger.py
9418 views
1
#!/usr/bin/env python3
2
3
# flake8: noqa
4
5
"""
6
Script to catch and give backtrace of a HardFault Crash
7
8
Usage:
9
python crash_debugger.py <elf_file> --ser-debug --ser-port <serial_port> --dump-fileout <dump_fileout>
10
python crash_debugger.py <elf_file> --swd-debug --gdb-port <gdb_port> --dump-fileout <dump_fileout>
11
python crash_debugger.py <elf_file> --dump-debug --dump-fileout <dump_fileout>
12
Copyright Siddharth Bharat Purohit, CubePilot Pty. Ltd. 2021
13
based on http://www.cyrilfougeray.com/2020/07/27/firmware-logs-with-stack-trace.html
14
Released under GNU GPL version 3 or later
15
"""
16
17
from serial import Serial
18
import sys
19
import subprocess
20
import argparse
21
import os
22
import time
23
from queue import Queue, Empty
24
from threading import Thread
25
import signal
26
27
def serial_debug(args):
28
global spinner, process_cmd
29
try:
30
ser = Serial(args.ser_port, 921600) # Configure speed depending on your config
31
print('Detecting Crash...')
32
ser.write("dump_crash_log".encode('utf-8')) # Send a newline to start the dump
33
while 1:
34
# Read line by line (waiting for '\n')
35
sys.stdout.write(next(spinner))
36
sys.stdout.flush()
37
line = ser.readline()
38
sys.stdout.write('\b')
39
40
if not line:
41
break
42
# When crash is detected
43
# Crash dump is added into a temporary file
44
# GDB is used to back trace the crash
45
if b"Enable logging" in line.strip():
46
print("Crash detected, retrieving crash info, please be patient...")
47
dump_file = open(args.dump_fileout, 'wb+')
48
49
# We are now storing the stack dump into the file
50
ser.write("dump_crash_log".encode('utf-8')) # Send a newline to start the dump
51
line = ser.readline()
52
dumping = False
53
while b"End of dump" not in line.strip():
54
sys.stdout.write(next(spinner))
55
sys.stdout.flush()
56
if b"6343" in line.strip(): # Look for the start of dump
57
dumping = True
58
if dumping:
59
dump_file.write(line)
60
line = ser.readline()
61
sys.stdout.write('\b')
62
63
print("Crash info retrieved.\n")
64
65
dump_file.close()
66
return True
67
except KeyboardInterrupt:
68
ser.close()
69
return False
70
71
def swd_debug(args):
72
global spinner, process_cmd
73
openocd_proc = None
74
try:
75
# Get BackTrace
76
ON_POSIX = 'posix' in sys.builtin_module_names
77
78
def enqueue_output(out, queue):
79
for line in iter(out.readline, b''):
80
queue.put(line)
81
out.close()
82
hardfault_detected = False
83
# Check if already in hardfault
84
# p = subprocess.Popen(['arm-none-eabi-gdb', '-nx', '--batch',
85
# '-ex', 'target extended-remote {}'.format(args.gdb_port),
86
# '-ex', 'bt',
87
# args.elf_file], stdout=subprocess.PIPE, close_fds=ON_POSIX)
88
# q = Queue()
89
# t = Thread(target=enqueue_output, args=(p.stdout, q))
90
# t.daemon = True # thread dies with the program
91
# t.start()
92
93
# print("Checking if already Crashed")
94
# while p.poll() is None:
95
# try:
96
# line = q.get(False)
97
# if b"HardFault_Handler" in line:
98
# hardfault_detected = True
99
# break
100
# except Empty:
101
# pass
102
# sys.stdout.write(next(spinner))
103
# sys.stdout.flush()
104
# sys.stdout.write('\b')
105
if not hardfault_detected:
106
# lets place breakpoint at HardFault_Handler and wait for it to hit
107
cmd = ['arm-none-eabi-gdb', '-nx', '--batch',
108
'-ex', 'target extended-remote {}'.format(args.gdb_port),
109
'-ex', 'b *&HardFault_Handler',
110
'-ex', 'continue',
111
'-ex', 'run',
112
args.elf_file]
113
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=ON_POSIX)
114
q = Queue()
115
t = Thread(target=enqueue_output, args=(p.stdout, q))
116
t.daemon = True # thread dies with the program
117
t.start()
118
print(' '.join(cmd))
119
# Wait for HardFault_Handler to hit
120
running = False
121
while p.poll() is None:
122
try:
123
line = q.get(False)
124
# print(line.decode('utf-8'))
125
if b"Breakpoint" in line:
126
time.sleep(1)
127
p.send_signal(signal.SIGINT)
128
running = True
129
if b"HardFault_Handler" in line and running:
130
hardfault_detected = True
131
break
132
except Empty:
133
pass
134
sys.stdout.write(next(spinner))
135
sys.stdout.flush()
136
sys.stdout.write('\b')
137
if hardfault_detected:
138
dir_path = os.path.dirname(os.path.realpath(__file__))
139
# generate crash log
140
print("Crash detected, retrieving crash info, please be patient...")
141
cmd = ['arm-none-eabi-gdb', '-nx', '--batch',
142
'-ex', 'target extended-remote {}'.format(args.gdb_port),
143
'-ex', 'set logging file {}'.format(args.dump_fileout),
144
'-x', os.path.join(dir_path, 'crash_dump.scr'),
145
args.elf_file]
146
# We are now storing the stack dump into the file
147
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=ON_POSIX)
148
q = Queue()
149
t = Thread(target=enqueue_output, args=(p.stdout, q))
150
t.daemon = True # thread dies with the program
151
t.start()
152
print(' '.join(cmd))
153
# Wait for HardFault_Handler to hit
154
# TODO: a progress bar would be nice here
155
while p.poll() is None:
156
sys.stdout.write(next(spinner))
157
sys.stdout.flush()
158
sys.stdout.write('\b')
159
print("Crash info retrieved.\n")
160
return True
161
else:
162
print("No crash detected")
163
raise KeyboardInterrupt
164
except KeyboardInterrupt:
165
# kill openocd if running
166
if openocd_proc is not None and openocd_proc.poll() is None:
167
openocd_proc.kill()
168
return False
169
170
if __name__ == '__main__':
171
global spinner, process_cmd
172
parser = argparse.ArgumentParser(description='manipulate parameter defaults in an ArduPilot firmware')
173
174
parser.add_argument('elf_file')
175
parser.add_argument('--ser-debug', action='store_true', help='enable serial debug')
176
parser.add_argument('--ser-port', help='serial port to use')
177
parser.add_argument('--dump-debug', action='store_true', help='generate stack trace from dump file')
178
parser.add_argument('--dump-filein', help='log file to use to generate stack trace')
179
parser.add_argument('--swd-debug', action='store_true', help='enable swd debug')
180
parser.add_argument('--gdb-port', default=':3333', help='set gdb port')
181
parser.add_argument('--dump-fileout', help='filename to dump crash dump')
182
183
args = parser.parse_args()
184
185
if not args.ser_debug and not args.swd_debug and not args.dump_debug:
186
parser.error('Must enable either --ser-debug or --swd-debug')
187
188
if args.ser_debug and not args.ser_port:
189
parser.error('--ser-debug requires --port')
190
191
if args.dump_debug and not args.dump_filein:
192
parser.error('--dump-debug requires --dump-filein')
193
194
#get directory of the script
195
dir_path = os.path.dirname(os.path.realpath(__file__))
196
crashdebug_exe = None
197
if sys.platform == "linux" or sys.platform == "linux2":
198
crashdebug_exe = str(os.path.join(dir_path, "../../modules/CrashDebug/bins/lin64/CrashDebug"))
199
elif sys.platform == "darwin":
200
crashdebug_exe = str(os.path.join(dir_path, "../../modules/CrashDebug/bins/osx/CrashDebug"))
201
elif sys.platform == "win32":
202
crashdebug_exe = str(os.path.join(dir_path, "../../modules/CrashDebug/bins/win32/CrashDebug"))
203
def spinning_cursor():
204
while True:
205
for cursor in '|/-\\':
206
yield cursor
207
208
spinner = spinning_cursor()
209
dump_file = None
210
if args.ser_debug:
211
if args.dump_fileout is None:
212
args.dump_fileout = "last_crash_dump_ser.txt"
213
if (serial_debug(args)):
214
dump_file = args.dump_fileout
215
elif args.swd_debug:
216
if args.dump_fileout is None:
217
args.dump_fileout = "last_crash_dump_gdb.txt"
218
if (swd_debug(args)):
219
dump_file = args.dump_fileout
220
elif args.dump_debug:
221
dump_file = args.dump_filein
222
223
if dump_file is not None:
224
print(crashdebug_exe)
225
print("Processing Crash Dump.\n")
226
process_cmd = "arm-none-eabi-gdb -nx --batch --quiet " + args.elf_file + " -ex \"set target-charset ASCII\" -ex \"target remote | " + crashdebug_exe + " --elf " + args.elf_file + " --dump " + dump_file + "\" -ex \"set print pretty on\" -ex \"bt full\" -ex \"quit\""
227
print(process_cmd)
228
# We can call GDB and CrashDebug using the command and print the results
229
process = subprocess.Popen(process_cmd, shell=True, stdout=subprocess.PIPE)
230
output, error = process.communicate()
231
232
print(output.decode("utf-8"))
233
print("---------\n")
234
line = b""
235
else:
236
print("No crash detected")
237
print("\nExiting!")
238
239