diff --git a/Debugger.py b/Debugger.py index aafd6c3..92a350e 100644 --- a/Debugger.py +++ b/Debugger.py @@ -27,6 +27,8 @@ import argparse import os +from pathlib import Path +import socket __author__ = "CSE" __copyright__ = "Copyright 2015, CSE" @@ -73,49 +75,84 @@ def parseCommandLineArgs(): help="This is required if -s option was used on the linker. That will allow " "binary to be loader at correct address specified inside the binary") + parser.add_argument("-bp", "--breakpoint", + required=False, + type=str, + default='', + help="This is an optional argument. If present, debugger will load breakpoints " + "previously defined and stored in the specified file.") + args = parser.parse_args() return args -def validatePaths(argsWithPaths): +def validateFilePath(filename, ext=None): """ - This function will simply validate that the input path exists and that the output path - is free for the system to use - :param argsWithPaths: An input parsed object as provided by argparse.parse_args() - :return: This does not return. Simply raises ValueError in cases where paths are not valid. + This function will simply validate file existence and file extension. + If either is not true, then will raise an error. + :param filename: str, file path to be validated + :param ext: str, expected extension + :return: boolean True, """ - gotSymbols = False - if not os.path.exists(argsWithPaths.input): - raise ValueError("ERROR: file {} does not exists.".format(argsWithPaths.input,)) + if not os.path.exists(filename): + raise ValueError("ERROR: File {} does not exists.".format(filename,)) else: - if os.path.exists(argsWithPaths.input.split(".")[0] + ".sym"): - gotSymbols = True + fileExt = filename.split(".")[-1] + if fileExt != ext: + raise ValueError("ERROR: Incorrect file extension on file {}".format(filename,)) + + return True + - return gotSymbols +def validateBreakPointFile(usable_args): + """ + This function will check if user provided a breakpoint file. + If user provided a file that does not exist, function will create the file. + If user did not provide a file, no breakpoint file will be used. + :param usable_args: An input parsed object as provided by argparse.parse_args() + :return inputBreakpointFile: str, name of the breakpointFile that will be used by Debugger + """ + inputBreakpointFile = usable_args.breakpoint + + if not os.path.exists(inputBreakpointFile): + Path(inputBreakpointFile).touch() + return inputBreakpointFile + else: + return inputBreakpointFile if __name__ == '__main__': usableArgs = parseCommandLineArgs() + goodInputFile = False gotSymbols = False symbolsFile = None + breakpointFile = '' if usableArgs.input is not None: - gotSymbols = validatePaths(usableArgs) # Make sure the parsed info is usable before using it! - symbolsFile = usableArgs.input.split(".")[0] + ".sym" if gotSymbols else "" + # Make sure the parsed info is usable before using it! + goodInputFile = validateFilePath(usableArgs.input, ext="bin") + gotSymbols = validateFilePath(usableArgs.input.split(".")[0] + ".sym", ext="sym") + breakpointFile = validateBreakPointFile(usableArgs) else: usableArgs.input = FIRMWARE_BINARY_FILE_PATH usableArgs.software = False usableArgs.address = FIRMWARE_LOAD_ADDRESS + if gotSymbols: + symbolsFile = usableArgs.input.split(".")[0] + ".sym" print("Debug session about to begin, following options will be used") print(" input file: {}".format(usableArgs.input,)) if gotSymbols: - print(" symbols file: {}".format(symbolsFile,)) + print(" symbols file: {}".format(symbolsFile,)) if usableArgs.output is not None: - print(" output file: {}".format(usableArgs.output,)) + print(" output file: {}".format(usableArgs.output,)) + if breakpointFile is not '': + print(" breakpoint file: {}".format(breakpointFile,)) + else: + print(" session starting without breakpoint file") if usableArgs.address is None: usableArgs.address = DEFAULT_LOAD_ADDRESS @@ -124,7 +161,8 @@ def validatePaths(argsWithPaths): outputFile=usableArgs.output, loadAddress=usableArgs.address, softwareLoader=usableArgs.software, - symbolsFile=symbolsFile) + symbolsFile=symbolsFile, + breakpointFile=breakpointFile) if usableArgs.output is not None and os.path.exists(usableArgs.output[0]): # The assembler did the job correctly and the out file has been written to disk! print("Debug session is over, output file has been written to {}". format(usableArgs.output,)) diff --git a/ToolChain/Debugger/Debugger.py b/ToolChain/Debugger/Debugger.py index 5b548f1..2995024 100644 --- a/ToolChain/Debugger/Debugger.py +++ b/ToolChain/Debugger/Debugger.py @@ -58,17 +58,18 @@ class Debugger: will create an execution environment load the input file to that environment and start execution as required. """ - outputFile = None capua = None - breakPoints = None + breakPoints = [] symbols = None + breakpointFile = None def __init__(self, inputFile=None, outputFile=None, loadAddress=DEFAULT_LOAD_ADDRESS, softwareLoader=False, - symbolsFile=None): + symbolsFile=None, + breakpointFile=None): """ Building the debugger :param inputFile: The input file that needs to be loaded in memory @@ -76,10 +77,10 @@ def __init__(self, inputFile=None, :param loadAddress: int, the address a which the binary will be loaded. This is required to resolve ref address :param softwareLoader: bool, is the loading done with the "software" option :param symbolsFile: str, the path to the symbols file to be loaded + :param breakpointFile: str, path to breakpoints stored after adding breakpoints in debugger :return: """ - - self.breakPoints = [] + self.breakpointFile = breakpointFile # First thing we need is to setup logging facilities self.setupLoggingFacilities(outputFile) @@ -93,27 +94,62 @@ def __init__(self, inputFile=None, self.loadProgram(inputFile=inputFile, loadAddress=loadAddress, softwareLoader=softwareLoader) # If we have the symbols, load them into the appropriate member - if symbolsFile != "" and symbolsFile is not None: + if symbolsFile is not None: self.symbols = {} self.loadSymbols(symbolsFile=symbolsFile) + if self.breakpointFile is not '': + self.breakPoints = self.loadBreakpoints(self.breakpointFile) + # At this point, debugging session is ready to be used self.debugLog("Debugging session is ready to be used. Have fun!") self.debug(inputFile=inputFile) self.tearDownLoggingFacilities() + def loadBreakpoints(self, breakpointFile): + """ + This will simply load the breakpoints from file + :param breakpointFile: str, path to file storing breakpoints + :return: list, breakpoints contained in the breakpoint file + """ + self.debugLog("Loading breakpoints from file {}".format(breakpointFile, )) + + try: + with open(breakpointFile, "r") as file: + breakpointFileContent = file.readlines() + except FileNotFoundError as e: + raise FileNotFoundError("Error, file {} not found during breakpoint loading".format(breakpointFile,)) + + breakPoints = [] + lineNumber =1 + + for line in breakpointFileContent: + if line != "": + line = line.strip("\n") + try: + breakPoints.append(int(line)) + lineNumber += 1 + except ValueError as e: + raise ValueError("Error, invalid address at line {} in {}. " + "Address must be an integer.".format(lineNumber, breakpointFile,)) + + self.debugLog("Done loading breakpoints") + + return breakPoints + def loadSymbols(self, symbolsFile=None): """ This will simply load the symbols so that they can be used :param symbolsFile: str, path to the symbols file :return: """ - self.debugLog("Loading symbols from file {}".format(symbolsFile,)) - file = open(symbolsFile, "r") - content = file.readlines() - file.close() + try: + with open(symbolsFile, "r") as file: + content = file.readlines() + except FileNotFoundError as e: + raise FileNotFoundError("Error, symbols file {} not found during symbols loading".format(symbolsFile,)) for line in content: if line != "": @@ -129,7 +165,6 @@ def translateSymbolToAddress(self, symbol=None): :param symbol: str, the symbol to be translated :return: int, the address where to find the symbol, none if symbol is can't be resolved """ - if "." not in symbol: # User is trying to go without file resolution found = None @@ -196,7 +231,6 @@ def loadProgram(self, inputFile=None, loadAddress=DEFAULT_LOAD_ADDRESS, software :param softwareLoader: bool, is the loading done with the "software" option :return: """ - # First we get the content of the binary file that needs to be loaded into memory content = self.getBinary(inputFile) @@ -228,9 +262,11 @@ def getBinary(self, inputFile=None): """ content = b"" - binFile = open(inputFile, "rb") - content = binFile.read() - binFile.close() + try: + with open(inputFile, "rb") as binFile: + content = binFile.read() + except FileNotFoundError as e: + raise FileNotFoundError("Error, file {} not found while retrieving binary data".format(inputFile,)) return content @@ -240,7 +276,6 @@ def debug(self, inputFile=None): debugging session. :return: """ - while True: self.debugLog("\nNext instruction to be executed:") self.displayNextInstruction() @@ -266,7 +301,6 @@ def displayXInstructionAtAddress(self, address=None, x: int=None): :param x: int, how many instructions do we want to display :return: """ - # We have to validate the user used a symbol for the address if type(address) is not int: try: @@ -362,7 +396,6 @@ def displayCPUInformation(self, register: str=None): This will display cpu register to the console :return: """ - # Display all registers to the console self.debugLog("Register information for {}".format(self.capua.eu.name,)) self.debugLog("{} = {} {}".format("A ", self.capua.eu.A, hex(self.capua.eu.A),)) @@ -397,7 +430,6 @@ def displayMemoryInFormat(self, address=None, length: int=4, displayFormat: str= :param displayFormat: :return: Nothing, simply display the thing """ - # We have to validate the user used a symbol for the address try: address = int(address, 16) @@ -473,7 +505,6 @@ def runUserCommand(self, command: str=None): :param command: str, a string that needs to be parsed :return: """ - brokenCommand = command.split() if len(brokenCommand) == 0: @@ -554,11 +585,14 @@ def runToBreakPoint(self): def removeBreakPoint(self, number: int=None): """ This will remove a single breakpoint from the list of breakpoint + and updates the breakpoints in file :param number: breakpoint number to remove :return: """ self.breakPoints.remove(self.breakPoints[number]) self.breakPoints.sort() + if self.breakpointFile is not '': + self.writeBreakPointFile() def displayBreakPoints(self): """ @@ -577,6 +611,7 @@ def displayBreakPoints(self): def addBreakPoint(self, address=None): """ This will simply add a break point into the break point list + and updates the breakpoints in file :param address: a valid capua address written in hexadecimal :return: """ @@ -589,8 +624,25 @@ def addBreakPoint(self, address=None): self.debugLog("Error while processing address or symbol {}".format(address,)) return - self.breakPoints.append(address) - self.breakPoints.sort() + if address not in self.breakPoints: + self.breakPoints.append(address) + self.breakPoints.sort() + if self.breakpointFile is not '': + self.writeBreakPointFile() + else: + self.debugLog("Error breakpoint already exist") + + def writeBreakPointFile(self): + """ + This function writes the breakpoints stored in self.breakpoints to a file + :return: + """ + try: + with open(self.breakpointFile, "w") as file: + for item in self.breakPoints: + file.write("%s\n" % item) + except PermissionError as e: + raise PermissionError("Error, unable to update breakpoints. Permission denied {}".format(self.breakpointFile,)) def debugLog(self, message:str="", screenDisplay:bool=True): """ @@ -599,8 +651,7 @@ def debugLog(self, message:str="", screenDisplay:bool=True): :param screenDisplay: If true, the message will be sent to the display :return: Nothing """ - if self.outputFile is not None: self.outputFile.write(message) if screenDisplay: - print(message) + print(message) \ No newline at end of file