#! python3
'''
MDK反汇编指令:fromelf --text -a -c -o "$L@L.asm" "#L"
IAR反汇编指令:ielfdumparm --code --source $TARGET_PATH$ -o $TARGET_PATH$.dis
GCC反汇编指令:objdump -d $@ > $@.dis
'''
import os
import re
import sys
import ctypes
import collections
import configparser

from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QFileDialog


Function = collections.namedtuple('Function','start end callees callers')   # callees: 此函数调用的函数
                                                                            #   address1 callee1,此函数在address1处调用了callee1
                                                                            # callers: 调用此函数的函数
                                                                            #   caller1 address1,caller1在address1处调用了此函数

'''
from HFView_UI import Ui_HFView
class HFView(QWidget, Ui_HFView):
    def __init__(self, parent=None):
        super(HFView, self).__init__(parent)
        
        self.setupUi(self)
'''
class HFView(QWidget):
    def __init__(self, parent=None):
        super(HFView, self).__init__(parent)
        
        uic.loadUi('HFView.ui', self)

        self.initSetting()

        self.CPURegs = collections.OrderedDict([
            ('R0',        0),   # 0, jlink.JLINKARM_ReadReg index
            ('R1',        0),
            ('R2',        0),
            ('R3',        0),
            ('R4',        0),
            ('R5',        0),
            ('R6',        0),
            ('R7',        0),
            ('R8',        0),
            ('R9',        0),
            ('R10',       0),
            ('R11',       0),
            ('R12',       0),
            ('R13(SP)',   0),
            ('R14(LR)',   0),
            ('R15(PC)',   0),
            ('XPSR',      0),   # 16
            ('MSP',       0),
            ('PSP',       0),
            ('RAZ',       0),
            ('CFBP',      0),
            ('APSR',      0),
            ('EPSR',      0),
            ('IPSR',      0),
            ('PRIMASK',   0),
            ('BASEPRI',   0),
            ('FAULTMASK', 0),
            ('CONTROL',   0),   # 27
        ])
        
    def initSetting(self):
        if not os.path.exists('setting.ini'):
            open('setting.ini', 'w', encoding='utf-8')
        
        self.conf = configparser.ConfigParser()
        self.conf.read('setting.ini', encoding='utf-8')
        
        if not self.conf.has_section('globals'):
            self.conf.add_section('globals')
            self.conf.set('globals', 'dllpath', '')
            self.conf.set('globals', 'dispath', '[]')
        self.linDLL.setText(self.conf.get('globals', 'dllpath'))
        self.cmbDis.addItems(eval(self.conf.get('globals', 'dispath')))
    
    @pyqtSlot()
    def on_btnDLL_clicked(self):
        dllpath, filter = QFileDialog.getOpenFileName(caption='JLink_x64.dll路径', filter='动态链接库文件 (*.dll)', directory=self.linDLL.text())
        if dllpath != '':
            self.linDLL.setText(dllpath)
    
    @pyqtSlot()
    def on_btnDis_clicked(self):
        dispath, filter = QFileDialog.getOpenFileName(caption='反汇编文件路径', filter='disassembler (*.dis *.asm *.txt)', directory=self.cmbDis.currentText())
        if dispath != '':
            self.cmbDis.insertItem(0, dispath)
            self.cmbDis.setCurrentIndex(0)

    @pyqtSlot(str)
    def on_cmbDis_currentIndexChanged(self, txt):
        self.Functions = collections.OrderedDict()

    def parseDis(self, path):
        with open(path, 'r', encoding='utf-8', errors='ignore') as f:
            txt = f.read()

            self.Functions = collections.OrderedDict()
            self.parseDis_MDK(txt)
            if not self.Functions:
                self.parseDis_GCC(txt)
                if not self.Functions:
                    self.parseDis_IAR(txt)
                    if not self.Functions:
                        return              # disassembler parse fail

        for name in self.Functions:
            for name2, func in self.Functions.items():
                for addr, callee in func.callees:                           # name2调用的函数中有name
                    if name == callee:
                        self.Functions[name].callers.append((name2, addr))  # name的调用者中添加name2
                        # TODO: 如果name2中多处调用name,怎么处理
                        #break

        for name, func in self.Functions.items():
            print(f'\n{name:30s} @ 0x{func.start:08X} - 0x{func.end:08X}')
            for addr, name in func.callees:
                print(f'    0x{addr:08X} {name}')
        print('\n')
        for name, func in self.Functions.items():
            print(f'\n{name:30s} called by:')
            for name, addr in func.callers:
                print(f'    {name:30s} @ 0x{addr:08X}')

    def parseDis_MDK(self, txt):
        for match in re.finditer(r'\n    ([A-Za-z_][A-Za-z_0-9]*)\n(        (0x[0-9a-f]{8}):[\s\S]+?)(?=\n    [A-Za-z_\$])', txt):
            name, start, end = match.group(1), int(match.group(3), 16), int(match.group(3), 16)

            lastline = match.group(2).strip().split('\n')[-1]
            match2 = re.match(r'        (0x[0-9a-f]{8}):', lastline)
            if match2:
                end = int(match2.group(1), 16)

            self.Functions[name] = Function(start, end, [], [])

            for line in match.group(2).split('\n'):
                match2 = re.match(r'        (0x[0-9a-f]{8}):\s+[0-9a-f]{4,8}\s+\S+\s+B[L.W]*\s+([A-Za-z_][A-Za-z0-9_]*) ;', line)
                if match2:
                    address, callee = int(match2.group(1), 16), match2.group(2)
                    self.Functions[name].callees.append((address, callee))

    def parseDis_IAR(self, txt):
        for match in re.finditer(r"\n\s+([A-Za-z_][A-Za-z0-9_]+):\n(\s+(0x[0-9a-f']+): 0x[0-9a-f]{4}[\s\S]+?)\n\s+((// [}a-z])|(\$)|(`.text))", txt):
            name, start, end = match.group(1), int(match.group(3).replace("'", ''), 16), int(match.group(3).replace("'", ''), 16)

            lastline = match.group(2).strip().split('\n')[-1]
            match2 = re.match(r"\s+(0x[0-9a-f']+): 0x[0-9a-f]{4}", lastline)
            if match2:
                end = int(match2.group(1).replace("'", ''), 16)

            self.Functions[name] = Function(start, end, [], [])

            for line in match.group(2).split('\n'):
                match2 = re.match(r"\s+(0x[0-9a-f']+): 0x[0-9a-f]{4} 0x[0-9a-f]{4}\s+BL\s+([A-Za-z_][A-Za-z0-9_]+)\s+; 0x[0-9a-f']+", line)
                if match2:
                    address, callee = int(match2.group(1).replace("'", ''), 16), match2.group(2)
                    self.Functions[name].callees.append((address, callee))

    def parseDis_GCC(self, txt):
        for match in re.finditer(r'\n([0-9a-f]{8}) <([A-Za-z_][A-Za-z_0-9]*)>:([\s\S]+?)(?=\n\n)', txt):
            name, start, end = match.group(2), int(match.group(1), 16), int(match.group(1), 16)

            lastline = match.group(3).strip().split('\n')[-1]
            match2 = re.match(r'\s+([0-9a-f]{1,8}):\s+[0-9a-f]{4}', lastline)
            if match2:
                end = int(match2.group(1), 16)

            self.Functions[name] = Function(start, end, [], [])

            for line in match.group(3).split('\n'):
                match2 = re.match(r'\s+([0-9a-f]{1,8}):.+?bl\s+[0-9a-f]+\s<([A-Za-z_][A-Za-z0-9_]*)>', line)
                if match2:
                    address, callee = int(match2.group(1), 16), match2.group(2)
                    self.Functions[name].callees.append((address, callee))

    @pyqtSlot()
    def on_btnRead_clicked(self):
        try:
            if not os.path.exists(self.linDLL.text()):
                QMessageBox.critical(self, 'JLink_x64.dll路径错误', 'JLink_x64.dll路径错误,请更正')
                return

            self.jlink = ctypes.cdll.LoadLibrary(self.linDLL.text())

            self.jlink.JLINKARM_Open()
            if not self.jlink.JLINKARM_IsOpen():
                raise Exception('No JLink connected')

            BUFF_LEN = 64
            err_buf = (ctypes.c_char * BUFF_LEN)()
            res = self.jlink.JLINKARM_ExecCommand('Device = Cortex-M0', err_buf, BUFF_LEN)

            self.jlink.JLINKARM_TIF_Select(1)
            self.jlink.JLINKARM_SetSpeed(4000)

            self.jlink.JLINKARM_Halt()
            for i, reg in enumerate(self.CPURegs):
                self.CPURegs[reg] = self.jlink.JLINKARM_ReadReg(i)
                if self.CPURegs[reg] < 0: self.CPURegs[reg] += 0x100000000  # 返回值int类型,若大于0x80000000则会变成负数
            self.CPURegs['CONTROL'] >>= 24  # J-Link Control Panel 中显示的也是移位前的

            self.txtMain.append('\nCPU Registers:')
            self.txtMain.append(
                'R0  : 0x%08X      R1  : 0x%08X      R2  : 0x%08X      R3  : 0x%08X\n'
                'R4  : 0x%08X      R5  : 0x%08X      R6  : 0x%08X      R7  : 0x%08X\n'
                'R8  : 0x%08X      R9  : 0x%08X      R10 : 0x%08X      R11 : 0x%08X\n'
                'R12 : 0x%08X      SP  : 0x%08X      LR  : 0x%08X      PC  : 0x%08X\n'
                'MSP : 0x%08X      PSP : 0x%08X\n'
                'XPSR: 0x%08X      APSR: 0x%08X      EPSR: 0x%08X      IPSR: 0x%08X\n'
                'CONTROL: 0x%02X (when Thread mode: %s, use %s)\n'
                'BASEPRI: 0x%02X         PRIMASK: %d            FAULTMASK: %d'
              %(self.CPURegs['R0'],     self.CPURegs['R1'],     self.CPURegs['R2'],     self.CPURegs['R3'],
                self.CPURegs['R4'],     self.CPURegs['R5'],     self.CPURegs['R6'],     self.CPURegs['R7'],
                self.CPURegs['R8'],     self.CPURegs['R9'],     self.CPURegs['R10'],    self.CPURegs['R11'],
                self.CPURegs['R12'],    self.CPURegs['R13(SP)'],self.CPURegs['R14(LR)'],self.CPURegs['R15(PC)'],
                self.CPURegs['MSP'],    self.CPURegs['PSP'],
                self.CPURegs['XPSR'],   self.CPURegs['APSR'],   self.CPURegs['EPSR'],   self.CPURegs['IPSR'],
                self.CPURegs['CONTROL'], 'unprivileged' if self.CPURegs['CONTROL']&1 else 'privileged', 'PSP' if self.CPURegs['CONTROL']&2 else 'MSP',
                self.CPURegs['BASEPRI'],self.CPURegs['PRIMASK'],self.CPURegs['FAULTMASK']
                ))
            
            if self.CPURegs['IPSR'] == 3:
                self.fault_diagnosis()

            if (self.CPURegs['R14(LR)'] >> 2) & 1 == 0:
                self.Stack_SP = self.CPURegs['MSP']
            else:
                self.Stack_SP = self.CPURegs['PSP']

            self.Stack_LEN = 64
            self.Stack_Mem = (ctypes.c_uint32 * self.Stack_LEN)()
            self.jlink.JLINKARM_ReadMemU32(self.Stack_SP, self.Stack_LEN, self.Stack_Mem, 0)
            self.jlink.JLINKARM_Close()

            self.txtMain.append('\nStack Content @ 0x%08X:' %self.Stack_SP)
            for i in range(self.Stack_LEN // 8):
                self.txtMain.append('%08X:  %08X %08X %08X %08X %08X %08X %08X %08X'
                    %(self.Stack_SP+i*8*4, self.Stack_Mem[i*8], self.Stack_Mem[i*8+1], self.Stack_Mem[i*8+2], self.Stack_Mem[i*8+3], self.Stack_Mem[i*8+4], self.Stack_Mem[i*8+5], self.Stack_Mem[i*8+6], self.Stack_Mem[i*8+7]))
            if self.Stack_LEN % 8:
                self.txtMain.append('%08X:  %s' %(self.Stack_SP+(self.Stack_LEN // 8)*8*4, ' '.join('%08X' %self.Stack_Mem[(self.Stack_LEN // 8)*8+i] for i in range(self.Stack_LEN % 8))))

        except Exception as e:
            self.txtMain.append(f'\nError:\n{e}')

    @pyqtSlot()
    def on_btnParse_clicked(self):
        if not os.path.exists(self.cmbDis.currentText()):
            QMessageBox.critical(self, '反汇编文件路径错误', '反汇编文件路径错误,请更正')
            return

        self.parseDis(self.cmbDis.currentText())
        if not self.Functions:
            self.txtMain.append('\nDisassembler parse fail!\n')
            return

        self.Program_Start = min([func.start for (name, func) in self.Functions.items()])
        self.Program_End   = max([func.end   for (name, func) in self.Functions.items()])

        self.on_btnRead_clicked()
        if self.CPURegs['IPSR'] != 3:
            self.txtMain.append('\nNot in HardFault\n')
            return

        self.CallStack, index = [], 0
        while index < self.Stack_LEN:
            if ((index <= self.Stack_LEN - 8) and
                (self.Program_Start <= self.Stack_Mem[index+5] <= self.Program_End and self.Stack_Mem[index+5]%2 == 1) and 
                (self.Program_Start <= self.Stack_Mem[index+6] <= self.Program_End and self.Stack_Mem[index+6]%2 == 0) and 
                ((self.Stack_Mem[index+7] >> 24) & 1 == 1)):    # 中断服务

                for name, func in self.Functions.items():       # 找出中断压栈时正在执行的函数
                    if func.start <= self.Stack_Mem[index+6] <= func.end:
                        self.CallStack.append((self.Stack_Mem[index+6], name))
                        break
                else:
                    self.txtMain.append('\nCannot find the function be interrupted\n')
                    break

                index += 8

            elif index == 0:
                self.txtMain.append('\nPlease make sure the stack is an Exception Stack Frame\n')
                return

            else:                                               # 函数调用
                for name, addr in self.Functions[self.CallStack[-1][1]].callers: # 遍历函数的调用者,谁在栈内
                    if self.Stack_Mem[index] == addr + 4 + 1:   # 存入LR的值是函数调用指令地址 + 4,然后地址低位为1
                        self.CallStack.append((self.Stack_Mem[index], name))
                        break

                index += 1
                
        self.txtMain.append('\nCall Stack:')
        for addr, name in self.CallStack:
            self.txtMain.append('0x%08X  %s' %(addr, name))

    def fault_diagnosis(self):
        SCS_BASE  =  0xE000E000
        SCB_BASE  = (SCS_BASE + 0x0D00)

        SCB_CPUID = (SCB_BASE + 0x00)   # 
        SCB_CFSR  = (SCB_BASE + 0x28)   # Configurable Fault Status Register
        SCB_HFSR  = (SCB_BASE + 0x2C)   # HardFault Status Register
        SCB_DFSR  = (SCB_BASE + 0x30)   # Debug Fault Status Register
        SCB_MFAR  = (SCB_BASE + 0x34)   # MemManage Fault Address Register
        SCB_BFAR  = (SCB_BASE + 0x38)   # BusFault Address Register
        SCB_AFSR  = (SCB_BASE + 0x3C)   # Auxiliary Fault Status Register

        SCB_CPUID_PARTNO_Pos        =  4
        SCB_CPUID_PARTNO_Msk        = (0xFFF << SCB_CPUID_PARTNO_Pos)
        SCB_CPUID_ARCHITECTURE_Pos  = 16
        SCB_CPUID_ARCHITECTURE_Msk  = (0xF   << SCB_CPUID_ARCHITECTURE_Pos)

        # HFSR: HardFault Status Register
        SCB_HFSR_VECTTBL_Pos        =  1        # Indicates hard fault is caused by failed vector fetch
        SCB_HFSR_VECTTBL_Msk        = (1 << SCB_HFSR_VECTTBL_Pos)
        SCB_HFSR_FORCED_Pos         = 30        # Indicates hard fault is taken because of bus fault/memory management fault/usage fault
        SCB_HFSR_FORCED_Msk         = (1 << SCB_HFSR_FORCED_Pos)
        SCB_HFSR_DEBUGEVT_Pos       = 31        # Indicates hard fault is triggered by debug event
        SCB_HFSR_DEBUGEVT_Msk       = (1 << SCB_HFSR_DEBUGEVT_Pos)

        # MFSR: MemManage Fault Status Register
        SCB_MFSR_IACCVIOL_Pos       = 0         # Instruction access violation
        SCB_MFSR_IACCVIOL_Msk       = (1 << SCB_MFSR_IACCVIOL_Pos)
        SCB_MFSR_DACCVIOL_Pos       = 1         # Data access violation
        SCB_MFSR_DACCVIOL_Msk       = (1 << SCB_MFSR_DACCVIOL_Pos)
        SCB_MFSR_MUNSTKERR_Pos      = 3         # Unstacking error
        SCB_MFSR_MUNSTKERR_Msk      = (1 << SCB_MFSR_MUNSTKERR_Pos)
        SCB_MFSR_MSTKERR_Pos        = 4         # Stacking error
        SCB_MFSR_MSTKERR_Msk        = (1 << SCB_MFSR_MSTKERR_Pos)
        SCB_MFSR_MMARVALID_Pos      = 7         # Indicates the MMAR is valid
        SCB_MFSR_MMARVALID_Msk      = (1 << SCB_MFSR_MMARVALID_Pos)

        # BFSR: Bus Fault Status Register
        SCB_BFSR_IBUSERR_Pos        = 8         # Instruction access violation
        SCB_BFSR_IBUSERR_Msk        = (1 << SCB_BFSR_IBUSERR_Pos)
        SCB_BFSR_PRECISERR_Pos      = 9         # Precise data access violation
        SCB_BFSR_PRECISERR_Msk      = (1 << SCB_BFSR_PRECISERR_Pos)
        SCB_BFSR_IMPREISERR_Pos     = 10        # Imprecise data access violation
        SCB_BFSR_IMPREISERR_Msk     = (1 << SCB_BFSR_IMPREISERR_Pos)
        SCB_BFSR_UNSTKERR_Pos       = 11        # Unstacking error
        SCB_BFSR_UNSTKERR_Msk       = (1 << SCB_BFSR_UNSTKERR_Pos)
        SCB_BFSR_STKERR_Pos         = 12        # Stacking error
        SCB_BFSR_STKERR_Msk         = (1 << SCB_BFSR_STKERR_Pos)
        SCB_BFSR_BFARVALID_Pos      = 15        # Indicates BFAR is valid
        SCB_BFSR_BFARVALID_Msk      = (1 << SCB_BFSR_BFARVALID_Pos)

        # UFSR: Usage Fault Status Register
        SCB_UFSR_UNDEFINSTR_Pos     = 16        # Attempts to execute an undefined instruction
        SCB_UFSR_UNDEFINSTR_Msk     = (1 << SCB_UFSR_UNDEFINSTR_Pos)
        SCB_UFSR_INVSTATE_Pos       = 17        # Attempts to switch to an invalid state (e.g., ARM)
        SCB_UFSR_INVSTATE_Msk       = (1 << SCB_UFSR_INVSTATE_Pos)
        SCB_UFSR_INVPC_Pos          = 18        # Attempts to do an exception with a bad value in the EXC_RETURN number
        SCB_UFSR_INVPC_Msk          = (1 << SCB_UFSR_INVPC_Pos)
        SCB_UFSR_NOCP_Pos           = 19        # Attempts to execute a coprocessor instruction
        SCB_UFSR_NOCP_Msk           = (1 << SCB_UFSR_NOCP_Pos)
        SCB_UFSR_UNALIGNED_Pos      = 24        # Indicates that an unaligned access fault has taken place
        SCB_UFSR_UNALIGNED_Msk      = (1 << SCB_UFSR_UNALIGNED_Pos)
        SCB_UFSR_DIVBYZERO0_Pos     = 25        # Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set)
        SCB_UFSR_DIVBYZERO0_Msk     = (1 << SCB_UFSR_DIVBYZERO0_Pos)

        buff = (ctypes.c_uint32 * 16)()
        self.jlink.JLINKARM_ReadMemU32(SCB_CPUID, 16, buff, 0)

        reg_CPUID, reg_CFSR, reg_HFSR, reg_MFAR, reg_BFAR = buff[0], buff[10], buff[11], buff[13], buff[14]

        if ((reg_CPUID & SCB_CPUID_PARTNO_Msk) >> SCB_CPUID_PARTNO_Pos) in [0xC20, 0xC60]: # Cortex-M0, Cortex-M0+
            return

        self.txtMain.append('')
        if (reg_HFSR & SCB_HFSR_VECTTBL_Msk):
            self.txtMain.append('hard fault is caused by failed vector fetch')
        elif (reg_HFSR & SCB_HFSR_FORCED_Msk):
            if (reg_CFSR & (0xFF << 0)):        # Memory Management Fault
                if (reg_CFSR & SCB_MFSR_IACCVIOL_Msk):
                    self.txtMain.append('Instruction access violation')
                if (reg_CFSR & SCB_MFSR_DACCVIOL_Msk):
                    self.txtMain.append('Data access violation')
                if (reg_CFSR & SCB_MFSR_MUNSTKERR_Msk):
                    self.txtMain.append('Unstacking error')
                if (reg_CFSR & SCB_MFSR_MSTKERR_Msk):
                    self.txtMain.append('Stacking error')
                if (reg_CFSR & SCB_MFSR_MMARVALID_Msk):
                    self.txtMain.append('SCB->MFAR = 0x%08X' %reg_MFAR)
            if (reg_CFSR & (0xFF << 8)):        # Bus Fault
                if (reg_CFSR & SCB_BFSR_IBUSERR_Msk):
                    self.txtMain.append('Instruction access violation')
                if (reg_CFSR & SCB_BFSR_PRECISERR_Msk):
                    self.txtMain.append('Precise data access violation')
                if (reg_CFSR & SCB_BFSR_IMPREISERR_Msk):
                    self.txtMain.append('Imprecise data access violation')
                if (reg_CFSR & SCB_BFSR_UNSTKERR_Msk):
                    self.txtMain.append('Unstacking error')
                if (reg_CFSR & SCB_BFSR_STKERR_Msk):
                    self.txtMain.append('Stacking error')
                if (reg_CFSR & SCB_BFSR_BFARVALID_Msk):
                    self.txtMain.append('SCB->BFAR = 0x%08X' %reg_BFAR)
            if (reg_CFSR & (0xFFFF << 16)):     # Usage Fault
                if (reg_CFSR & SCB_UFSR_UNDEFINSTR_Msk):
                    self.txtMain.append('Attempts to execute an undefined instruction')
                if (reg_CFSR & SCB_UFSR_INVSTATE_Msk):
                    self.txtMain.append('Attempts to switch to an invalid state (e.g., ARM)')
                if (reg_CFSR & SCB_UFSR_INVPC_Msk):
                    self.txtMain.append('Attempts to do an exception with a bad value in the EXC_RETURN number')
                if (reg_CFSR & SCB_UFSR_NOCP_Msk):
                    self.txtMain.append('Attempts to execute a coprocessor instruction')
                if (reg_CFSR & SCB_UFSR_UNALIGNED_Msk):
                    self.txtMain.append('an unaligned access fault has taken place')
                if (reg_CFSR & SCB_UFSR_DIVBYZERO0_Msk):
                    self.txtMain.append('a divide by zero has taken place (can be set only if DIV_0_TRP is set)')

    @pyqtSlot()
    def on_btnClear_clicked(self):
        self.txtMain.clear()
    
    def closeEvent(self, evt):
        self.closed = True
        
        self.conf.set('globals', 'dllpath', self.linDLL.text())
        
        dispath = [self.cmbDis.currentText()] + [self.cmbDis.itemText(i) for i in range(self.cmbDis.count())]
        self.conf.set('globals', 'dispath', repr(list(collections.OrderedDict.fromkeys(dispath))))   # 保留顺序去重

        self.conf.write(open('setting.ini', 'w', encoding='utf-8'))
        

if __name__ == "__main__":
    app = QApplication(sys.argv)
    rtt = HFView()
    rtt.show()
    app.exec()