diff --git a/pyproject.toml b/pyproject.toml index 473046bd..0803c847 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [project] name = "spimdisasm" # Version should be synced with spimdisasm/__init__.py -version = "1.16.3" +version = "1.16.4" description = "MIPS disassembler" # license = "MIT" readme = "README.md" diff --git a/spimdisasm/__init__.py b/spimdisasm/__init__.py index 5607568f..dd8aa96c 100644 --- a/spimdisasm/__init__.py +++ b/spimdisasm/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations -__version_info__: tuple[int, int, int] = (1, 16, 3) +__version_info__: tuple[int, int, int] = (1, 16, 4) __version__ = ".".join(map(str, __version_info__)) __author__ = "Decompollaborate" diff --git a/spimdisasm/common/ContextSymbols.py b/spimdisasm/common/ContextSymbols.py index 11a88d5e..86a4d610 100644 --- a/spimdisasm/common/ContextSymbols.py +++ b/spimdisasm/common/ContextSymbols.py @@ -178,6 +178,8 @@ class ContextSymbol: allowedToReferenceConstants: bool = False notAllowedToReferenceConstants: bool = False + isAutocreatedSymFromOtherSizedSym: bool = False + @property def vram(self) -> int: @@ -221,6 +223,9 @@ def isTrustableFunction(self, rsp: bool=False) -> bool: if self.isGotLocal: return False + if self.isAutocreatedSymFromOtherSizedSym: + return True + currentType = self.getTypeSpecial() if GlobalConfig.TRUST_USER_FUNCTIONS and self.isUserDeclared: @@ -566,7 +571,8 @@ def getCsvHeader() -> str: output += "isDefined,isUserDeclared,isAutogenerated,isMaybeString,isMaybePascalString," output += "referenceCounter,overlayCategory,unknownSegment," output += "isGot,isGotGlobal,isGotLocal,gotIndex," - output += "firstLoAccess,isAutogeneratedPad,isElfNotype" + output += "firstLoAccess,isAutogeneratedPad,isElfNotype," + output += "isAutocreatedSymFromOtherSizedSym" return output def toCsv(self) -> str: @@ -591,7 +597,8 @@ def toCsv(self) -> str: output += f"{self.isDefined},{self.isUserDeclared},{self.isAutogenerated},{self.isMaybeString},{self.isMaybePascalString}," output += f"{self.referenceCounter},{self.overlayCategory},{self.unknownSegment}," output += f"{self.isGot},{self.isGotGlobal},{self.isGotLocal},{self.gotIndex}," - output += f"{self.firstLoAccess},{self.isAutogeneratedPad()},{self.isElfNotype}" + output += f"{self.firstLoAccess},{self.isAutogeneratedPad()},{self.isElfNotype}," + output += f"{self.isAutocreatedSymFromOtherSizedSym}" return output diff --git a/spimdisasm/common/GlobalConfig.py b/spimdisasm/common/GlobalConfig.py index 846fef17..48d1a784 100644 --- a/spimdisasm/common/GlobalConfig.py +++ b/spimdisasm/common/GlobalConfig.py @@ -54,9 +54,12 @@ def fromStr(value: str) -> Compiler: class Abi(enum.Enum): - O32 = "O32" - N32 = "N32" - N64 = "N64" + O32 = "O32" + N32 = "N32" + O64 = "O64" + N64 = "N64" + EABI32 = "EABI32" + EABI64 = "EABI64" @staticmethod def fromStr(value: str) -> Abi: @@ -311,7 +314,7 @@ def addParametersToArgParse(self, parser: argparse.ArgumentParser): backendConfig.add_argument("--endian", help=f"Set the endianness of input files. Defaults to {self.ENDIAN.name.lower()}", choices=["big", "little", "middle"], default=self.ENDIAN.name.lower()) - backendConfig.add_argument("--abi", help=f"Changes the ABI of the disassembly, applying corresponding tweaks. Defaults to {self.ABI.name}", choices=["O32", "N32", "N64"], default=self.ABI.name) + backendConfig.add_argument("--abi", help=f"Changes the ABI of the disassembly, applying corresponding tweaks. Defaults to {self.ABI.name}", choices=["O32", "N32", "O64", "N64", "EABI32", "EABI64"], default=self.ABI.name) backendConfig.add_argument("--arch-level", help=f"Changes the arch level of the disassembly, applying corresponding tweaks. Defaults to {self.ARCHLEVEL.name}", choices=archLevelOptions, default=self.ARCHLEVEL.name) diff --git a/spimdisasm/elf32/Elf32Constants.py b/spimdisasm/elf32/Elf32Constants.py index 951332a1..c31b29a2 100644 --- a/spimdisasm/elf32/Elf32Constants.py +++ b/spimdisasm/elf32/Elf32Constants.py @@ -74,6 +74,41 @@ class Elf32HeaderFlag(enum.Enum): FP64 = 0x00000200 # Uses FP64 (12 callee-saved). NAN2008 = 0x00000400 # Uses IEEE 754-2008 NaN encoding. + + # ABI + O32 = 0x00001000 # The original o32 abi. + O64 = 0x00002000 # O32 extended to work on 64 bit architectures + EABI32 = 0x00003000 # EABI in 32 bit mode + EABI64 = 0x00004000 # EABI in 64 bit mode + + + # CPU + CPU = 0x00FF0000 + + _3900 = 0x00810000 + _4010 = 0x00820000 + _4100 = 0x00830000 + _4650 = 0x00850000 + _4120 = 0x00870000 + _4111 = 0x00880000 + SB1 = 0x008a0000 + OCTEON = 0x008b0000 + XLR = 0x008c0000 + OCTEON2 = 0x008d0000 + OCTEON3 = 0x008e0000 + _5400 = 0x00910000 + _5900 = 0x00920000 + IAMR2 = 0x00930000 + _5500 = 0x00980000 + _9000 = 0x00990000 + LS2E = 0x00A00000 + LS2F = 0x00A10000 + GS464 = 0x00A20000 + GS464E = 0x00A30000 + GS264E = 0x00A40000 + + + # arch level ARCH = 0xF0000000 # MIPS architecture level. # Legal values for MIPS architecture level @@ -95,6 +130,7 @@ def parseFlags(rawFlags: int) -> tuple[list[Elf32HeaderFlag], int]: Elf32HeaderFlag.XGOT, Elf32HeaderFlag.F_64BIT_WHIRL, Elf32HeaderFlag.ABI2, Elf32HeaderFlag.ABI_ON32, Elf32HeaderFlag._32BITSMODE, Elf32HeaderFlag.FP64, Elf32HeaderFlag.NAN2008, + Elf32HeaderFlag.O32, Elf32HeaderFlag.O64, Elf32HeaderFlag.EABI32, Elf32HeaderFlag.EABI64 ] parsedFlags: list[Elf32HeaderFlag] = list() @@ -103,6 +139,53 @@ def parseFlags(rawFlags: int) -> tuple[list[Elf32HeaderFlag], int]: parsedFlags.append(flagEnum) rawFlags &= ~flagEnum.value + cpu = rawFlags & Elf32HeaderFlag.CPU.value + rawFlags &= ~Elf32HeaderFlag.CPU.value + if cpu == Elf32HeaderFlag._3900.value: + parsedFlags.append(Elf32HeaderFlag._3900) + elif cpu == Elf32HeaderFlag._4010.value: + parsedFlags.append(Elf32HeaderFlag._4010) + elif cpu == Elf32HeaderFlag._4100.value: + parsedFlags.append(Elf32HeaderFlag._4100) + elif cpu == Elf32HeaderFlag._4650.value: + parsedFlags.append(Elf32HeaderFlag._4650) + elif cpu == Elf32HeaderFlag._4120.value: + parsedFlags.append(Elf32HeaderFlag._4120) + elif cpu == Elf32HeaderFlag._4111.value: + parsedFlags.append(Elf32HeaderFlag._4111) + elif cpu == Elf32HeaderFlag.SB1.value: + parsedFlags.append(Elf32HeaderFlag.SB1) + elif cpu == Elf32HeaderFlag.OCTEON.value: + parsedFlags.append(Elf32HeaderFlag.OCTEON) + elif cpu == Elf32HeaderFlag.XLR.value: + parsedFlags.append(Elf32HeaderFlag.XLR) + elif cpu == Elf32HeaderFlag.OCTEON2.value: + parsedFlags.append(Elf32HeaderFlag.OCTEON2) + elif cpu == Elf32HeaderFlag.OCTEON3.value: + parsedFlags.append(Elf32HeaderFlag.OCTEON3) + elif cpu == Elf32HeaderFlag._5400.value: + parsedFlags.append(Elf32HeaderFlag._5400) + elif cpu == Elf32HeaderFlag._5900.value: + parsedFlags.append(Elf32HeaderFlag._5900) + elif cpu == Elf32HeaderFlag.IAMR2.value: + parsedFlags.append(Elf32HeaderFlag.IAMR2) + elif cpu == Elf32HeaderFlag._5500.value: + parsedFlags.append(Elf32HeaderFlag._5500) + elif cpu == Elf32HeaderFlag._9000.value: + parsedFlags.append(Elf32HeaderFlag._9000) + elif cpu == Elf32HeaderFlag.LS2E.value: + parsedFlags.append(Elf32HeaderFlag.LS2E) + elif cpu == Elf32HeaderFlag.LS2F.value: + parsedFlags.append(Elf32HeaderFlag.LS2F) + elif cpu == Elf32HeaderFlag.GS464.value: + parsedFlags.append(Elf32HeaderFlag.GS464) + elif cpu == Elf32HeaderFlag.GS464E.value: + parsedFlags.append(Elf32HeaderFlag.GS464E) + elif cpu == Elf32HeaderFlag.GS264E.value: + parsedFlags.append(Elf32HeaderFlag.GS264E) + else: + rawFlags |= cpu + archLevel = rawFlags & Elf32HeaderFlag.ARCH.value rawFlags &= ~Elf32HeaderFlag.ARCH.value if archLevel == Elf32HeaderFlag.ARCH_1.value: diff --git a/spimdisasm/elf32/Elf32File.py b/spimdisasm/elf32/Elf32File.py index 070bd130..3d5c24b4 100644 --- a/spimdisasm/elf32/Elf32File.py +++ b/spimdisasm/elf32/Elf32File.py @@ -125,9 +125,23 @@ def handleFlags(self) -> None: common.Utils.eprint(f"Warning: Elf with NAN2008 flag.") common.Utils.eprint(f"\t This flag is currently not handled in any way, please report this") - if Elf32HeaderFlag.ABI2 in self.elfFlags: + if Elf32HeaderFlag.ABI2 in self.elfFlags and Elf32HeaderFlag.O64 in self.elfFlags: + common.Utils.eprint(f"Warning: Elf compiled using N64 ABI. Support is in experimental state") + common.GlobalConfig.ABI = common.Abi.N32 + elif Elf32HeaderFlag.ABI2 in self.elfFlags: common.Utils.eprint(f"Warning: Elf compiled using N32 ABI. Support is in experimental state") common.GlobalConfig.ABI = common.Abi.N32 + elif Elf32HeaderFlag.O64 in self.elfFlags: + common.Utils.eprint(f"Warning: Elf compiled using O64 ABI. Support is in experimental state") + common.GlobalConfig.ABI = common.Abi.O64 + + if Elf32HeaderFlag.EABI32 in self.elfFlags: + common.Utils.eprint(f"Warning: Elf compiled using EABI32 ABI. Support is in experimental state") + common.GlobalConfig.ABI = common.Abi.EABI32 + + if Elf32HeaderFlag.EABI64 in self.elfFlags: + common.Utils.eprint(f"Warning: Elf compiled using EABI64 ABI. Support is in experimental state") + common.GlobalConfig.ABI = common.Abi.N32 unkArchLevel = {Elf32HeaderFlag.ARCH_5, Elf32HeaderFlag.ARCH_32, Elf32HeaderFlag.ARCH_64, Elf32HeaderFlag.ARCH_32R2, Elf32HeaderFlag.ARCH_64R2} & set(self.elfFlags) if unkArchLevel: diff --git a/spimdisasm/elfObjDisasm/ElfObjDisasmInternals.py b/spimdisasm/elfObjDisasm/ElfObjDisasmInternals.py index bb6f05f2..2358b639 100644 --- a/spimdisasm/elfObjDisasm/ElfObjDisasmInternals.py +++ b/spimdisasm/elfObjDisasm/ElfObjDisasmInternals.py @@ -7,6 +7,7 @@ import argparse from pathlib import Path +import rabbitizer from .. import common from .. import elf32 @@ -246,7 +247,9 @@ def addContextSymFromSymEntry(context: common.Context, symEntry: elf32.Elf32SymE contextSym = segment.addSymbol(symAddress, vromAddress=symVrom) contextSym.isElfNotype = True else: - common.Utils.eprint(f"Warning: NOTYPE symbol '{symName}' has an unhandled shndx value: '0x{symEntry.shndx:X}'") + bind = elf32.Elf32SymbolTableBinding.fromValue(symEntry.stBind) + if bind != elf32.Elf32SymbolTableBinding.LOCAL: + common.Utils.eprint(f"Warning: Non-LOCAL ({bind}) NOTYPE symbol '{symName}' has an unhandled shndx value: '0x{symEntry.shndx:X}'") contextSym = segment.addSymbol(symAddress, vromAddress=symVrom) else: common.Utils.eprint(f"Warning: symbol '{symName}' has an unhandled stType: '{symEntry.stType}'") @@ -358,11 +361,13 @@ def injectAllElfSymbols(context: common.Context, elfFile: elf32.Elf32File, proce return def processGlobalOffsetTable(context: common.Context, elfFile: elf32.Elf32File) -> None: - if elfFile.dynamic is not None and elfFile.got is not None: + if elfFile.dynamic is not None: common.GlobalConfig.GP_VALUE = elfFile.dynamic.getGpValue() - if elfFile.reginfo is not None: - common.GlobalConfig.GP_VALUE = elfFile.reginfo.gpValue + if elfFile.reginfo is not None: + common.GlobalConfig.GP_VALUE = elfFile.reginfo.gpValue + + if elfFile.dynamic is not None and elfFile.got is not None: globalsTable = [gotEntry.getAddress() for gotEntry in elfFile.got.globalsTable] assert elfFile.dynamic.pltGot is not None @@ -390,6 +395,11 @@ def processArguments(args: argparse.Namespace) -> int: elfFile.handleHeaderIdent() elfFile.handleFlags() + instrCategory = args.instr_category + if instrCategory is None: + if elf32.Elf32Constants.Elf32HeaderFlag._5900 in elfFile.elfFlags: + instrCategory = "r5900" + common.Utils.printQuietless(f"{PROGNAME} {inputPath}: Processing global offset table...") processGlobalOffsetTable(context, elfFile) @@ -423,7 +433,7 @@ def processArguments(args: argparse.Namespace) -> int: changeGlobalSegmentRanges(context, processedSegments) - fec.FrontendUtilities.configureProcessedFiles(processedSegments, args.instr_category) + fec.FrontendUtilities.configureProcessedFiles(processedSegments, instrCategory) common.Utils.printQuietless(f"{PROGNAME} {inputPath}: Injecting elf symbols...") injectAllElfSymbols(context, elfFile, processedSegments, sectionsPerName) diff --git a/spimdisasm/frontendCommon/FrontendUtilities.py b/spimdisasm/frontendCommon/FrontendUtilities.py index ce831182..36204122 100644 --- a/spimdisasm/frontendCommon/FrontendUtilities.py +++ b/spimdisasm/frontendCommon/FrontendUtilities.py @@ -80,7 +80,7 @@ def getInstrCategoryFromStr(category: str|None) -> rabbitizer.Enum: return gInstrCategoriesNameMap.get(category.lower(), rabbitizer.InstrCategory.CPU) -def configureProcessedFiles(processedFiles: dict[common.FileSectionType, list[mips.sections.SectionBase]], category: str) -> None: +def configureProcessedFiles(processedFiles: dict[common.FileSectionType, list[mips.sections.SectionBase]], category: str|None) -> None: instrCat = getInstrCategoryFromStr(category) for textFile in processedFiles.get(common.FileSectionType.Text, []): diff --git a/spimdisasm/mips/sections/MipsSectionText.py b/spimdisasm/mips/sections/MipsSectionText.py index 454acfdd..a43135d0 100644 --- a/spimdisasm/mips/sections/MipsSectionText.py +++ b/spimdisasm/mips/sections/MipsSectionText.py @@ -49,14 +49,118 @@ def tryDetectRedundantFunctionEnd(self) -> bool: return common.GlobalConfig.DETECT_REDUNDANT_FUNCTION_END return self.detectRedundantFunctionEnd - def _findFunctions(self, instrsList: list[rabbitizer.Instruction]): - if len(instrsList) == 0: + + def _findFunctions_branchChecker(self, instructionOffset: int, instr: rabbitizer.Instruction, funcsStartsList: list[int], unimplementedInstructionsFuncList: list[bool], farthestBranch: int, isLikelyHandwritten: bool, isInstrImplemented: bool) -> tuple[int, bool]: + haltFunctionSearching = False + + if instr.isJumpWithAddress(): + # If this instruction is a jump and it is jumping to a function then + # don't treat it as a branch, it is probably actually being used as + # a jump + targetVram = instr.getInstrIndexAsVram() + auxSym = self.getSymbol(targetVram, tryPlusOffset=False, checkGlobalSegment=False) + + if auxSym is not None and auxSym.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): + return farthestBranch, haltFunctionSearching + + branchOffset = instr.getBranchOffsetGeneric() + if branchOffset > farthestBranch: + # keep track of the farthest branch target + farthestBranch = branchOffset + if branchOffset < 0: + if branchOffset + instructionOffset < 0: + # Whatever we are reading is not a valid instruction + if not instr.isJump(): # Make an exception for `j` + haltFunctionSearching = True + # make sure to not branch outside of the current function + if not isLikelyHandwritten and isInstrImplemented: + j = len(funcsStartsList) - 1 + while j >= 0: + if branchOffset + instructionOffset < 0: + break + otherFuncStartOffset = funcsStartsList[j] * 4 + if (branchOffset + instructionOffset) < otherFuncStartOffset: + vram = self.getVramOffset(otherFuncStartOffset) + vromAddress = self.getVromOffset(otherFuncStartOffset) + funcSymbol = self.getSymbol(vram, vromAddress=vromAddress, tryPlusOffset=False, checkGlobalSegment=False) + if funcSymbol is not None and funcSymbol.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): + j -= 1 + continue + del funcsStartsList[j] + del unimplementedInstructionsFuncList[j-1] + else: + break + j -= 1 + return farthestBranch, haltFunctionSearching + + def _findFunctions_checkFunctionEnded(self, instructionOffset: int, instr: rabbitizer.Instruction, index: int, currentVrom: int, currentVram: int, currentFunctionSym: common.ContextSymbol|None, farthestBranch: int, currentInstructionStart: int, isLikelyHandwritten: bool, instrsList: list[rabbitizer.Instruction], nInstr: int) -> tuple[bool, bool]: + functionEnded = False + prevFuncHadUserDeclaredSize = False + + # Try to find the end of the function + if currentFunctionSym is not None and currentFunctionSym.userDeclaredSize is not None: + # If the function has a size set by the user then only use that and ignore the other ways of determining function-ends + if instructionOffset + 8 == currentInstructionStart + currentFunctionSym.getSize(): + functionEnded = True + prevFuncHadUserDeclaredSize = True + else: + funcSymbol = self.getSymbol(currentVram + 8, vromAddress=currentVrom + 8, tryPlusOffset=False, checkGlobalSegment=False) + # If there's another function after this then the current function has ended + if funcSymbol is not None and funcSymbol.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): + if funcSymbol.vromAddress is None or currentVrom + 8 == funcSymbol.vromAddress: + functionEnded = True + + if not functionEnded and not (farthestBranch > 0) and instr.isJump(): + if instr.isReturn(): + # Found a jr $ra and there are no branches outside of this function + if self.tryDetectRedundantFunctionEnd(): + # IDO -g, -g1 and -g2 can generate a redundant and unused `jr $ra; nop`. In normal conditions this would be detected + # as its own separate empty function, which would cause issues on a decompilation project. + # In other words, we try to detect the following pattern, and the last two instructions not being a function + # already referenced or user-declared. + # jr $ra + # nop + # jr $ra + # nop + redundantPatternDetected = False + if index + 3 < nInstr: + instr1 = instrsList[index+1] + instr2 = instrsList[index+2] + instr3 = instrsList[index+3] + if funcSymbol is None and instr1.isNop() and instr2.isReturn() and instr3.isNop(): + redundantPatternDetected = True + if not redundantPatternDetected: + functionEnded = True + else: + functionEnded = True + elif instr.isJumptableJump(): + # Usually jumptables, ignore + pass + elif not instr.doesLink(): + if isLikelyHandwritten or self.instrCat == rabbitizer.InstrCategory.RSP: + # I don't remember the reasoning of this condition... + functionEnded = True + elif instr.isJumpWithAddress(): + # If this instruction is a jump and it is jumping to a function then + # we can consider this as a function end. This can happen as a + # tail-optimization in modern compilers + targetVram = instr.getInstrIndexAsVram() + auxSym = self.getSymbol(targetVram, tryPlusOffset=False, checkGlobalSegment=False) + if auxSym is not None and auxSym.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): + functionEnded = True + + return functionEnded, prevFuncHadUserDeclaredSize + + def _findFunctions(self, instrsList: list[rabbitizer.Instruction]) -> tuple[list[int], list[bool]]: + nInstr = len(instrsList) + + if nInstr == 0: return [0], [False] functionEnded = False farthestBranch = 0 - funcsStartsList = [0] - unimplementedInstructionsFuncList = [] + funcsStartsList: list[int] = [0] + unimplementedInstructionsFuncList: list[bool] = [] instructionOffset = 0 currentInstructionStart = 0 @@ -66,7 +170,6 @@ def _findFunctions(self, instrsList: list[rabbitizer.Instruction]): isInstrImplemented = True index = 0 - nInstr = len(instrsList) if instrsList[0].isNop(): isboundary = False @@ -91,6 +194,8 @@ def _findFunctions(self, instrsList: list[rabbitizer.Instruction]): funcsStartsList.append(index) unimplementedInstructionsFuncList.append(not isInstrImplemented) + prevFuncHadUserDeclaredSize = False + while index < nInstr: instr = instrsList[index] if not instr.isImplemented() or not instr.isValid(): @@ -127,8 +232,12 @@ def _findFunctions(self, instrsList: list[rabbitizer.Instruction]): funcsStartsList.append(index) unimplementedInstructionsFuncList.append(not isInstrImplemented) - if index >= len(instrsList): + if index >= nInstr: break + if prevFuncHadUserDeclaredSize: + auxSym = self.addFunction(self.getVramOffset(instructionOffset), isAutogenerated=True, symbolVrom=self.getVromOffset(instructionOffset)) + auxSym.isAutocreatedSymFromOtherSizedSym = True + prevFuncHadUserDeclaredSize = False instr = instrsList[index] isInstrImplemented = instr.isImplemented() and instr.isValid() @@ -139,76 +248,11 @@ def _findFunctions(self, instrsList: list[rabbitizer.Instruction]): isLikelyHandwritten = instr.isLikelyHandwritten() if instr.isBranch() or instr.isUnconditionalBranch(): - branchOffset = instr.getBranchOffsetGeneric() - if branchOffset > farthestBranch: - # keep track of the farthest branch target - farthestBranch = branchOffset - if branchOffset < 0: - if branchOffset + instructionOffset < 0: - # Whatever we are reading is not a valid instruction - if not instr.isJump(): # Make an exception for `j` - break - # make sure to not branch outside of the current function - if not isLikelyHandwritten and isInstrImplemented: - j = len(funcsStartsList) - 1 - while j >= 0: - if branchOffset + instructionOffset < 0: - break - if (branchOffset + instructionOffset) < funcsStartsList[j] * 4: - vram = self.getVramOffset(funcsStartsList[j]*4) - vromAddress = self.getVromOffset(funcsStartsList[j]*4) - funcSymbol = self.getSymbol(vram, vromAddress=vromAddress, tryPlusOffset=False, checkGlobalSegment=False) - if funcSymbol is not None and funcSymbol.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): - j -= 1 - continue - del funcsStartsList[j] - del unimplementedInstructionsFuncList[j-1] - else: - break - j -= 1 - - # Try to find the end of the function - if currentFunctionSym is not None and currentFunctionSym.userDeclaredSize is not None: - # If the function has a size set by the user then only use that and ignore the other ways of determining function-ends - if instructionOffset + 8 == currentInstructionStart + currentFunctionSym.getSize(): - functionEnded = True - else: - funcSymbol = self.getSymbol(currentVram + 8, vromAddress=currentVrom + 8, tryPlusOffset=False, checkGlobalSegment=False) - # If there's another function after this then the current function has ended - if funcSymbol is not None and funcSymbol.isTrustableFunction(self.instrCat == rabbitizer.InstrCategory.RSP): - if funcSymbol.vromAddress is None or currentVrom + 8 == funcSymbol.vromAddress: - functionEnded = True + farthestBranch, haltFunctionSearching = self._findFunctions_branchChecker(instructionOffset, instr, funcsStartsList, unimplementedInstructionsFuncList, farthestBranch, isLikelyHandwritten, isInstrImplemented) + if haltFunctionSearching: + break - if not functionEnded and not (farthestBranch > 0) and instr.isJump(): - if instr.isReturn(): - # Found a jr $ra and there are no branches outside of this function - if self.tryDetectRedundantFunctionEnd(): - # IDO -g, -g1 and -g2 can generate a redundant and unused `jr $ra; nop`. In normal conditions this would be detected - # as its own separate empty function, which would cause issues on a decompilation project. - # In other words, we try to detect the following pattern, and the last two instructions not being a function - # already referenced or user-declared. - # jr $ra - # nop - # jr $ra - # nop - redundantPatternDetected = False - if index + 3 < nInstr: - instr1 = instrsList[index+1] - instr2 = instrsList[index+2] - instr3 = instrsList[index+3] - if funcSymbol is None and instr1.isNop() and instr2.isReturn() and instr3.isNop(): - redundantPatternDetected = True - if not redundantPatternDetected: - functionEnded = True - else: - functionEnded = True - elif instr.isJumptableJump(): - # Usually jumptables, ignore - pass - elif not instr.doesLink(): - if isLikelyHandwritten or self.instrCat == rabbitizer.InstrCategory.RSP: - # I don't remember the reasoning of this condition... - functionEnded = True + functionEnded, prevFuncHadUserDeclaredSize = self._findFunctions_checkFunctionEnded(instructionOffset, instr, index, currentVrom, currentVram, currentFunctionSym, farthestBranch, currentInstructionStart, isLikelyHandwritten, instrsList, nInstr) index += 1 farthestBranch -= 4 diff --git a/spimdisasm/mips/symbols/MipsSymbolFunction.py b/spimdisasm/mips/symbols/MipsSymbolFunction.py index 7d020663..fb677a5e 100644 --- a/spimdisasm/mips/symbols/MipsSymbolFunction.py +++ b/spimdisasm/mips/symbols/MipsSymbolFunction.py @@ -437,7 +437,7 @@ def analyze(self): contextSym.setAccessTypeIfUnset(symAccess.accessType, symAccess.unsignedMemoryAccess) if contextSym.isAutogenerated: # Handle mips1 doublefloats - if contextSym.accessType == rabbitizer.AccessType.FLOAT: + if contextSym.accessType == rabbitizer.AccessType.FLOAT and common.GlobalConfig.ABI == common.Abi.O32: instr = self.instructions[loOffset//4] if instr.doesDereference() and instr.isFloat() and not instr.isDouble(): if instr.ft.value % 2 != 0: @@ -708,6 +708,7 @@ def disassemble(self, migrate: bool=False, useGlobalLabel: bool=True, isSplitted self._generateRelocsFromInstructionAnalyzer() symName = self.getName() + symSize = self.contextSym.getSize() output += self.getSymbolAsmDeclaration(symName, useGlobalLabel) wasLastInstABranch = False @@ -734,8 +735,8 @@ def disassemble(self, migrate: bool=False, useGlobalLabel: bool=True, isSplitted wasLastInstABranch = instr.hasDelaySlot() instructionOffset += 4 - if common.GlobalConfig.ASM_EMIT_SIZE_DIRECTIVE: - output += f".size {symName}, . - {symName}{common.GlobalConfig.LINE_ENDS}" + if instructionOffset == symSize: + output += self.getSizeDirective(symName) if common.GlobalConfig.ASM_TEXT_END_LABEL: output += f"{common.GlobalConfig.ASM_TEXT_END_LABEL} {self.getName()}" + common.GlobalConfig.LINE_ENDS