Files
luban-lite/tools/scripts/panic_parse.py
刘可亮 6e36e8e296 v1.2.0
2025-04-23 17:54:31 +08:00

432 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2025, ArtInChip Technology Co., Ltd
# Author: Matteo <duanmt@artinchip.com>
import os
import sys
import argparse
import subprocess
import datetime
import json
import re
import shutil
# Global
SDK_DIR = os.getcwd() + '/'
VERBOSE = False
STAGE_CNT = 0
COLOR_BEGIN = "\033["
COLOR_RED = COLOR_BEGIN + "41;37m"
COLOR_YELLOW = COLOR_BEGIN + "43;30m"
COLOR_WHITE = COLOR_BEGIN + "47;30m"
COLOR_END = "\033[0m"
def cur_date():
now = datetime.datetime.now()
tm_str = ('%d-%02d-%02d') % (now.year, now.month, now.day)
return tm_str
def cur_time():
now = datetime.datetime.now()
tm_str = ('%02d:%02d:%02d') % (now.hour, now.minute, now.second)
return tm_str
def pr_err(string):
if VERBOSE:
print(COLOR_RED + '*** ' + cur_time() + ' ' + string + COLOR_END)
else:
print(COLOR_RED + '*** ' + string + COLOR_END)
def pr_info(string):
if VERBOSE:
print(COLOR_WHITE + '>>> ' + cur_time() + ' ' + string + COLOR_END)
else:
print(COLOR_WHITE + '>>> ' + string + COLOR_END)
def pr_warn(string):
if VERBOSE:
print(COLOR_YELLOW + '!!! ' + cur_time() + ' ' + string + COLOR_END)
else:
print(COLOR_YELLOW + '!!! ' + string + COLOR_END)
def do_pipe(cmd):
result = subprocess.run(cmd, shell=True, capture_output=True)
return result.stdout.decode('utf-8')
def stage_log(msg):
print('\n---------------------------------------------------------------')
print(msg)
print('---------------------------------------------------------------')
def is_linux():
return sys.platform.startswith('linux')
def search_special_reg(reg, line):
try:
return int(line.strip().replace('0x', '').split(':')[1].strip(), 16)
except Exception as e:
return 0
def scan_elf():
with open('.defconfig', 'r') as f:
defconfig = f.read().strip()
soc = defconfig.split('_')[0]
return os.path.join('output', defconfig, 'images', soc + '.elf')
def check_elf(elf_file):
with open(elf_file, 'rb') as f:
elf_header = f.read(4)
if elf_header != b'\x7fELF':
pr_err(f'Invalid ELF header: {elf_file}')
return False
else:
print(f'ELF: {elf_file}')
return True
pr_warn(f'No ELF file')
return False
class CPU:
def __init__(self, name, elf):
self.name = name
self.elf_file = elf
self.regs = {}
self.tool_addr2line = ''
self.tool_readelf = ''
self.exception = 0xFF
self.text_start = 0
self.text_end = 0
self.stack_start = 0
self.stack_end = 0
def get_exception(self):
if self.exception in self.exceptions:
return self.exceptions[self.exception]
else:
return 'Unknown'
def get_toolchain(self):
return self.toochain
def check_toolchain(self):
if is_linux():
tool_appendix = ''
else:
tool_appendix = '.exe'
self.tool_addr2line = os.path.join('toolchain', 'bin',
self.get_toolchain() + '-addr2line' + tool_appendix)
if not os.path.exists(self.tool_addr2line):
pr_warn(f'Can not found {self.tool_addr2line}...')
print(f'Please try build Luban-Lite first!')
return False
self.tool_readelf = self.tool_addr2line.replace('addr2line', 'readelf')
print(f'Toolchain: {self.get_toolchain()}')
return True
def get_reg_alias(self, reg_name):
if reg_name in self.reg_alias:
return self.reg_alias[reg_name]
else:
return ''
def parse_symbol_from_elf(self, symbol):
if not self.elf_file:
return 0
if is_linux():
tool_grep = 'grep'
else:
tool_grep = 'findstr'
tmp = do_pipe(f'{self.tool_readelf} -s {self.elf_file} | {tool_grep} {symbol}')
return int(tmp.strip().split()[1], 16)
def parse_section(self, log):
with open(log, 'r') as f:
log = f.read()
self.text_start = self.parse_symbol_from_elf('__stext')
self.text_end = self.parse_symbol_from_elf('__etext')
print('Text section: 0x{:08x} - 0x{:08x}, size: {} Bytes'
.format(self.text_start, self.text_end, self.text_end - self.text_start))
match = re.search(r"stack_addr:0x([0-9a-f]+)", log)
self.stack_start = int(match.group(1), 16) if match else 0
match = re.search(r"stack_addr_end:0x([0-9a-f]+)", log)
self.stack_end = int(match.group(1), 16) if match else 0
def is_code(self, addr):
if addr >= self.text_start and addr <= self.text_end:
return True
else:
return False
def add2line(self, addr, elf_file):
tmp = do_pipe(f'{self.tool_addr2line} -e {elf_file} -fp {hex(addr)}')
return tmp.strip().replace(SDK_DIR, '')
def show_cpu_regs(self, log, elf_file):
if len(self.regs) == 0:
print('No CPU registers found')
return False
stage_log('CPU Registers:')
print(f'CPU Exception: {self.exception} ({self.get_exception()})')
if not elf_file:
return False
for reg_name, reg_value in self.regs.items():
if self.is_code(reg_value):
code = self.add2line(reg_value, elf_file)
else:
code = ''
reg_alias = self.get_reg_alias(reg_name)
if reg_alias:
print('{} ({}): 0x{:08x} {}'.format(reg_name, reg_alias, reg_value, code))
else:
print('{}: 0x{:08x} {}'.format(reg_name, reg_value, code))
return True
def show_call_stack(self, log, elf_file):
if not elf_file:
return False
stage_log('Call Stack:')
print('Stack section: 0x{:08x} - 0x{:08x}, size: {} Bytes'
.format(self.stack_start, self.stack_end, self.stack_end - self.stack_start))
with open(log, 'r') as f:
log = f.read()
if 'stack:' not in log:
pr_warn('No call stack found')
return False
stack_log = log.split('stack:')[1]
addresses = re.findall(r'0x[0-9a-fA-F]+', stack_log)
cnt = 0
for address in addresses:
if self.is_code(int(address, 16)):
code = self.add2line(int(address, 16), elf_file)
print('{:2}: [{}] {}'.format(cnt, address, code))
cnt += 1
class RISCV_CPU(CPU):
def __init__(self, name, elf):
super().__init__(name, elf)
self.toochain = 'riscv64-unknown-elf'
self.exceptions = {
0: 'Reserved',
1: 'Fetch Instruction Access Fault',
2: 'Illegal Instruction',
3: 'Breakpoint Fault',
4: 'Load Instruction Address Misaligned',
5: 'Load Instruction Access Fault',
6: 'Store/AMO Address Misaligned',
7: 'Store/AMO Access Fault',
8: 'Environment Call from U-Mode',
9: 'Environment Call from S-Mode',
11: 'Environment Call from M-Mode',
12: 'Fetch Instruction page error',
13: 'Load Instruction page error',
15: 'Store/AMO Instruction page error',
24: 'NMI',
}
self.reg_alias = {
'x0': 'zero',
'x1': 'ra',
'x2': 'sp',
'x3': 'gp',
'x4': 'tp',
'x5': 't0',
'x6': 't1',
'x7': 't2',
'x8': 's0/fp',
'x9': 's1',
'x10': 'a0',
'x11': 'a1',
'x12': 'a2',
'x13': 'a3',
'x14': 'a4',
'x15': 'a5',
'x16': 'a6',
'x17': 'a7',
'x18': 's2',
'x19': 's3',
'x20': 's4',
'x21': 's5',
'x22': 's6',
'x23': 's7',
'x24': 's8',
'x25': 's9',
'x26': 's10',
'x27': 's11',
'x28': 't3',
'x29': 't4',
'x30': 't5',
'x31': 't6',
}
def parse_cpu_regs(self, log):
with open(log, 'r') as f:
found = False
for line in f:
if not found:
if "CPU Exception:" in line:
match = re.search(r"CPU Exception: NO\.(\d+)", line)
self.exception = int(match.group(1)) if match else None
found = True
continue
if len(line) < 2:
continue
if 'mcause' in line:
self.regs['mcause'] = search_special_reg('mcause', line)
elif 'mtval' in line:
self.regs['mtval'] = search_special_reg('mtval', line)
elif 'mepc' in line:
self.regs['mepc'] = search_special_reg('mepc', line)
elif 'mstatus' in line:
self.regs['mstatus'] = search_special_reg('mstatus', line)
elif ': ' in line:
tmp = re.split('[: \t]', line.strip())
tmp = [i for i in tmp if len(i) > 0]
for i in range(int(len(tmp)/2)):
self.regs[tmp[i * 2]] = int(tmp[i * 2 + 1], 16)
if not found:
pr_err(f"Cannot found 'CPU Exception' in {log}")
class CSKY_CPU(CPU):
def __init__(self, name, elf):
super().__init__(name, elf)
self.toochain = 'csky-elf-noneabiv2'
self.exceptions = {
0: 'Reset Fault',
1: 'Instruction Address Misaligned',
2: 'Instruction Access Fault',
4: 'Illegal Instruction',
5: 'Privileged Instruction',
7: 'Breakpoint Fault',
8: 'Unrecoverable Fault',
16: 'TRAP0 Fault',
17: 'TRAP1 Fault',
18: 'TRAP2 Fault',
19: 'TRAP3 Fault',
22: 'Tspend interrupt',
}
self.reg_alias = {
'r14': 'sp',
'r15': 'lr',
'r23': 'fp',
'r24': 'top',
'r25': 'bsp',
'r30': 'svbr',
}
def parse_cpu_regs(self, log):
with open(log, 'r') as f:
found = False
for line in f:
if not found:
if "CPU Exception:" in line:
match = re.search(r"CPU Exception: NO\.(\d+)", line)
self.exception = int(match.group(1)) if match else None
found = True
continue
if len(line) < 2:
continue
if 'epsr' in line:
self.regs['epsr'] = search_special_reg('epsr', line)
elif 'epc' in line:
self.regs['epc'] = search_special_reg('epc', line)
elif ': ' in line:
tmp = re.split('[: \t]', line.strip())
tmp = [i for i in tmp if len(i) > 0]
for i in range(int(len(tmp)/2)):
self.regs[tmp[i * 2]] = int(tmp[i * 2 + 1], 16)
if not found:
pr_err(f"Cannot found 'CPU Exception' in {log}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--cpu", type=str, help="The name of CPU IP")
parser.add_argument("-e", "--elf", type=str, help="The name of ELF file")
parser.add_argument("-l", "--log", type=str, help="The name of panic log file")
parser.add_argument("-v", "--verbose", action='store_true', help="Verbose log")
args = parser.parse_args()
if not os.path.exists('tools/onestep.sh'):
pr_err('Must run the strip in the root director of Luban-Lite!')
sys.exit(1)
if args.verbose:
VERBOSE = True
if not args.log:
pr_err('Must given the name of panic log')
sys.exit(1)
if not os.path.exists(args.log):
pr_err(f'{args.log} does not exist')
sys.exit(1)
if not args.elf:
elf_file = scan_elf()
else:
elf_file = args.elf
if not check_elf(elf_file):
pr_warn("No ELF file found!")
elf_file = None
print()
if not args.cpu:
print('CPU: RISC-V')
CPU = RISCV_CPU('riscv', elf_file)
elif args.cpu.upper() in 'C906/E906/E907':
print(f'CPU: {args.cpu}')
CPU = RISCV_CPU(args.cpu.upper(), elf_file)
elif args.cpu.upper() in 'CK802':
print(f'CPU: {args.cpu}')
CPU = CSKY_CPU(args.cpu, elf_file)
else:
pr_err(f'Unknown CPU: {args.cpu}')
sys.exit(1)
if not CPU.check_toolchain():
sys.exit(1)
CPU.parse_cpu_regs(args.log)
CPU.parse_section(args.log)
CPU.show_cpu_regs(args.log, elf_file)
CPU.show_call_stack(args.log, elf_file)