1#!/usr/bin/env python3
2
3# SPDX-License-Identifier: BSD-3-Clause
4#
5# Copyright (c) 2019, Intel Corporation. All rights reserved.
6#
7# Author: Michal Jerzy Wierzbicki <michalx.wierzbicki@linux.intel.com>
8#         Adrian Bonislawski <adrian.bonislawski@linux.intel.com>
9
10# Tool for processing FW stack dumps.
11# For more detailed useage, use --help option.
12
13from __future__  import print_function
14import argparse
15import struct
16import os
17import sys
18import itertools
19import re
20import shutil
21import time
22from ctypes      import LittleEndianStructure, BigEndianStructure, c_uint32, c_char
23from collections import namedtuple
24from operator    import attrgetter
25from functools   import partial
26
27def stderr_print(*args, **kwargs):
28	print(*args, file=sys.stderr, **kwargs)
29
30def stdout_print(*args, **kwargs):
31	print(*args, file=sys.stdout, **kwargs)
32
33try:
34	from sty import fg, bg, ef, rs, Rule, Render
35	CAN_COLOUR=True
36except:
37	CAN_COLOUR=False
38
39ArchDefinition = namedtuple('ArchDefinition', ['name', 'bitness', 'endianness'])
40VALID_ARCHS = {}
41[VALID_ARCHS.update({archdef.name : archdef})
42	for archdef in [ArchDefinition(*tup) for tup in
43	[
44		( 'LE32bit',
45			32,
46			LittleEndianStructure, ),
47		( 'LE64bit',
48			64,
49			LittleEndianStructure, ),
50#		( 'BE32bit', #untested, treat as not implemented
51#			32,
52#			BigEndianStructure, ),
53#		( 'BE64bit', #untested, treat as not implemented
54#			64,
55#			BigEndianStructure, ),
56	]]
57]
58
59# Exception casues:
60# CODE: [Exception cause, excvaddr loaded]
61EXCCAUSE_CODE = {
62	0: ["IllegalInstructionCause: Illegal instruction", False],
63	1: ["SyscallCause: SYSCALL instruction", True],
64	2: ["InstructionFetchErrorCause: Processor internal physical address or data error during instruction fetch", True],
65	3: ["LoadStoreErrorCause: Processor internal physical address or data error during load or store", True],
66	4: ["Level1InterruptCause: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register", False],
67	5: ["AllocaCause: MOVSP instruction, if caller's registers are not in the register file", False],
68	6: ["IntegerDivideByZeroCause: QUOS, QUOU, REMS, or REMU divisor operand is zero", False],
69	8: ["PrivilegedCause: Attempt to execute a privileged operation when CRING ? 0", False],
70	9: ["LoadStoreAlignmentCause: Load or store to an unaligned address", True],
71	12: ["InstrPIFDataErrorCause: PIF data error during instruction fetch", True],
72	13: ["LoadStorePIFDataErrorCause: ynchronous PIF data error during LoadStore access", True],
73	14: ["InstrPIFAddrErrorCause: PIF address error during instruction fetch", True],
74	15: ["LoadStorePIFAddrErrorCause: Synchronous PIF address error during LoadStore access", True],
75	16: ["InstTLBMissCause: Error during Instruction TLB refill", True],
76	17: ["InstTLBMultiHitCause: Multiple instruction TLB entries matched", True],
77	18: ["InstFetchPrivilegeCause: An instruction fetch referenced a virtual address at a ring level less than CRING", True],
78	20: ["InstFetchProhibitedCause: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch", True],
79	24: ["LoadStoreTLBMissCause: Error during TLB refill for a load or store", True],
80	25: ["LoadStoreTLBMultiHitCause: Multiple TLB entries matched for a load or store", True],
81	26: ["LoadStorePrivilegeCause: A load or store referenced a virtual address at a ring level less than CRING", True],
82	28: ["LoadProhibitedCause: A load referenced a page mapped with an attribute that does not permit loads", True],
83	29: ["StoreProhibitedCause: A store referenced a page mapped with an attribute that does not permit stores", True],
84	32: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
85	33: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
86	34: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
87	35: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
88	36: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
89	37: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
90	38: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False],
91	39: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False]
92}
93
94def valid_archs_print():
95	archs = ''.join("{0}, ".format(x) for x in VALID_ARCHS)
96	archs = archs[:len(archs)-2]
97	return "{0}.".format(archs)
98
99TERM_SIZE = shutil.get_terminal_size((120, 20))
100AR_WINDOW_WIDTH = 4
101IS_COLOUR=False
102
103class argparse_readable_file( argparse.Action):
104	def raise_error(self, filepath, reason):
105		raise argparse.ArgumentTypeError(
106			"is_readable_file:{0} {1}".format(
107				filepath,
108				reason
109			))
110	def is_readable_file(self, filepath):
111		if not os.path.isfile(filepath):
112			self.raise_error(filepath, "is not a valid path")
113		if os.access(filepath, os.R_OK):
114			return True
115		else:
116			self.raise_error(filepath, "is not a readable file")
117		return False
118	def __call__(self, parser, namespace, values, option_string=None):
119		filepath = values[0]
120		if (self.is_readable_file(filepath)):
121			setattr(namespace, self.dest, filepath)
122
123class argparse_writeable_file(argparse.Action):
124	def raise_error(self, filepath, reason):
125		raise argparse.ArgumentTypeError(
126			"is_writeable_file:{0} {1}".format(
127				filepath,
128				reason
129			))
130	def is_writeable_file(self, filepath):
131		absdir = os.path.abspath(os.path.dirname(filepath))
132		if os.path.isdir(absdir) and not os.path.exists(filepath):
133			return True
134		else:
135			if not os.path.isfile(filepath):
136				self.raise_error(filepath, "is not a valid path")
137			if os.access(filepath, os.W_OK):
138				return True
139			else:
140				self.raise_error(filepath, "is not a writeable file")
141		return False
142	def __call__(self, parser, namespace, values, option_string=None):
143		filepath = values[0]
144		if (self.is_writeable_file(filepath)):
145			setattr(namespace, self.dest, filepath)
146		else:
147			self.raise_error(
148				filepath,
149				"failed to determine whether file is writeable"
150			)
151
152class argparse_architecture(  argparse.Action):
153	def raise_error(self, value, reason):
154		raise argparse.ArgumentTypeError(
155			"architecture: {0} {1}".format(
156				value,
157				reason
158			))
159	def is_valid_architecture(self, value):
160		if value in VALID_ARCHS:
161			return True
162		return False
163	def __call__(self, parser, namespace, values, option_string=None):
164		value = values[0]
165		if (self.is_valid_architecture(value)):
166			setattr(namespace, self.dest, VALID_ARCHS[value])
167		else:
168			self.raise_error(
169				value,
170				"is invalid architecture. Valid architectures are: {0}".format(valid_archs_print())
171			)
172
173def parse_params():
174	parser = argparse.ArgumentParser(
175		description="Tool for processing FW stack dumps."
176			+" In verbose mode it prints DSP registers, and function call"
177			+" addresses in stack up to that which caused core dump."
178			+" It then prints either to file or to stdin all gdb"
179			+" commands unwrapping those function call addresses to"
180			+" function calls in  human readable format."
181	)
182	ArgTuple = namedtuple('ArgTuple', ['name', 'optionals', 'parent'])
183	ArgTuple.__new__.__defaults__ = ((), {}, parser)
184
185	# below cannot be empty once declared
186	outputMethod = parser.add_mutually_exclusive_group(required=True)
187	inputMethod  = parser.add_mutually_exclusive_group()
188	[parent.add_argument(*name, **optionals)
189		for name, optionals, parent in sorted([ArgTuple(*x) for x in
190			[
191				( ( '-a', '--arch'       , ), {
192						'type'   : str,
193						'help'   :'determine architecture of dump file; valid archs are:  {0}'
194							.format(valid_archs_print()),
195						'action' : argparse_architecture,
196						'nargs'  : 1,
197						'default': VALID_ARCHS['LE64bit'],
198					},),
199				( ( '-v', '--verbose'    , ), {
200						'help'  :'increase output verbosity',
201						'action':'store_true',
202					},),
203				( (       '--stdin'      , ), {
204						'help'   :'input is from stdin',
205						'action' : 'store_true',
206					},
207					inputMethod),
208				( ( '-o', '--outfile'    , ), {
209						'type'   : str,
210						'help'   :'output is to FILE',
211						'action' : argparse_writeable_file,
212						'nargs'  : 1,
213					},
214					outputMethod),
215				( (       '--stdout'     , ), {
216						'help'   :'output is to stdout',
217						'action' :'store_true',
218					},
219					outputMethod),
220				( ( '-c', '--colour', ), {
221						'help'  :'set output to be colourful!',
222						'action':'store_true',
223					},),
224				( ( '-l', '--columncount', ), {
225						'type'  : int,
226						'help'  :'set how many colums to group the output in, ignored without -v',
227						'action':'store',
228						'nargs' : 1,
229					},),
230				( ( '-i', '--infile'     , ), {
231						'type'   : str,
232						'help'   :'path to sys dump bin',
233						'action' : argparse_readable_file,
234						'nargs'  : 1,
235					},
236					inputMethod),
237			]],
238			key=lambda argtup: (argtup.parent.__hash__(), argtup.name)
239		)
240	]
241
242	parsed = parser.parse_args()
243
244	if parsed.columncount and not parsed.verbose:
245		stderr_print("INFO: -l option will be ignored without -v")
246
247	return parsed
248
249def chunks(l, n):
250	return [l[i:i + n] for i in range(0, len(l), n)]
251
252def flaten(l):
253	return [item for sublist in l for item in sublist]
254
255def raiseIfArchNotValid(arch):
256	if arch not in VALID_ARCHS.values():
257		raise ValueError(
258			"CoreDumpFactory: {0} not in valid architectures: {1}"
259				.format(arch, valid_archs_print())
260		)
261	endiannesses = [arch.endianness for arch in VALID_ARCHS.values()]
262	if arch.endianness not in endiannesses:
263		raise ValueError(
264			"CoreDumpFactory: {0} not in valid endiannesses: {1}"
265				.format(endianness, endiannesses)
266		)
267
268def FileInfoFactory(arch, filename_length):
269	raiseIfArchNotValid(arch)
270	class FileInfo(arch.endianness):
271		_fields_ = [
272			("hdr",  c_uint32),
273			("code",  c_uint32),
274			("filename", filename_length * c_char),
275			("line_no",  c_uint32)
276		]
277		def __str__(self):
278			return "{}:{:d}".format(self.filename.decode(), self.line_no)
279	if FileInfo is None:
280		raise RuntimeError(
281			"FileInfoFactory: failed to produce FileInfo({0})"
282				.format(arch.name)
283		)
284	return FileInfo
285
286class Colourer():
287	#TODO: Add detection of 8bit/24bit terminal
288	#      Add 8bit/24bit colours (with flag, maybe --colour=24bit)
289	#      Use this below as fallback only
290	__print = partial(stdout_print)
291	if CAN_COLOUR == True:
292		__style = {
293			'o' : fg.red,
294			'O' : fg.yellow,
295			'y' : fg.blue,
296			'Y' : fg.cyan,
297			'D' : fg.white + bg.red,
298		}
299	matchings          = []
300	matchingsInParenth = []
301	matchingsInStderr  = []
302	def __init__(self):
303		if CAN_COLOUR == True:
304			self.matchings = [
305				(
306					lambda x: self.enstyleNumHex(x.group()),
307					re.compile(r'\b([A-Fa-f0-9]{8})\b')
308				),
309				(
310					lambda x: '0x' + self.enstyleNumHex(x.group(2)),
311					re.compile(r'(0x)([A-Fa-f0-9]{1,})')
312				),
313				(
314					lambda x: self.enstyleNumBin(x.group()),
315					re.compile(r'\b(b[01]+)\b')
316				),
317				(
318					r'\1' +
319					r'\2' +
320					self.enstyle(           fg.green , r'\3') ,
321					re.compile(r'(\#)(ar)([0-9]+)\b')
322				),
323				(
324					self.enstyle(bg.green            , r'\1') +
325					rs.all + '\n',
326					re.compile(r'(\(xt-gdb\)\ *)')
327				),
328				(
329					r'\1' +
330					r'\2' +
331					self.enstyle(           fg.green   , r'\3') +
332					r':'  +
333					self.enstyle(           fg.magenta , r'\4') ,
334					re.compile(r'(\bat\b\ *)(.+/)?(.*):([0-9]+)')
335				),
336				(
337					lambda x:\
338						'in '+
339						self.enstyle(fg.green,  x.group(2))+
340						self.enstyleFuncParenth(x.group(3)),
341					re.compile(r'(\bin\b\ *)([^\ ]+)\ *(\(.*\))')
342				),
343			]
344			self.matchingsInParenth = [
345				(
346					self.enstyle(           fg.yellow   , r'\1') +
347					self.enstyle(           fg.magenta  , r'=' ) +
348					r'\2',
349					re.compile(r'([\-_a-zA-Z0-9]+)\ *=\ *([^,]+)')
350				),
351				(
352					self.enstyle(           fg.magenta  , r'\1') ,
353					re.compile(r'(\ *[\(\)]\ *)')
354				),
355				(
356					self.enstyle(           fg.magenta  , r', ') ,
357					re.compile(r'(\ *,\ *)')
358				),
359			]
360			self.matchingsInStderr = [
361				(
362					self.enstyle(bg.yellow  + fg.black    , r'\1') ,
363					re.compile(r'([Ww]arning)')
364				),
365				(
366					self.enstyle(bg.red     + fg.black    , r'\1') ,
367					re.compile(r'([Ee]rror)')
368				),
369				(
370					self.enstyle(bg.magenta + fg.black    , r'\1') ,
371					re.compile(r'([Ff]atal)')
372				),
373			]
374
375	def toGroup(self, txt):
376		return [ (label, sum(1 for _ in group))
377				for label, group in itertools.groupby(txt) ]
378
379	def leadingZero(self, txt, char):
380		result = ""
381		groups = self.toGroup(txt)
382		lead = 0
383		if groups[0][0] == '0':
384			lead = min(4, groups[0][1])
385			result += char.lower() *    lead
386		result +=     char.upper() * (4-lead)
387		return result
388
389	def findSub(self, txt, mask, sub, char):
390		pos = txt.find(sub)
391		if pos >= 0:
392			return mask[:pos] + char * len(sub) + mask[(len(sub)+pos):]
393		else:
394			return mask
395
396	def enstyleFuncParenth(self, txt):
397		result = txt
398		for repl, regex in self.matchingsInParenth:
399			result = re.sub(regex, repl, result)
400		return result
401
402	def enstyleNumBin(self, txt):
403		result = rs.all + bg.magenta + "b"
404		prev = ""
405		for c in txt[1:]:
406			if prev != c:
407				prev = c
408				result += rs.all
409				if c == "0":
410					result += fg.red
411			result += c
412		result += rs.all
413		return result
414
415	def enstyleNumHex(self, txt):
416		if len(txt) < 8:
417			txt = (8-len(txt))*'0' + txt
418		p1 = 'o'
419		p2 = 'y'
420		if txt == "00000000":
421			styleMask = p1 * 8
422		elif txt.lower() == "deadbeef":
423			styleMask = "DDDDDDDD"
424		else:
425			styleMask = "".join(
426				[self.leadingZero(string, style)
427				for string, style in [
428					(txt[:4], p1),
429					(txt[4:], p2),
430			]])
431			styleMask = "".join(
432				[self.findSub(txt, styleMask, string, style)
433				for string, style in [
434					('dead', 'D'),
435			]])
436
437		result = ""
438		thisstyle = ''
439		for iter, style in enumerate(styleMask):
440			if thisstyle != style:
441				thisstyle = style
442				result += rs.all + self.__style[thisstyle]
443			result += txt[iter]
444		result += rs.all
445		return result
446
447	def enstyleStderr(self, txt):
448		if txt is None:
449			return ''
450		result = txt
451		for repl, regex in self.matchingsInStderr:
452			result = re.sub(regex, repl, result)
453		for repl, regex in self.matchings:
454			result = re.sub(regex, repl, result)
455		return fg.red + result + rs.all
456
457	def enstyle(self, style, txt):
458		return style + txt + rs.all
459
460	def produce_string(self, txt):
461		result = txt
462		for repl, regex in self.matchings:
463			result = re.sub(regex, repl, result)
464		return result
465
466	def print(self, txt):
467		self.__print(self.produce_string(txt))
468
469def CoreDumpFactory(dsp_arch):
470	raiseIfArchNotValid(dsp_arch)
471	class CoreDump(dsp_arch.endianness):
472		_fields_ = [(x, c_uint32) for x in
473				[
474					# struct sof_ipc_dsp_oops_arch_hdr {
475					"arch",
476					"totalsize",
477					# }
478					# struct sof_ipc_dsp_oops_plat_hdr {
479					"configidhi",
480					"configidlo",
481					"numaregs",
482					"stackoffset",
483					"stackptr",
484					# }
485					"exccause",
486					"excvaddr",
487					"ps"
488				]
489				+ ["epc" + str(x) for x in range(1,7+1)]
490				+ ["eps" + str(x) for x in range(2,7+1)]
491				+ [
492					"depc",
493					"intenable",
494					"interrupt",
495					"sar",
496					"debugcause",
497					"windowbase",
498					"windowstart",
499					"excsave1" # to
500				]
501			] + [
502				("a", dsp_arch.bitness * c_uint32)
503			]
504
505		def __init__(self, columncount):
506			self.dsp_arch = dsp_arch
507			self._fields_
508			self.ar_regex = re.compile(r'ar[0-9]+')
509			# below: smart column count
510			self._longest_field = len(max([x[0] for x in self._fields_], key=len))
511			if columncount is not None:
512				self.columncount = max (1, int(columncount[0]))
513			else:
514				self.columncount = max(1,
515					int(TERM_SIZE[0]/(self._longest_field + 2 + 2 * AR_WINDOW_WIDTH + 2))
516				)
517			self.columncount_ar = (
518				self.columncount
519					if self.columncount <= AR_WINDOW_WIDTH else
520				AR_WINDOW_WIDTH * int(self.columncount/AR_WINDOW_WIDTH)
521			)
522
523		def __windowbase_shift(self, iter, direction):
524			return (iter + self.windowbase * AR_WINDOW_WIDTH * direction)\
525				% self.dsp_arch.bitness
526		def windowbase_shift_left(self, iter):
527			return self.__windowbase_shift(iter, -1)
528		def windowbase_shift_right(self, iter):
529			return self.__windowbase_shift(iter,  1)
530
531		def reg_from_string(self, string):
532			if self.ar_regex.fullmatch(string):
533				return self.a[self.windowbase_shift_left(int(string[2:]))]
534			else:
535				return self.__getattribute__(string)
536
537		def __str__(self):
538			string = ""
539			string += "exccause"
540			return string
541
542		def to_string(self, is_gdb):
543			# in case windowbase in dump has invalid value
544			windowbase_shift = min(
545				self.windowbase * AR_WINDOW_WIDTH,
546				self.dsp_arch.bitness
547			)
548			# flatten + chunk enable to smartly print in N columns
549			string = ''.join([self.fmt(is_gdb, x)
550				for x in flaten(
551					[chunks(word, self.columncount) for word in [
552						["arch", "totalsize", "stackptr", "stackoffset"],
553						["configidhi", "configidlo", "numaregs"],
554					]]
555				)
556			])
557
558			string += "\n# CPU registers:\n\n"
559
560			string += ''.join([self.fmt(is_gdb, x)
561				for x in flaten(
562					[chunks(word, self.columncount) for word in [
563						["exccause", "excvaddr", "ps"],
564						["epc" + str(x) for x in range(1,7+1)],
565						["eps" + str(x) for x in range(2,7+1)],
566						["depc", "intenable", "interrupt", "sar", "debugcause"],
567						["windowbase", "windowstart"],
568						["excsave1"],
569					]] +
570					[chunks(word, self.columncount_ar) for word in [
571						["ar" + str(x) for x in itertools.chain(
572							range(   windowbase_shift, self.dsp_arch.bitness),
573							range(0, windowbase_shift),
574						)]
575					]]
576				)
577			])
578
579			if not is_gdb:
580				string += "\n"
581			return string
582
583		def fmt_gdb_command(self):
584			return "set ${}=0x{:08x}\n"
585
586		def fmt_pretty_form(self, separator = "|"):
587			return separator + "{:" + str(self._longest_field) + "} {:08x} "
588
589		def fmt_separator(self, name):
590			separator = "# "
591			return separator
592
593		def fmt_pretty_auto(self, name):
594			return self.fmt_pretty_form(self.fmt_separator(name))
595
596		def fmt(self, is_gdb, names):
597			if is_gdb:
598				fmtr = lambda name: self.fmt_gdb_command()
599			else:
600				fmtr = lambda name: self.fmt_pretty_auto(name)
601
602			string = ""
603			for name in names:
604				string += fmtr(name).format(
605					name, self.reg_from_string(name)
606				)
607			if not is_gdb:
608				string += "\n"
609			return string
610
611		def windowstart_process(self):
612			string = ""
613			binary = "{0:b}".format(self.windowstart)
614			bit_start = len(binary)-1-self.windowbase
615			fnc_num = 0
616			header = ""
617
618			for it, c in enumerate(binary[bit_start:]):
619				if c != "0":
620					header += str(fnc_num)
621					fnc_num += 1
622				else:
623					header += " "
624			for it, c in enumerate(binary[:bit_start]):
625				if c != "0":
626					header += str(fnc_num)
627					fnc_num += 1
628				else:
629					header += " "
630			header = header[self.windowbase+1:]+ header[:self.windowbase+1]
631
632			string += "# windowbase: {:0X}\n".format(self.windowbase)
633			string += "#               {0}\n".format(header)
634			string += "# windowstart: b{0}\n\n".format(binary)
635			string += "#      reg         a0         a1\n"
636			string += "#                  (return)   (sptr)\n"
637			string += "#      ---         --------   -------\n"
638			fnc_num = 0
639			for iter, digit in enumerate(binary[bit_start:]):
640				if (digit == '1'):
641					reg = "ar{0}".format(AR_WINDOW_WIDTH * (self.windowbase - iter))
642					reg1 = "ar{0}".format(AR_WINDOW_WIDTH * (self.windowbase - iter) + 1)
643					string += "# {0:2d} ".format(++fnc_num)
644					string += self.fmt_pretty_auto(reg).format(
645						reg, self.reg_from_string(reg)
646					) + "  {:0x} ".format(self.reg_from_string(reg1)) + "\n"
647					fnc_num += 1
648			for iter, digit in enumerate(binary[:bit_start]):
649				if (digit == '1'):
650					reg = "ar{0}".format(AR_WINDOW_WIDTH * (len(binary) - 1 - iter))
651					reg1 = "ar{0}".format(AR_WINDOW_WIDTH * (len(binary) - 1 - iter) + 1)
652					string += "# {0:2d} ".format(++fnc_num)
653					string += self.fmt_pretty_auto(reg).format(
654						reg, self.reg_from_string(reg)
655					) + "  {:0x} ".format(self.reg_from_string(reg1)) + "\n"
656					fnc_num += 1
657			return string
658
659	if CoreDump is None:
660		raise RuntimeError(
661			"CoreDumpFactory: failed to produce CoreDump({0})"
662				.format(dsp_arch.name)
663		)
664	return CoreDump
665
666class CoreDumpReader(object):
667	def __init__(self, args):
668		self.core_dump    = CoreDumpFactory(args.arch)(
669			args.columncount
670		)
671		self.file_info    = FileInfoFactory(args.arch, 32)()
672
673		if IS_COLOUR:
674			colourer = Colourer()
675		if args.verbose:
676			verbosePrint =\
677					colourer.print\
678				if IS_COLOUR else\
679					stdout_print
680		else:
681			verbosePrint = lambda *discard_this: None
682
683		if   args.stdout:
684			stdoutOpen  = lambda: None
685			stdoutPrint = print
686			stdoutClose = lambda: None
687		elif args.outfile:
688			#TODO: open file in stdOutOpen
689			stdoutDest  = open(args.outfile, "w")
690			stdoutOpen  = lambda: None
691			stdoutPrint = stdoutDest.write
692			stdoutClose = stdoutDest.close
693		else:
694			raise RuntimeError("CoreDumpReader: No output method.")
695
696		if   args.stdin:
697			inStream = lambda: sys.stdin.buffer
698		elif args.infile:
699			inStream = lambda: open(args.infile, "rb")
700		else:
701			raise RuntimeError("CoreDumpReader: No input method.")
702
703		with inStream() as cd_file:
704			[cd_file.readinto(x) for x in [
705				self.core_dump,
706				self.file_info
707			]]
708			self.stack = cd_file.read()
709
710		verbosePrint("# Core header:\n")
711		verbosePrint(self.core_dump.to_string(0))
712
713		verbosePrint(self.core_dump.windowstart_process())
714
715		stack_base = self.core_dump.stackptr
716		stack_dw_num = int(len(self.stack)/AR_WINDOW_WIDTH)
717		verbosePrint("# Stack dumped from {:08x} dwords num {:d}"
718			.format(stack_base, stack_dw_num))
719
720		stdoutOpen()
721		stdoutPrint("break *0xbefe0000\nrun\n")
722		stdoutPrint(self.core_dump.to_string(1))
723
724		#TODO: make this elegant
725		for dw, addr in [(
726			struct.unpack("I", self.stack[i*AR_WINDOW_WIDTH : (i+1)*AR_WINDOW_WIDTH])[0],
727			stack_base + i*AR_WINDOW_WIDTH
728		) for i in range(0, stack_dw_num)]:
729			stdoutPrint("set *0x{:08x}=0x{:08x}\n"
730				.format(addr, dw))
731
732                # Exccause 63 is reserved for panics; the other causes come
733                # from actual exceptions
734		if self.core_dump.exccause != 63:
735			verbosePrint("\n# *EXCEPTION*\n")
736			verbosePrint("# exccause: " + EXCCAUSE_CODE[self.core_dump.exccause][0])
737			if EXCCAUSE_CODE[self.core_dump.exccause][1]:
738				verbosePrint("# excvaddr: " + str(self.core_dump.excvaddr))
739			stdoutPrint('p "Exception location:"\nlist *$epc1\n');
740		else:
741			verbosePrint("# Location: " + str(self.file_info));
742
743		# TODO: if excsave1 is not empty, pc should be set to that value
744		# (exception mode, not forced panic mode)
745		stdoutPrint('set $pc=&arch_dump_regs_a\np "backtrace"\nbacktrace\n')
746		stdoutClose()
747
748if __name__ == "__main__":
749	args = parse_params()
750	if args.colour:
751		if CAN_COLOUR:
752			IS_COLOUR=True
753		else:
754			stderr_print("INFO: Cannot color the output: module 'sty' not found")
755	CoreDumpReader(args)
756
757