CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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