diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1e8a5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xcuserstate + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md + +fastlane/report.xml +fastlane/screenshots + +#Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/Demangler/Demangler.swift b/Demangler/Demangler.swift new file mode 100644 index 0000000..ac5fed5 --- /dev/null +++ b/Demangler/Demangler.swift @@ -0,0 +1,112 @@ +import Foundation + +public enum DemangleResult { + case Success(String) + case Ignored(String) + case Failed(String?) +} + +public final class Demangler { + private let ExtractFromObjCRegex = try! NSRegularExpression(pattern: "^[-+]\\[([^\\]]+)\\s+[^\\]]+\\]$", + options: [.AnchorsMatchLines]) + + private var internalDemangler: InternalDemangler = LibraryDemangler() + + public init() {} + + /** + Demangle a mangled swift string + + - parameter name: A mangled swift string + + - returns: The demangled name + */ + public func demangle(string mangledName: String?) -> DemangleResult { + guard let string = mangledName else { + return .Failed(mangledName) + } + + if !self.shouldDemangle(string: string) { + return .Ignored(string) + } + + let (extracted, wasExtracted) = self.extractMangledString(fromObjCSelector: string) + if let demangled = self.internalDemangler.demangle(string: extracted) { + if wasExtracted { + return .Success(self.integrateDemangledString(intoSelector: string, string: demangled)) + } else { + return .Success(demangled) + } + } else { + return .Failed(string) + } + } + + /** + Integrate a demangled name back into the original ObjC selector it was pulled from. + Example: -[_MangledName_ foo] -> -[Demangled foo] + + - parameter selector: The original ObjC style selector + - parameter name: The demangled name + + - returns: The demangled name integrated into the ObjC selector + */ + func integrateDemangledString(intoSelector selector: String, string: String) -> String { + let range = NSRange(location: 0, length: selector.characters.count) + let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range) + assert(matches.count <= 1) + assert(matches.first?.numberOfRanges == 2) + + let match = matches.first! + let matchRange = match.rangeAtIndex(1) + let selectorString = selector as NSString + let start = selectorString.substringWithRange(NSRange(location: 0, length: matchRange.location)) + let position = matchRange.location + matchRange.length + let end = selectorString.substringWithRange(NSRange(location: position, + length: selectorString.length - position)) + return "\(start)\(string)\(end)" + } + + func extractMangledString(fromObjCSelector selector: String) -> (String, Bool) { + let range = NSRange(location: 0, length: selector.characters.count) + let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range) + assert(matches.count <= 1) + + if let match = matches.first where match.numberOfRanges == 2 { + let range = match.rangeAtIndex(1) + return ((selector as NSString).substringWithRange(range), true) + } + + return (selector, false) + } + + /** + Does a simple check to see if the string should be demangled. This only exists to reduce the number of + times we have to shell out to the demangle tool. + + - SEEALSO: https://github.com/apple/swift/blob/82509cbd7451e72fb99d22556ad259ceb335cb1f/lib/SwiftDemangle/SwiftDemangle.cpp#L22 + + - parameter string: The possibly mangled string + + - returns: true if a demangle should be attempted + */ + func shouldDemangle(string string: String) -> Bool { + if string.hasPrefix("__T") { + return true + } + + if string.hasPrefix("_T") { + return true + } + + if string.hasPrefix("-[_T") { + return true + } + + if string.hasPrefix("+[_T") { + return true + } + + return false + } +} diff --git a/Demangler/InternalDemangler.swift b/Demangler/InternalDemangler.swift new file mode 100644 index 0000000..12c33af --- /dev/null +++ b/Demangler/InternalDemangler.swift @@ -0,0 +1,3 @@ +protocol InternalDemangler { + func demangle(string string: String) -> String? +} diff --git a/Demangler/LibraryDemangler.swift b/Demangler/LibraryDemangler.swift new file mode 100644 index 0000000..2455728 --- /dev/null +++ b/Demangler/LibraryDemangler.swift @@ -0,0 +1,42 @@ +import Foundation + +private typealias SwiftDemangleFunction = @convention(c) (UnsafePointer, + UnsafeMutablePointer, size_t) -> size_t + +private let kDemangleLibraryPath = ("/Applications/Xcode.app/Contents/Developer/Toolchains" + + "/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib") +private let kBufferSize = 1024 + +final class LibraryDemangler: InternalDemangler { + private var handle: UnsafeMutablePointer = { + return dlopen(kDemangleLibraryPath, RTLD_NOW) + }() + + private lazy var internalDemangleFunction: SwiftDemangleFunction = { + let address = dlsym(self.handle, "swift_demangle_getDemangledName") + return unsafeBitCast(address, SwiftDemangleFunction.self) + }() + + func demangle(string string: String) -> String? { + let formattedString = self.removingExcessLeadingUnderscores(fromString: string) + let outputString = UnsafeMutablePointer.alloc(kBufferSize) + let resultSize = self.internalDemangleFunction(formattedString, outputString, kBufferSize) + if resultSize > kBufferSize { + NSLog("Attempted to demangle string with length \(resultSize) but buffer size \(kBufferSize)") + } + + return String(CString: outputString, encoding: NSUTF8StringEncoding) + } + + private func removingExcessLeadingUnderscores(fromString string: String) -> String { + if string.hasPrefix("__T") { + return String(string.characters.dropFirst()) + } + + return string + } + + deinit { + dlclose(self.handle) + } +} diff --git a/Demangler/ShellDemangler.swift b/Demangler/ShellDemangler.swift new file mode 100644 index 0000000..79e8cdf --- /dev/null +++ b/Demangler/ShellDemangler.swift @@ -0,0 +1,33 @@ +import Foundation + +final class ShellDemangler: InternalDemangler { + private lazy var executablePath: String = { + return self.output(forShellCommand: "/usr/bin/xcrun --find swift-demangle")! + }() + + func demangle(string string: String) -> String? { + return self.output(forShellCommand: self.demangleCommand(forString: string)) + } + + private func demangleCommand(forString string: String) -> String { + return "\(self.executablePath) -compact \(string)" + } + + private func output(forShellCommand command: String) -> String? { + assert(command.split().count >= 2) + + let task = NSTask() + let pipe = NSPipe() + let components = command.split() + + task.launchPath = components.first + task.arguments = Array(components.dropFirst()) + task.standardOutput = pipe + + task.launch() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: NSUTF8StringEncoding)?.strip() + } +} diff --git a/Demangler/String+Extension.swift b/Demangler/String+Extension.swift new file mode 100644 index 0000000..b311a9f --- /dev/null +++ b/Demangler/String+Extension.swift @@ -0,0 +1,12 @@ +import Foundation + +extension String { + func split() -> [String] { + return self.characters.split(" ").map(String.init) + } + + func strip() -> String { + return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + } +} + diff --git a/Demangler/Supporting Files/Info.plist b/Demangler/Supporting Files/Info.plist new file mode 100644 index 0000000..f237f4c --- /dev/null +++ b/Demangler/Supporting Files/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 smileykeith. All rights reserved. + NSPrincipalClass + + + diff --git a/DemanglerTests/DemangleObjCSelectorTests.swift b/DemanglerTests/DemangleObjCSelectorTests.swift new file mode 100644 index 0000000..07b7075 --- /dev/null +++ b/DemanglerTests/DemangleObjCSelectorTests.swift @@ -0,0 +1,38 @@ +@testable import Demangler +import XCTest + +final class DemangleObjCSelectorTests: XCTestCase { + private var demangler: Demangler! + + override func setUp() { + super.setUp() + + demangler = Demangler() + } + + func testExtractsString() { + let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "-[Foo bar]") + + XCTAssertEqual(name, "Foo") + XCTAssertTrue(extracted) + } + + func testDoesNotExtractNonObjCString() { + let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "_TCFoo") + + XCTAssertEqual(name, "_TCFoo") + XCTAssertFalse(extracted) + } + + func testIntegrateString() { + let selector = demangler.integrateDemangledString(intoSelector: "-[Foo bar]", string: "Baz") + + XCTAssertEqual(selector, "-[Baz bar]") + } + + func testIntegrateStringIntoClassSelector() { + let selector = demangler.integrateDemangledString(intoSelector: "+[Foo.qux bar]", string: "Baz") + + XCTAssertEqual(selector, "+[Baz bar]") + } +} diff --git a/DemanglerTests/DemanglerTests.swift b/DemanglerTests/DemanglerTests.swift new file mode 100644 index 0000000..e722b5e --- /dev/null +++ b/DemanglerTests/DemanglerTests.swift @@ -0,0 +1,47 @@ +@testable import Demangler +import XCTest + +extension DemangleResult: Equatable {} + +public func == (lhs: DemangleResult, rhs: DemangleResult) -> Bool { + switch (lhs, rhs) { + case (.Success(let string), .Success(let string2)): + return string == string2 + case (.Ignored(let string), .Ignored(let string2)): + return string == string2 + case (.Failed(let string), .Failed(let string2)): + return string == string2 + default: + return false + } +} + +final class DemanglerTests: XCTestCase { + private var demangler: Demangler! + + override func setUp() { + super.setUp() + + demangler = Demangler() + } + + func testDemangleSanity() { + let demangledResult = demangler.demangle(string: "__TFV3foo3BarCfT_S0_") + let expectedName = "foo.Bar.init () -> foo.Bar" + + XCTAssertEqual(demangledResult, DemangleResult.Success(expectedName)) + } + + func testReturningNonDemangledName() { + let demangled = demangler.demangle(string: "sub_123") + + XCTAssertEqual(demangled, DemangleResult.Ignored("sub_123")) + } + + func testDemanglingExtractedName() { + let demangledString = demangler.demangle(string: "-[_TtC3foo3Baz objective]") + let expectedString = "-[foo.Baz objective]" + + XCTAssertEqual(demangledString, DemangleResult.Success(expectedString)) + } +} diff --git a/DemanglerTests/ShouldDemangleTests.swift b/DemanglerTests/ShouldDemangleTests.swift new file mode 100644 index 0000000..0bff3a8 --- /dev/null +++ b/DemanglerTests/ShouldDemangleTests.swift @@ -0,0 +1,42 @@ +@testable import Demangler +import XCTest + +final class ShouldDemangleTests: XCTestCase { + private var demangler: Demangler! + + override func setUp() { + super.setUp() + + demangler = Demangler() + } + + func testShouldNotDemangleSub() { + let shouldDemangle = demangler.shouldDemangle(string: "sub_123") + + XCTAssertFalse(shouldDemangle) + } + + func testShouldNotDemangleImp() { + let shouldDemangle = demangler.shouldDemangle(string: "imp__abc") + + XCTAssertFalse(shouldDemangle) + } + + func testShouldDemangleMangledString() { + let shouldDemangle = demangler.shouldDemangle(string: "__TFV3foo3BarCfT_S0_") + + XCTAssertTrue(shouldDemangle) + } + + func testShouldDemangleClassFunction() { + let shouldDemangle = demangler.shouldDemangle(string: "+[_TtC3foo3Baz objective]") + + XCTAssertTrue(shouldDemangle) + } + + func testShouldDemangleNormalString() { + let shouldDemangle = demangler.shouldDemangle(string: "_TtC3foo3Baz") + + XCTAssertTrue(shouldDemangle) + } +} diff --git a/DemanglerTests/StringExtensionTests.swift b/DemanglerTests/StringExtensionTests.swift new file mode 100644 index 0000000..3a2d0cb --- /dev/null +++ b/DemanglerTests/StringExtensionTests.swift @@ -0,0 +1,15 @@ +@testable import Demangler +import XCTest + +final class StringExtensionTests: XCTestCase { + func testSplit() { + let string = "hi there" + let components = string.split() + XCTAssertEqual(components, ["hi", "there"]) + } + + func testStrip() { + let string = "hi there\n" + XCTAssertEqual(string.strip(), "hi there") + } +} diff --git a/DemanglerTests/Supporting Files/Info.plist b/DemanglerTests/Supporting Files/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/DemanglerTests/Supporting Files/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/HopperSDK/CPUContext.h b/HopperSDK/CPUContext.h new file mode 100644 index 0000000..727ab63 --- /dev/null +++ b/HopperSDK/CPUContext.h @@ -0,0 +1,173 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import +#include "DisasmStruct.h" + +@protocol HPSegment; +@protocol HPProcedure; +@protocol HPBasicBlock; +@protocol HPFormattedInstructionInfo; +@protocol HPDisassembledFile; +@protocol CPUDefinition; + +@class Decompiler; +@class ASTNode; + +@protocol CPUContext + +- (NSObject *)cpuDefinition; + +- (void)initDisasmStructure:(DisasmStruct*)disasm withSyntaxIndex:(NSUInteger)syntaxIndex; + +//////////////////////////////////////////////////////////////////////////////// +// +// Analysis +// +//////////////////////////////////////////////////////////////////////////////// + +/// Adjust address to the lowest possible address acceptable by the CPU. Example: M68000 instruction must be word aligned, so this method would clear bit 0. +- (Address)adjustCodeAddress:(Address)address; + +/// Returns a guessed CPU mode for a given address. Example, ARM processors knows that an instruction is in Thumb mode if bit 0 is 1. +- (uint8_t)cpuModeFromAddress:(Address)address; + +/// Returns YES if we know that a given address forces the CPU to use a specific mode. Thumb mode of comment above. +- (BOOL)addressForcesACPUMode:(Address)address; + +/// An heuristic to estimate the CPU mode at a given address, not based on the value of the +/// address itself (this is the purpose of the "cpuModeFromAddress:" method), but rather +/// by trying to disassemble a few instruction and see which mode seems to be the best guess. +- (uint8_t)estimateCPUModeAtVirtualAddress:(Address)address; + +- (Address)nextAddressToTryIfInstructionFailedToDecodeAt:(Address)address forCPUMode:(uint8_t)mode; + +/// Return 0 if the instruction at this address doesn't represent a NOP instruction (or any padding instruction), or the insturction length if any. +- (int)isNopAt:(Address)address; + +/// Returns YES if a procedure prolog has been detected at this address. +- (BOOL)hasProcedurePrologAt:(Address)address; + +/// Notify the plugin that an analysisbegan from an entry point. +/// This could be either a simple disassembling, or a procedure creation. +/// In the latter case, another method will be called to notify the plugin (see below). +- (void)analysisBeginsAt:(Address)entryPoint; +/// Notify the plugin that analysis has ended. +- (void)analysisEnded; + +/// A Procedure object is about to be created. +- (void)procedureAnalysisBeginsForProcedure:(NSObject *)procedure atEntryPoint:(Address)entryPoint; +/// The prolog of the created procedure is being analyzed. +/// Warning: this method is not called at the begining of the procedure creation, but once all basic blocks +/// have been created. +- (void)procedureAnalysisOfPrologForProcedure:(NSObject *)procedure atEntryPoint:(Address)entryPoint; +- (void)procedureAnalysisOfEpilogForProcedure:(NSObject *)procedure atEntryPoint:(Address)entryPoint; +- (void)procedureAnalysisEndedForProcedure:(NSObject *)procedure atEntryPoint:(Address)entryPoint; + +/// A new basic bloc is created +- (void)procedureAnalysisContinuesOnBasicBlock:(NSObject *)basicBlock; + +/// This method may be called when the internal state of the disassembler should be reseted. +/// For instance, the ARM plugin maintains a state during the disassembly process to +/// track the state of IT blocks. When this method is called, this state is reseted. +- (void)resetDisassembler; + +/// Disassemble a single instruction, filling the DisasmStruct structure. +/// Only a few fields are set by Hopper (mainly, the syntaxIndex, the "bytes" field and the virtualAddress of the instruction). +/// The CPU should fill as much information as possible. +- (int)disassembleSingleInstruction:(DisasmStruct *)disasm usingProcessorMode:(NSUInteger)mode; + +/// Returns whether or not an instruction may halt the processor (like the HLT Intel instruction). +- (BOOL)instructionHaltsExecutionFlow:(DisasmStruct *)disasm; + +/// These methods are called to let you update your internal plugin state during the analysis. +- (void)performProcedureAnalysis:(NSObject *)procedure basicBlock:(NSObject *)basicBlock disasm:(DisasmStruct *)disasm; +- (void)updateProcedureAnalysis:(DisasmStruct *)disasm; + +/// Return YES if the provided DisasmStruct represents an instruction that cand directly reference a memory address. +/// Ususally, this methods returns YES. This is used by the ARM plugin to avoid false references on "MOVW" instruction +/// for instance. +- (BOOL)instructionCanBeUsedToExtractDirectMemoryReferences:(DisasmStruct *)disasmStruct; + +/// Return YES if the instruction may be used to build a switch/case statement. +/// For instance, for the Intel processor, it returns YES for the "JMP reg" and the "JMP [xxx+reg*4]" instructions, +/// and for the Am processor, it returns YES for the "TBB" and "TBH" instructions. +- (BOOL)instructionMayBeASwitchStatement:(DisasmStruct *)disasmStruct; + +/// If a branch instruction is found, Hopper calls this method to compute additional destinations of the instruction. +/// The "*next" value is already set to the address which follows the instruction if the jump does not occurs. +/// The "branches" array is filled by NSNumber objects. The values are the addresses where the instruction can jump. Only the +/// jumps that occur in the same procedure are put here (for instance, CALL instruction targets are not put in this array). +/// The "calledAddresses" array is filled by NSNumber objects of addresses that are the target of a "CALL like" instruction, ie +/// all the jumps which go outside of the procedure. +/// The "callSiteAddresses" contains NSNumber of the addresses of the "CALL" instructions. +/// The purpose of this method is to compute additional destinations. +/// Most of the time, Hopper already found the destinations, so there is no need to do more. +/// This is used by the Intel CPU plugin to compute the destinations of switch/case constructions when it found a "JMP register" instruction. +- (void)performBranchesAnalysis:(DisasmStruct *)disasm + computingNextAddress:(Address *)next + andBranches:(NSMutableArray *)branches + forProcedure:(NSObject *)procedure + basicBlock:(NSObject *)basicBlock + ofSegment:(NSObject *)segment + calledAddresses:(NSMutableArray *)calledAddresses + callsites:(NSMutableArray *)callSitesAddresses; + +/// If you need a specific analysis, this method will be called once the previous branch analysis is performed. +/// For instance, this is used by the ARM CPU plugin to set the type of the destination of an LDR instruction to +/// an int of the correct size. +- (void)performInstructionSpecificAnalysis:(DisasmStruct *)disasm forProcedure:(NSObject *)procedure inSegment:(NSObject *)segment; + +/// Returns the destination address if the function starting at the given address is a thunk (ie: a direct jump to another method) +/// Returns BAD_ADDRESS is the instruction is not a thunk. +- (Address)getThunkDestinationForInstructionAt:(Address)address; + +//////////////////////////////////////////////////////////////////////////////// +// +// Printing instruction +// +//////////////////////////////////////////////////////////////////////////////// + +/// Build the complete instruction string in the DisasmStruct structure. +/// This is the string to be displayed in Hopper. +- (void)buildInstructionString:(DisasmStruct *)disasm forSegment:(NSObject *)segment populatingInfo:(NSObject *)formattedInstructionInfo; + +//////////////////////////////////////////////////////////////////////////////// +// +// Decompiler +// +//////////////////////////////////////////////////////////////////////////////// + +- (BOOL)canDecompileProcedure:(NSObject *)procedure; + +/// Return the address of the first instruction of the procedure, after its prolog. +- (Address)skipHeader:(NSObject *)basicBlock ofProcedure:(NSObject *)procedure; + +/// Return the address of the last instruction of the procedure, before its epilog. +- (Address)skipFooter:(NSObject *)basicBlock ofProcedure:(NSObject *)procedure; + +/// Decompile an assembly instruction. +/// Note: ASTNode is not publicly exposed yet. You cannot write a decompiler at the moment. +- (ASTNode *)decompileInstructionAtAddress:(Address)a + disasm:(DisasmStruct)d + addNode_p:(BOOL *)addNode_p + usingDecompiler:(Decompiler *)decompiler; + +//////////////////////////////////////////////////////////////////////////////// +// +// Assembler +// +//////////////////////////////////////////////////////////////////////////////// + +- (NSData *)assembleRawInstruction:(NSString *)instr atAddress:(Address)addr forFile:(NSObject *)file withCPUMode:(uint8_t)cpuMode usingSyntaxVariant:(NSUInteger)syntax error:(NSError **)error; + +@end diff --git a/HopperSDK/CPUDefinition.h b/HopperSDK/CPUDefinition.h new file mode 100644 index 0000000..43673d7 --- /dev/null +++ b/HopperSDK/CPUDefinition.h @@ -0,0 +1,70 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import +#import "CPUContext.h" +#import "HopperPlugin.h" +#import "CommonTypes.h" + +@protocol CPUDefinition + +/// Build a context for disassembling. +/// This method should be fast, because it'll be called very often. +- (NSObject *)buildCPUContextForFile:(NSObject *)file; + +/// Returns an array of NSString of CPU families handled by the plugin. +- (NSArray *)cpuFamilies; +/// Returns an array of NSString of CPU subfamilies handled by the plugin for a given CPU family. +- (NSArray *)cpuSubFamiliesForFamily:(NSString *)family; +/// Returns 32 or 64, according to the family and subFamily arguments. +- (int)addressSpaceWidthInBitsForCPUFamily:(NSString *)family andSubFamily:(NSString *)subFamily; + +/// Default endianess of the CPU. +- (CPUEndianess)endianess; +/// Usually, returns 1, but for the Intel processor, it'll return 2 because we have the Intel and the AT&T syntaxes. +- (NSUInteger)syntaxVariantCount; +/// The number of CPU modes. For instance, 2 for the ARM CPU family: ARM and Thumb modes. +- (NSUInteger)cpuModeCount; + +- (NSArray *)syntaxVariantNames; +- (NSArray *)cpuModeNames; + +- (NSUInteger)registerClassCount; +- (NSUInteger)registerCountForClass:(RegClass)reg_class; +- (NSString *)registerIndexToString:(int)reg ofClass:(RegClass)reg_class withBitSize:(int)size andPosition:(DisasmPosition)position; +- (NSString *)cpuRegisterStateMaskToString:(uint32_t)cpuState; +- (BOOL)registerIndexIsStackPointer:(uint32_t)reg ofClass:(RegClass)reg_class; +- (BOOL)registerIndexIsFrameBasePointer:(uint32_t)reg ofClass:(RegClass)reg_class; +- (BOOL)registerIndexIsProgramCounter:(uint32_t)reg; +// Returns the name of the frame pointer register, ie, "bp" for x86, or "r7" for ARM. +- (NSString *)framePointerRegisterNameForFile:(NSObject*)file; + +/// A weirdness of the Hopper internals. You'll usually simply need to return the "index" argument. +/// This is used by Hopper to handle the fact that operands in Intel and AT&T syntaxes are inverted. +- (NSUInteger)translateOperandIndex:(NSUInteger)index operandCount:(NSUInteger)count accordingToSyntax:(uint8_t)syntaxIndex; + +/// Returns a colorized string to be displayed. +/// HPHopperServices protocol provides a very simple colorizer, based on predicates. +- (NSAttributedString *)colorizeInstructionString:(NSAttributedString *)string; + +/// Returns a array of bytes that represents a NOP instruction of a given size. +- (NSData *)nopWithSize:(NSUInteger)size andMode:(NSUInteger)cpuMode forFile:(NSObject *)file; + +/// Return YES if the plugin embed an assembler. +- (BOOL)canAssembleInstructionsForCPUFamily:(NSString *)family andSubFamily:(NSString *)subFamily; + +/// Return YES if the plugin embed a decompiler. +/// Note: you cannot create a decompiler yet, because the main class (ASTNode) is not +/// publicly exposed yet. +- (BOOL)canDecompileProceduresForCPUFamily:(NSString *)family andSubFamily:(NSString *)subFamily; + +@end diff --git a/HopperSDK/CommonTypes.h b/HopperSDK/CommonTypes.h new file mode 100644 index 0000000..0b6e8f7 --- /dev/null +++ b/HopperSDK/CommonTypes.h @@ -0,0 +1,379 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#ifndef _HOPPER_COMMONTYPES_H_ +#define _HOPPER_COMMONTYPES_H_ + +#import + +// Addresses + +typedef uint64_t Address; +typedef struct { + Address from; + size_t len; +} AddressRange; + +#define BAD_ADDRESS ((Address)-1) + +// Colors + +typedef uint32_t Color; +#define NO_COLOR 0 + +#if defined(__OBJC__) +# if defined(NS_ENUM) +# define HP_BEGIN_DECL_ENUM(BASE,TYPE) typedef NS_ENUM(BASE,TYPE) +# define HP_END_DECL_ENUM(TYPE) +# else +# define HP_BEGIN_DECL_ENUM(BASE,TYPE) typedef enum TYPE : BASE TYPE; enum TYPE : BASE +# define HP_END_DECL_ENUM(TYPE) +# endif +# if defined(NS_OPTIONS) +# define HP_BEGIN_DECL_OPTIONS(BASE,TYPE) typedef NS_OPTIONS(BASE,TYPE) +# define HP_END_DECL_OPTIONS(TYPE) +# else +# define HP_BEGIN_DECL_OPTIONS(BASE,TYPE) typedef enum TYPE : BASE TYPE; enum TYPE : BASE +# define HP_END_DECL_OPTIONS(TYPE) +# endif +#else +# define HP_BEGIN_DECL_ENUM(BASE,TYPE) typedef enum +# define HP_END_DECL_ENUM(TYPE) TYPE +# define HP_BEGIN_DECL_OPTIONS(BASE,TYPE) typedef enum +# define HP_END_DECL_OPTIONS(TYPE) TYPE +#endif + +HP_BEGIN_DECL_ENUM(uint8_t, ByteType) { + Type_Undefined, + Type_Outside, + + Type_Next, /// This memory block info is part of the previous bloc + + Type_Int8, + Type_Int16, + Type_Int32, + Type_Int64, + + Type_ASCII, + Type_Unicode, + + Type_Data, /// METATYPE : Only used for searching, no bytes have this type! + + Type_Code, + Type_Procedure, + + Type_Structure +} +HP_END_DECL_ENUM(ByteType); + +HP_BEGIN_DECL_ENUM(uint8_t, ProcedureCreationReason) { + PCReason_None, + PCReason_Unknown, // Unknown reason + PCReason_User, // Created by the used + PCReason_Script, // A Python script created the procedure + PCReason_Called, // A call statement has been found somewhere + PCReason_Prolog // A procedure prolog was detected during the analysis +} +HP_END_DECL_ENUM(ProcedureCreationReason); + +HP_BEGIN_DECL_ENUM(uint8_t, SignatureCreationReason) { + SCReason_None, + SCReason_Unknown, // Unknown reason + SCReason_GuessedFromDecompilation, // Signature built during the decompilation process + SCReason_GuessedFromDataFlow, // Signature built from the data flow analysis. + SCReason_Called, // Signature built from a method call + SCReason_Database, // A known signature, from the embedded database + SCReason_Demangling, // From demangling, or decoding a signature string + SCReason_User // Defined by the user +} +HP_END_DECL_ENUM(SignatureCreationReason); + +HP_BEGIN_DECL_ENUM(uint8_t, CommentCreationReason) { + CCReason_None, + CCReason_Unknown, // Unknown reason + CCReason_User, // Created by the user + CCReason_Script, // A Python script created the comment + CCReason_Automatic, // Automatic comment, like XREF comments, values found during the analysis... + CCReason_Dynamic // A dynamic comment. Its content depends on another MBI (the anchor). +} +HP_END_DECL_ENUM(CommentCreationReason); + +HP_BEGIN_DECL_ENUM(uint8_t, NameCreationReason) { + NCReason_None, + NCReason_Unknown, // Unknown reason + NCReason_User, // Created by the user + NCReason_Script, // A Python script created the name + NCReason_Import, // The name was read from the executable file + NCReason_Metadata, // The name was derived from metadata (like Objective-C) + NCReason_Automatic // An automatic temporary name (like sub_XXXXX) +} +HP_END_DECL_ENUM(NameCreationReason); + +// Types + +HP_BEGIN_DECL_ENUM(NSUInteger, TypeDescType) { + TypeDesc_Void, + + // Fixed size primitive types + TypeDesc_Int8, + TypeDesc_UInt8, + TypeDesc_Int16, + TypeDesc_UInt16, + TypeDesc_Int32, + TypeDesc_UInt32, + TypeDesc_Int64, + TypeDesc_UInt64, + TypeDesc_Float, + TypeDesc_Double, + + // Primitive types whose size depends on the disassembled file + TypeDesc_Int, + TypeDesc_UInt, + TypeDesc_Long, + TypeDesc_ULong, + TypeDesc_LongLong, + TypeDesc_ULongLong, + + // Non-primitive, structured types + TypeDesc_Pointer, + TypeDesc_Struct, + TypeDesc_Union, + TypeDesc_Array, + + TypeDesc_Typedef, + + TypeDesc_Bool, + TypeDesc_Char, + TypeDesc_UChar, + TypeDesc_Short, + TypeDesc_UShort, + + TypeDesc_FunctionPointer +} +HP_END_DECL_ENUM(TypeDescType); + +// Operand Format +#define FORMAT_TYPE_MASK 0x1F + +HP_BEGIN_DECL_ENUM(NSUInteger, ArgFormat) { + Format_Default, + + Format_Hexadecimal, + Format_Decimal, + Format_Octal, + Format_Character, + Format_StackVariable, + Format_Offset, + Format_Address, + Format_Float, + Format_Binary, + + Format_Structured, + + Format_Negate = 0x20, + Format_LeadingZeroes = 0x40, + Format_Signed = 0x80 +} +HP_END_DECL_ENUM(ArgFormat); + +// Switch / case Hints + +HP_BEGIN_DECL_ENUM(uint8_t, HintValueType) { + SwitchHint_None, + SwitchHint_AbsoluteAddress, + SwitchHint_TableRelative, + SwitchHint_PICRelative, + SwitchHint_FixedValueRelative +} +HP_END_DECL_ENUM(HintValueType); + +// Plugin + +HP_BEGIN_DECL_ENUM(NSUInteger, HopperPluginType) { + Plugin_CPU, + Plugin_Loader, + Plugin_Tool +} +HP_END_DECL_ENUM(HopperPluginType); + +// CPU Definition + +HP_BEGIN_DECL_ENUM(NSUInteger, CPUEndianess) { + CPUEndianess_Little, + CPUEndianess_Big +} +HP_END_DECL_ENUM(CPUEndianess); + +// Register Class + +#define MAX_REGISTER_CLASS 16 + +HP_BEGIN_DECL_ENUM(NSUInteger, RegClass) { + RegClass_CPUState = 0, // CPU State registers + RegClass_PseudoRegisterSTACK = 1, // Pseudo registers used to simulate the stack + + RegClass_GeneralPurposeRegister = 2, + + RegClass_FirstUserClass = 3, + + // x86 + RegClass_X86_FPU = RegClass_FirstUserClass, + RegClass_X86_MMX, + RegClass_X86_SSE, + RegClass_X86_AVX, + + // ARM + RegClass_ARM_VFP_Single = RegClass_FirstUserClass, + RegClass_ARM_VFP_Double, + RegClass_ARM_VFP_Quad, + RegClass_ARM_Media, + + RegClass_LastUserClass = MAX_REGISTER_CLASS, + + RegClass_Variable = 100, + RegClass_Argument = 101, + RegClass_Temporaries = 102, + RegClass_Special = 103 +} +HP_END_DECL_ENUM(RegClass); + +HP_BEGIN_DECL_ENUM(NSUInteger, CPUStateFieldIndexes) { + CSF_FlagIndexC = 0, + CSF_FlagIndexP = 2, + CSF_FlagIndexA = 4, + CSF_FlagIndexZ = 6, + CSF_FlagIndexS = 7, + CSF_FlagIndexT = 8, + CSF_FlagIndexI = 9, + CSF_FlagIndexD = 10, + CSF_FlagIndexO = 11 +} +HP_END_DECL_ENUM(CPUStateFieldIndexes); + +HP_BEGIN_DECL_OPTIONS(NSUInteger, CPUStateFieldMasks) { + CSF_FlagMaskNone = 0, + CSF_FlagMaskC = (1 << 0), + CSF_FlagMaskP = (1 << 2), + CSF_FlagMaskA = (1 << 4), + CSF_FlagMaskZ = (1 << 6), + CSF_FlagMaskS = (1 << 7), + CSF_FlagMaskT = (1 << 8), + CSF_FlagMaskI = (1 << 9), + CSF_FlagMaskD = (1 << 10), + CSF_FlagMaskO = (1 << 11) +} +HP_END_DECL_OPTIONS(CPUStateFieldMasks); + +// Calling Conventions + +HP_BEGIN_DECL_ENUM(NSUInteger, CallingConvention) { + CallingConvention_default = 0, + + CallingConvention_cdecl = 1, + CallingConvention_stdcall, + CallingConvention_fastcall, + CallingConvention_fastcall_borland, + CallingConvention_thiscall, + CallingConvention_watcom, + + CallingConvention_AAPCS = 10, + CallingConvention_AAPCS_VFP, + + CallingConvention_X86_64SysV = 20, + CallingConvention_X86_64Win64 +} +HP_END_DECL_ENUM(CallingConvention); + +// File Loaders + +HP_BEGIN_DECL_ENUM(NSUInteger, FileLoaderLoadingStatus) { + DIS_OK, + DIS_BadFormat, + DIS_DebugMismatch, + DIS_DebugUUIDMismatch, + DIS_MissingProcessor, + DIS_NotSupported +} +HP_END_DECL_ENUM(FileLoaderLoadingStatus); + +HP_BEGIN_DECL_ENUM(NSUInteger, LOCKind) { + LOC_Address, + LOC_Checkbox, + LOC_CPU, + LOC_StringList, + LOC_ComboxBox +} +HP_END_DECL_ENUM(LOCKind); + +HP_BEGIN_DECL_ENUM(NSUInteger, DFTAddressWidth) { + AW_16bits = 1, + AW_32bits = 2, + AW_64bits = 3 +} +HP_END_DECL_ENUM(DFTAddressWidth); + +HP_BEGIN_DECL_OPTIONS(NSUInteger, FileLoaderOptions) { + FLS_None = 0, + FLS_ParseObjectiveC = 1 +} +HP_END_DECL_OPTIONS(FileLoaderOptions); + +HP_BEGIN_DECL_OPTIONS(NSUInteger, AnalysisOptions) { + AO_None = 0, + AO_PureProcedureTextSection = (1 << 0) +} +HP_END_DECL_OPTIONS(AnalysisOptions); +#define DEFAULT_ANALYSIS_OPTIONS AO_None + +// Disassembler + +HP_BEGIN_DECL_OPTIONS(NSUInteger, DisassembleOptions) { + DO_None = 0, + DO_FollowCode = (1 << 0), + DO_ProcedureMode = (1 << 1), + DO_ProceedToAnalysis = (1 << 2), + DO_PropagateSignature = (1 << 3) +} +HP_END_DECL_OPTIONS(DisassembleOptions); + +// Debugger + +HP_BEGIN_DECL_ENUM(NSUInteger, DebuggerState) { + STATE_NotConnected, + STATE_Connected, + STATE_Running, + STATE_Signaled, + STATE_Terminated, + STATE_Exited +} +HP_END_DECL_ENUM(DebuggerState); + +HP_BEGIN_DECL_ENUM(NSUInteger, DebuggerType) { + Debugger_None, + Debugger_Local, + Debugger_HopperDebuggerServer, + Debugger_GDBRemote, + Debugger_DebugServer +} +HP_END_DECL_ENUM(DebuggerType); + +// Decompiler +#define DECOMPILER_DEFAULT_OPTIONS (Decompiler_RemoveDeadCode | Decompiler_RemoveMacros) + +HP_BEGIN_DECL_OPTIONS(NSUInteger, DecompilerOptions) { + Decompiler_None = 0, + Decompiler_RemoveDeadCode = 1, + Decompiler_RemoveMacros = 2 +} +HP_END_DECL_OPTIONS(DecompilerOptions); + +#endif diff --git a/HopperSDK/DisasmStruct.h b/HopperSDK/DisasmStruct.h new file mode 100644 index 0000000..4b1a15e --- /dev/null +++ b/HopperSDK/DisasmStruct.h @@ -0,0 +1,325 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#ifndef _HOPPER_DISASM_STRUCT_H_ +#define _HOPPER_DISASM_STRUCT_H_ + +#include +#include "CommonTypes.h" + +#define DISASM_INSTRUCTION_MAX_LENGTH 2048 +#define DISASM_OPERAND_MNEMONIC_MAX_LENGTH 1024 + +#define DISASM_UNKNOWN_OPCODE -1 + +#define DISASM_MAX_OPERANDS 6 +#define DISASM_MAX_REG_INDEX 32 +#define DISASM_MAX_REG_CLASSES 16 + +#define DISASM_OPERAND_REGISTER_INDEX_MASK 0x00000000FFFFFFFFllu +#define DISASM_OPERAND_TYPE_MASK 0xFFFF000000000000llu +#define DISASM_OPERAND_REG_CLASS_MASK 0x0000FFFF00000000llu +#define DISASM_OPERAND_TYPE_AND_REG_CLASS_MASK 0xFFFFFFFF00000000llu + +// Type +#define DISASM_OPERAND_NO_OPERAND 0x8000000000000000llu +#define DISASM_OPERAND_CONSTANT_TYPE 0x4000000000000000llu +#define DISASM_OPERAND_MEMORY_TYPE 0x2000000000000000llu +#define DISASM_OPERAND_REGISTER_TYPE 0x1000000000000000llu +#define DISASM_OPERAND_ABSOLUTE 0x0800000000000000llu +#define DISASM_OPERAND_RELATIVE 0x0400000000000000llu +#define DISASM_OPERAND_OTHER 0x0200000000000000llu + +#define DISASM_EXTRACT_REGISTER_CLASS(TYPE) ((RegClass)(((TYPE) & DISASM_OPERAND_REG_CLASS_MASK) >> DISASM_MAX_REG_INDEX)) + +#define DISASM_BUILD_REGISTER_CLS_MASK(CLS) (0x100000000llu << (CLS)) +#define DISASM_BUILD_REGISTER_INDEX_MASK(INDEX) (1llu << (INDEX)) +#define DISASM_BUILD_REGISTER_MASK(CLS,INDEX) (DISASM_BUILD_REGISTER_CLS_MASK(CLS) | DISASM_BUILD_REGISTER_INDEX_MASK(INDEX)) + +#define DISASM_OPERAND_GENERAL_REG_INDEX 2 + +// Register class (x86) +#define DISASM_OPERAND_X86_FPU_REG_INDEX 3 +#define DISASM_OPERAND_X86_MMX_REG_INDEX 4 +#define DISASM_OPERAND_X86_SSE_REG_INDEX 5 +#define DISASM_OPERAND_X86_AVX_REG_INDEX 6 +#define DISASM_OPERAND_X86_CR_REG_INDEX 7 +#define DISASM_OPERAND_X86_DR_REG_INDEX 8 +#define DISASM_OPERAND_X86_SPECIAL_REG_INDEX 9 +#define DISASM_OPERAND_X86_MEMORY_MANAGEMENT_REG_INDEX 10 +#define DISASM_OPERAND_X86_SEGMENT_REG_INDEX 11 + +#define DISASM_OPERAND_GENERAL_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_GENERAL_REG_INDEX) +#define DISASM_OPERAND_X86_FPU_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_FPU_REG_INDEX) +#define DISASM_OPERAND_X86_MMX_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_MMX_REG_INDEX) +#define DISASM_OPERAND_X86_SSE_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_SSE_REG_INDEX) +#define DISASM_OPERAND_X86_AVX_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_AVX_REG_INDEX) +#define DISASM_OPERAND_X86_CR_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_CR_REG_INDEX) +#define DISASM_OPERAND_X86_DR_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_DR_REG_INDEX) +#define DISASM_OPERAND_X86_SPECIAL_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_SPECIAL_REG_INDEX) +#define DISASM_OPERAND_X86_MEMORY_MANAGEMENT_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_MEMORY_MANAGEMENT_REG_INDEX) +#define DISASM_OPERAND_X86_SEGMENT_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_X86_SEGMENT_REG_INDEX) + +// Register class (ARM) +#define DISASM_OPERAND_ARM_VFP_SINGLE_REG_INDEX 3 +#define DISASM_OPERAND_ARM_VFP_DOUBLE_REG_INDEX 4 +#define DISASM_OPERAND_ARM_VFP_QUAD_REG_INDEX 5 +#define DISASM_OPERAND_ARM_MEDIA_REG_INDEX 6 + +#define DISASM_OPERAND_ARM_VFP_SINGLE_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_ARM_VFP_SINGLE_REG_INDEX) +#define DISASM_OPERAND_ARM_VFP_DOUBLE_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_ARM_VFP_DOUBLE_REG_INDEX) +#define DISASM_OPERAND_ARM_VFP_QUAD_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_ARM_VFP_QUAD_REG_INDEX) +#define DISASM_OPERAND_ARM_MEDIA_REG DISASM_BUILD_REGISTER_CLS_MASK(DISASM_OPERAND_ARM_MEDIA_REG_INDEX) + +#define DISASM_REG0 DISASM_BUILD_REGISTER_INDEX_MASK(0) +#define DISASM_REG1 DISASM_BUILD_REGISTER_INDEX_MASK(1) +#define DISASM_REG2 DISASM_BUILD_REGISTER_INDEX_MASK(2) +#define DISASM_REG3 DISASM_BUILD_REGISTER_INDEX_MASK(3) +#define DISASM_REG4 DISASM_BUILD_REGISTER_INDEX_MASK(4) +#define DISASM_REG5 DISASM_BUILD_REGISTER_INDEX_MASK(5) +#define DISASM_REG6 DISASM_BUILD_REGISTER_INDEX_MASK(6) +#define DISASM_REG7 DISASM_BUILD_REGISTER_INDEX_MASK(7) +#define DISASM_REG8 DISASM_BUILD_REGISTER_INDEX_MASK(8) +#define DISASM_REG9 DISASM_BUILD_REGISTER_INDEX_MASK(9) +#define DISASM_REG10 DISASM_BUILD_REGISTER_INDEX_MASK(10) +#define DISASM_REG11 DISASM_BUILD_REGISTER_INDEX_MASK(11) +#define DISASM_REG12 DISASM_BUILD_REGISTER_INDEX_MASK(12) +#define DISASM_REG13 DISASM_BUILD_REGISTER_INDEX_MASK(13) +#define DISASM_REG14 DISASM_BUILD_REGISTER_INDEX_MASK(14) +#define DISASM_REG15 DISASM_BUILD_REGISTER_INDEX_MASK(15) +#define DISASM_REG16 DISASM_BUILD_REGISTER_INDEX_MASK(16) +#define DISASM_REG17 DISASM_BUILD_REGISTER_INDEX_MASK(17) +#define DISASM_REG18 DISASM_BUILD_REGISTER_INDEX_MASK(18) +#define DISASM_REG19 DISASM_BUILD_REGISTER_INDEX_MASK(19) +#define DISASM_REG20 DISASM_BUILD_REGISTER_INDEX_MASK(20) +#define DISASM_REG21 DISASM_BUILD_REGISTER_INDEX_MASK(21) +#define DISASM_REG22 DISASM_BUILD_REGISTER_INDEX_MASK(22) +#define DISASM_REG23 DISASM_BUILD_REGISTER_INDEX_MASK(23) +#define DISASM_REG24 DISASM_BUILD_REGISTER_INDEX_MASK(24) +#define DISASM_REG25 DISASM_BUILD_REGISTER_INDEX_MASK(25) +#define DISASM_REG26 DISASM_BUILD_REGISTER_INDEX_MASK(26) +#define DISASM_REG27 DISASM_BUILD_REGISTER_INDEX_MASK(27) +#define DISASM_REG28 DISASM_BUILD_REGISTER_INDEX_MASK(28) +#define DISASM_REG29 DISASM_BUILD_REGISTER_INDEX_MASK(29) +#define DISASM_REG30 DISASM_BUILD_REGISTER_INDEX_MASK(30) +#define DISASM_REG31 DISASM_BUILD_REGISTER_INDEX_MASK(31) + +typedef uint64_t DisasmOperandType; + +typedef enum { + DISASM_LOWPOSITION, + DISASM_HIGHPOSITION +} DisasmPosition; + +typedef enum { + DISASM_EFLAGS_TESTED = 0x01, // the flag is tested + DISASM_EFLAGS_MODIFIED = 0x02, // the flag is modified + DISASM_EFLAGS_RESET = 0x04, // the flag is reset + DISASM_EFLAGS_SET = 0x08, // the flag is set + DISASM_EFLAGS_UNDEFINED = 0x10, // undefined behavior + DISASM_EFLAGS_PRIOR = 0x20 // restore prior state +} DisasmEflagsState; + +typedef enum { + DISASM_BRANCH_JNO = -1, + DISASM_BRANCH_JNC = -2, + DISASM_BRANCH_JNB = DISASM_BRANCH_JNC, + DISASM_BRANCH_JNE = -3, + DISASM_BRANCH_JNA = -4, + DISASM_BRANCH_JNS = -5, + DISASM_BRANCH_JNP = -6, + DISASM_BRANCH_JNL = -7, + DISASM_BRANCH_JNG = -8, + + DISASM_BRANCH_NONE = 0, + + DISASM_BRANCH_JO = 1, + DISASM_BRANCH_JC = 2, + DISASM_BRANCH_JB = DISASM_BRANCH_JC, + DISASM_BRANCH_JE = 3, + DISASM_BRANCH_JA = 4, + DISASM_BRANCH_JS = 5, + DISASM_BRANCH_JP = 6, + DISASM_BRANCH_JL = 7, + DISASM_BRANCH_JG = 8, + DISASM_BRANCH_JLE = DISASM_BRANCH_JNG, + DISASM_BRANCH_JGE = DISASM_BRANCH_JNL, + + DISASM_BRANCH_JECXZ = 10, + + DISASM_BRANCH_JMP = 11, + DISASM_BRANCH_CALL = 12, + DISASM_BRANCH_RET = 13, + + DISASM_BRANCH_JCXZ = 14, + DISASM_BRANCH_JRCXZ = 15 +} DisasmBranchType; + +typedef enum { + DISASM_INST_COND_AL, + DISASM_INST_COND_EQ, + DISASM_INST_COND_NE, + DISASM_INST_COND_CS, + DISASM_INST_COND_CC, + DISASM_INST_COND_MI, + DISASM_INST_COND_PL, + DISASM_INST_COND_VS, + DISASM_INST_COND_VC, + DISASM_INST_COND_HI, + DISASM_INST_COND_LS, + DISASM_INST_COND_GE, + DISASM_INST_COND_LT, + DISASM_INST_COND_GT, + DISASM_INST_COND_LE, + + DISASM_INST_COND_NEVER +} DisasmCondition; + +typedef enum { + DISASM_SHIFT_NONE, + DISASM_SHIFT_LSL, + DISASM_SHIFT_LSR, + DISASM_SHIFT_ASR, + DISASM_SHIFT_ROR, + DISASM_SHIFT_RRX +} DisasmShiftMode; + +typedef enum { + DISASM_ACCESS_NONE = 0x0, + DISASM_ACCESS_READ = 0x1, + DISASM_ACCESS_WRITE = 0x2 +} DisasmAccessMode; + +enum { + DISASM_REG_INDEX_RAX, DISASM_REG_INDEX_RCX, DISASM_REG_INDEX_RDX, DISASM_REG_INDEX_RBX, + DISASM_REG_INDEX_RSP, DISASM_REG_INDEX_RBP, DISASM_REG_INDEX_RSI, DISASM_REG_INDEX_RDI, + DISASM_REG_INDEX_R8, DISASM_REG_INDEX_R9, DISASM_REG_INDEX_R10, DISASM_REG_INDEX_R11, + DISASM_REG_INDEX_R12, DISASM_REG_INDEX_R13, DISASM_REG_INDEX_R14, DISASM_REG_INDEX_R15, + DISASM_REG_INDEX_RIP +}; + +typedef enum { + DISASM_ES_Reg = 1, + DISASM_DS_Reg = 2, + DISASM_FS_Reg = 3, + DISASM_GS_Reg = 4, + DISASM_CS_Reg = 5, + DISASM_SS_Reg = 6 +} DisasmSegmentReg; + +typedef struct { + uint8_t lockPrefix; + uint8_t operandSize; + uint8_t addressSize; + uint8_t repnePrefix; + uint8_t repPrefix; +} DisasmPrefix; + +typedef struct { + uint8_t OF_flag; + uint8_t SF_flag; + uint8_t ZF_flag; + uint8_t AF_flag; + uint8_t PF_flag; + uint8_t CF_flag; + uint8_t TF_flag; + uint8_t IF_flag; + uint8_t DF_flag; + uint8_t NT_flag; + uint8_t RF_flag; +} DisasmEFLAGS; + +/// Define a memory access in the form [BASE_REGISTERS + (INDEX_REGISTERS) * SCALE + DISPLACEMENT] +typedef struct { + uint32_t baseRegister; /// Base registers mask + uint32_t indexRegister; /// Index registers mask + int32_t scale; /// Scale (1, 2, 4, 8) + int64_t displacement; /// Displacement +} DisasmMemoryAccess; + +typedef struct { + char mnemonic[20]; /// Instruction mnemonic, with its optional condition. + + char unconditionalMnemonic[16]; /// Mnemonic string without the conditional part. + DisasmCondition condition; /// Condition to be met to execute instruction. + + uintptr_t userData; /// A field that you can use internally to keep information on the instruction. Hopper don't need it. + + uint8_t length; /// Length in bytes of the instruction encoding. + + DisasmBranchType branchType; /// Information on the type of branch this instruction can perform. + DisasmEFLAGS eflags; /// Information on the CPU state register after this instruction is executed. + Address addressValue; /// A value computed from one of the operands, known to point to an address. + + /// The value of the PC register at this address. + /// For instance, on the Intel processor, it will be the address of the instruction + the length of the instruction. + /// For the ARM processor, it will be the address of the instruction + 4 or 8, depending on various things. + /// Anyway, it must reflects the exact value of the PC register if read from the instruction. + Address pcRegisterValue; + + uint8_t ARMSBit; // ARM specific. Set to 1 if 'S' flag. + uint8_t ARMWriteBack; // ARM specific: Set to 1 if writeback flag (!). + uint8_t ARMSpecial; // ARM specific: Set to 1 if special flag (^). + uint8_t ARMThumb; // ARM specifig: Set to 1 if thumb instruction. +} DisasmInstruction; + +typedef struct { + char mnemonic[DISASM_OPERAND_MNEMONIC_MAX_LENGTH]; + DisasmOperandType type; /// Mask of DISASM_OPERAND_* values. + uint32_t size; /// Argument size in bits. In the case of a memory access, this is the size of the read or written value. + DisasmAccessMode accessMode; /// Whether the operand is accessed for reading or writing. DISASM_ACCESS_READ / DISASM_ACCESS_WRITE + + DisasmPosition position; /// DISASM_HIGHPOSITION / DISASM_LOWPOSITION: high position if for 8bits registers AH, BH, CH, DH + + DisasmSegmentReg segmentReg; /// X86 specific: the segment register used for the memory access. + DisasmMemoryAccess memory; /// Description of the memory indirection. + int8_t memoryDecoration; /// Used for decoration on memory operands, like "dword ptr"… This is a plugin specific value, Hopper don't need it. + + /// Shifting used when the type is DISASM_OPERAND_REGISTER_TYPE + DisasmShiftMode shiftMode; /// Shifting mode + int32_t shiftAmount; /// Shifting amount (if not shifted by a register) + int32_t shiftByReg; /// Shifting register + + int64_t immediateValue; /// The immediate value for this operand, if known. +} DisasmOperand; + +typedef struct { + /// Address where you can read the bytes to be decoded. Set by Hopper. + const uint8_t * bytes; + + /// Virtual address in the disassembled file space. Set by Hopper. + Address virtualAddr; + + /// Syntax to be used when building the various mnemonics. + uint8_t syntaxIndex; + + /// Formatted instruction string, directly displayed in Hopper. + char completeInstructionString[DISASM_INSTRUCTION_MAX_LENGTH]; + + /// You can set the CPU, CPUSubType to any value during the initialization of the structure. + /// These values are only used by plugins for their own purpose. Hopper don't need them. + int32_t CPU; + int32_t CPUSubType; + + /// Fields to be set by the plugin. + DisasmPrefix prefix; + DisasmInstruction instruction; + + /// Mask of registers implicitly read or written. + uint32_t implicitlyReadRegisters[DISASM_MAX_REG_CLASSES]; + uint32_t implicitlyWrittenRegisters[DISASM_MAX_REG_CLASSES]; + + /// Instruction operands description + DisasmOperand operand[DISASM_MAX_OPERANDS]; +} DisasmStruct; + +#endif diff --git a/HopperSDK/FileLoader.h b/HopperSDK/FileLoader.h new file mode 100644 index 0000000..240c273 --- /dev/null +++ b/HopperSDK/FileLoader.h @@ -0,0 +1,46 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import +#import "HPDisassembledFile.h" +#import "HPDetectedFileType.h" +#import "HopperPlugin.h" +#import "CommonTypes.h" + +@class DetectedFileType; + +@protocol FileLoader + +- (BOOL)canLoadDebugFiles; + +/// Returns an array of DetectedFileType objects. +- (NSArray *)detectedTypesForData:(NSData *)data; + +/// Load a file. +/// The plugin should create HPSegment and HPSection objects. +/// It should also fill information about the CPU by setting the CPU family, the CPU subfamily and optionally the CPU plugin UUID. +/// The CPU plugin UUID should be set ONLY if you want a specific CPU plugin to be used. If you don't set it, it will be later set by Hopper. +/// During long operations, you should call the provided "callback" block to give a feedback to the user on the loading process. +- (FileLoaderLoadingStatus)loadData:(NSData *)data usingDetectedFileType:(DetectedFileType *)fileType options:(FileLoaderOptions)options forFile:(NSObject *)file usingCallback:(FileLoadingCallbackInfo)callback; +- (FileLoaderLoadingStatus)loadDebugData:(NSData *)data forFile:(NSObject *)file usingCallback:(FileLoadingCallbackInfo)callback; + +/// Hopper changed the base address of the file, and needs help to fix it up. +/// The address of every segment was shifted of "slide" bytes. +- (void)fixupRebasedFile:(NSObject *)file withSlide:(int64_t)slide originalFileData:(NSData *)fileData; + +/// Extract a file +/// In the case of a "composite loader", extract the NSData object of the selected file. +- (NSData *)extractFromData:(NSData *)data + usingDetectedFileType:(NSObject *)fileType + returnAdjustOffset:(uint64_t *)adjustOffset; + +@end diff --git a/HopperSDK/HPBasicBlock.h b/HopperSDK/HPBasicBlock.h new file mode 100644 index 0000000..77fed3e --- /dev/null +++ b/HopperSDK/HPBasicBlock.h @@ -0,0 +1,35 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +@protocol HPProcedure; +@protocol HPTag; + +@protocol HPBasicBlock + +- (Address)from; +- (Address)to; + +- (NSObject *)procedure; +- (NSUInteger)index; + +- (BOOL)hasSuccessors; +- (BOOL)hasPredecessors; + +- (NSArray *)predecessors; /// Array of NSObject * +- (NSArray *)successors; /// Array of NSObject * + +// Tags +- (void)addTag:(NSObject *)tag; +- (void)removeTag:(NSObject *)tag; +- (BOOL)hasTag:(NSObject *)tag; + +@end diff --git a/HopperSDK/HPDetectedFileType.h b/HopperSDK/HPDetectedFileType.h new file mode 100644 index 0000000..653ca05 --- /dev/null +++ b/HopperSDK/HPDetectedFileType.h @@ -0,0 +1,36 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import "CommonTypes.h" + +@protocol HPDetectedFileType + +/// Internal information for the loader. You can put any value you need here. +@property (assign) NSUInteger internalId; +@property (strong) id internalObject; + +@property (strong) NSString *shortDescriptionString; /// This string can be used by the command line tool to select the loader. + +@property (assign) BOOL compositeFile; /// The loader handles only a container (like a static library, a ZIP file...) and will delegate the loading process of the contained file to another loader. + +@property (copy) NSString *fileDescription; + +@property (assign) BOOL debugData; /// set to YES if this is debug data, like a dSYM file for instance. +@property (assign) DFTAddressWidth addressWidth; +@property (copy) NSString *cpuFamily; /// Generic CPU family name to discriminate CPU modules. Names may be "intel", "arm", "aarch64" or any other kind. +@property (copy) NSString *cpuSubFamily; /// Modes could be "x86" or "x86_64" for the "intel" family, or "v6", "v7", "v7s", "v7m" for the "arm" family. + +@property (strong) NSArray *additionalParameters; /// An array of NSObject that describes some additional parameters to present to the user when using this loader. + +@property (assign) BOOL lowPriority; + +@end diff --git a/HopperSDK/HPDisassembledFile.h b/HopperSDK/HPDisassembledFile.h new file mode 100644 index 0000000..bbd353d --- /dev/null +++ b/HopperSDK/HPDisassembledFile.h @@ -0,0 +1,171 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import "CommonTypes.h" + +@class HopperUUID; +@protocol CPUDefinition; +@protocol HPSegment; +@protocol HPSection; +@protocol HPProcedure; +@protocol HPTag; +@protocol CPUContext; + +typedef void (^FileLoadingCallbackInfo)(NSString *desc, float progress); + +@protocol HPDisassembledFile + +@property (copy) HopperUUID *fileUUID; + +@property (copy) NSString *cpuFamily; +@property (copy) NSString *cpuSubFamily; +@property (strong) NSObject *cpuDefinition; + +// Methods essentially used by Loader plugin +- (NSUInteger)addressSpaceWidthInBits; +- (void)setAddressSpaceWidthInBits:(NSUInteger)bits; + +- (BOOL)is32Bits; +- (BOOL)is64Bits; + +- (BOOL)analysisInProgress; + +- (Address)fileBaseAddress; + +- (void)addEntryPoint:(Address)address; +- (void)addPotentialProcedure:(Address)address; +- (Address)firstEntryPoint; +- (NSArray *)entryPoints; + +// Get access to segments and sections +- (NSArray *> *)segments; +- (NSUInteger)segmentCount; + +- (NSObject *)segmentForVirtualAddress:(Address)virtualAddress; +- (NSObject *)sectionForVirtualAddress:(Address)virtualAddress; + +- (NSObject *)segmentNamed:(NSString *)name; +- (NSObject *)sectionNamed:(NSString *)name; + +- (NSObject *)addSegmentAt:(Address)address size:(size_t)length; +- (NSObject *)addSegmentAt:(Address)address toExcludedAddress:(Address)endAddress; + +- (NSObject *)firstSegment; +- (NSObject *)lastSegment; +- (NSObject *)firstSection; +- (NSObject *)lastSection; + +- (NSObject *)previousSegment:(NSObject *)segment; +- (NSObject *)nextSegment:(NSObject *)segment; + +// CPUContext factory +- (NSObject *)buildCPUContext; + +// Access to the labels +/// An array of NSString objects, containing all labels. +- (NSArray *)allNames; +/// An array of NSNumber objects, representing the address of memory locations that was named. +- (NSArray *)allNamedAddresses; +- (NSString *)nameForVirtualAddress:(Address)virtualAddress; +- (NSString *)nearestNameBeforeVirtualAddress:(Address)virtualAddress; + +- (Address)nearestNamedAddressBeforeVirtualAddress:(Address)virtualAddress; +- (void)setName:(NSString *)name forVirtualAddress:(Address)virtualAddress reason:(NameCreationReason)reason; +- (Address)findVirtualAddressNamed:(NSString *)name; + +// Comments +- (void)removeCommentAtVirtualAddress:(Address)virtualAddress; +- (void)removeInlineCommentAtVirtualAddress:(Address)virtualAddress; +- (NSString *)commentAtVirtualAddress:(Address)virtualAddress; +- (NSString *)inlineCommentAtVirtualAddress:(Address)virtualAddress; +- (void)setComment:(NSString *)comment atVirtualAddress:(Address)virtualAddress reason:(CommentCreationReason)reason; +- (void)setInlineComment:(NSString *)comment atVirtualAddress:(Address)virtualAddress reason:(CommentCreationReason)reason; + +// Types +- (BOOL)typeCanBeModifiedAtAddress:(Address)va; +- (ByteType)typeForVirtualAddress:(Address)virtualAddress; +- (void)setType:(ByteType)type atVirtualAddress:(Address)virtualAddress forLength:(size_t)length; +- (BOOL)hasCodeAt:(Address)virtualAddress; +- (uint8_t)cpuModeAtVirtualAddress:(Address)virtualAddress; + +- (void)newRuntimeMethodNameDefinedAt:(Address)virtualAddress; + +// Searching +- (Address)findNextAddress:(Address)address ofTypeOrMetaType:(ByteType)typeOrMetaType wrapping:(BOOL)wrapping; + +// Instruction Operand Format +- (ArgFormat)formatForArgument:(int)argIndex atVirtualAddress:(Address)virtualAddress; +- (void)setFormat:(ArgFormat)format forArgument:(int)argIndex atVirtualAddress:(Address)virtualAddress; + +// Procedures +- (BOOL)hasProcedureAt:(Address)address; +- (NSObject *)procedureAt:(Address)address; +- (void)removeProcedure:(NSObject *)procedure; +- (void)removeProcedureAt:(Address)address; + +// Colors +- (BOOL)hasColorAt:(Address)address; +- (Color)colorAt:(Address)address; +- (void)setColor:(Color)color at:(Address)address; +- (void)setColor:(Color)color atRange:(AddressRange)range; +- (void)clearColorAt:(Address)address; +- (void)clearColorAtRange:(AddressRange)range; + +// Tags +- (NSObject *)tagWithName:(NSString *)tagName; +- (NSObject *)buildTag:(NSString *)tagName; +- (void)deleteTag:(NSObject *)tag; +- (void)deleteTagName:(NSString *)tagName; + +- (void)addTag:(NSObject *)tag at:(Address)address; +- (void)removeTag:(NSObject *)tag at:(Address)address; +- (NSArray *)tagsAt:(Address)virtualAddress; +- (BOOL)hasTag:(NSObject *)tag at:(Address)virtualAddress; + +// Problem list +- (void)addProblemAt:(Address)address withString:(NSString *)message; + +// Assembler +- (NSData *)assembleInstruction:(NSString *)instr atAddress:(Address)address withCPUMode:(uint8_t)cpuMode usingSyntaxVariant:(NSUInteger)syntax isRawData:(BOOL *)isRawData error:(NSError **)error; +- (NSData *)nopDataForRegion:(AddressRange)range; + +// Reading file +// Warning: don't use these methods in a Loader plugin, because no CPU plugin +// is attached to the file at this stage! +// Note: writing operations are defined in the HPDocument protocol. +- (int8_t)readInt8AtVirtualAddress:(Address)virtualAddress; +- (int16_t)readInt16AtVirtualAddress:(Address)virtualAddress; +- (int32_t)readInt32AtVirtualAddress:(Address)virtualAddress; +- (int64_t)readInt64AtVirtualAddress:(Address)virtualAddress; +- (uint8_t)readUInt8AtVirtualAddress:(Address)virtualAddress; +- (uint16_t)readUInt16AtVirtualAddress:(Address)virtualAddress; +- (uint32_t)readUInt32AtVirtualAddress:(Address)virtualAddress; +- (uint64_t)readUInt64AtVirtualAddress:(Address)virtualAddress; + +- (Address)readAddressAtVirtualAddress:(Address)virtualAddress; + +- (int64_t)readSignedLEB128AtVirtualAddress:(Address)virtualAddress length:(size_t *)numberLength; +- (uint64_t)readUnsignedLEB128AtVirtualAddress:(Address)virtualAddress length:(size_t *)numberLength; + +- (NSString *)readCStringAt:(Address)address; + +// Misc +- (Address)parseAddressString:(NSString *)addressString; + +// Undo/Redo Stack Management +- (BOOL)undoRedoLoggingEnabled; + +- (void)beginUndoRedoTransactionWithName:(NSString *)name; +- (void)endUndoRedoTransaction; +- (void)discardUndoRedoTransaction; + +@end diff --git a/HopperSDK/HPDocument.h b/HopperSDK/HPDocument.h new file mode 100644 index 0000000..55e878b --- /dev/null +++ b/HopperSDK/HPDocument.h @@ -0,0 +1,93 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import + +@protocol HPDisassembledFile; +@protocol HPSegment; +@protocol HPSection; + +@class NSWindow; + +typedef void (^CancelBlock)(void); + +@protocol HPDocument + +- (nullable NSObject *)disassembledFile; +- (nullable NSObject *)currentSegment; +- (nullable NSObject *)currentSection; + +- (void)updateStaticNames; +//- (void)updateStaticNameListAccordingToAddress:(Address)virtualAddress; + +// Cursor position, and moving without the navigation stack +- (Address)currentAddress; +- (AddressRange)selectedAddressRange; +- (void)moveCursorToVirtualAddress:(Address)a; +- (void)moveCursorToVirtualAddressRange:(AddressRange)range; + +// Moving using navigation stack +- (void)gotoVirtualAddress:(Address)virtualAddress; +- (void)gotoVirtualAddressString:(nonnull NSString *)virtualAddressString; +- (void)popAddressFromNavigationStack; + +// Background process +- (BOOL)backgroundProcessActive; +- (void)requestBackgroundProcessStop; + +// Determines if the user can interact with the document +- (BOOL)isWaiting; +- (void)beginToWait:(nonnull NSString *)message; +- (void)beginToWait:(nonnull NSString *)message cancelBlock:(nonnull CancelBlock)block; +- (void)updateWaitingValue:(float)value desc:(nonnull NSString *)description; +- (void)endWaiting; + +// Display message +- (void)logStringMessage:(nonnull NSString *)message; +- (void)logErrorStringMessage:(nonnull NSString *)message; +- (NSInteger)displayAlertWithMessageText:(nonnull NSString *)text + defaultButton:(nonnull NSString *)defaultButton + alternateButton:(nullable NSString *)alternateButton + otherButton:(nullable NSString *)otherButton + informativeText:(nullable NSString *)message; + +// Reading and modifying file +// These operations are performed in the endianess of the CPU module attached +// to the file. During the loading process, you should NOT use these methods, +// as no CPU plugin is attached at this stage! +- (int8_t)readInt8AtVirtualAddress:(Address)virtualAddress; +- (int16_t)readInt16AtVirtualAddress:(Address)virtualAddress; +- (int32_t)readInt32AtVirtualAddress:(Address)virtualAddress; +- (int64_t)readInt64AtVirtualAddress:(Address)virtualAddress; +- (uint8_t)readUInt8AtVirtualAddress:(Address)virtualAddress; +- (uint16_t)readUInt16AtVirtualAddress:(Address)virtualAddress; +- (uint32_t)readUInt32AtVirtualAddress:(Address)virtualAddress; +- (uint64_t)readUInt64AtVirtualAddress:(Address)virtualAddress; +- (Address)readAddressAtVirtualAddress:(Address)virtualAddress; + +- (nullable NSString *)readCStringAt:(Address)virtualAddress; + +- (BOOL)writeInt8:(int8_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeInt16:(int16_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeInt32:(int32_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeInt64:(int64_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeUInt8:(uint8_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeUInt16:(uint16_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeUInt32:(uint32_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeUInt64:(uint64_t)value atVirtualAddress:(Address)virtualAddress; +- (BOOL)writeAddress:(Address)value atVirtualAddress:(Address)virtualAddress; + +// Global UI +- (void)updateUI; +- (nullable NSWindow *)windowForSheet; + +@end diff --git a/HopperSDK/HPFormattedInstructionInfo.h b/HopperSDK/HPFormattedInstructionInfo.h new file mode 100644 index 0000000..f771211 --- /dev/null +++ b/HopperSDK/HPFormattedInstructionInfo.h @@ -0,0 +1,20 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +@protocol HPFormattedInstructionInfo + +/// Indicates that the operand contains a reference to a local variable on stack. +/// This is an information used by Hopper to let one renames stack variables from the interface +/// when the cursor is on an operand that references a variable, instead of renaming the address. +- (void)setFormatOfOperandIndex:(NSUInteger)operand toStackVariableWithDisplacement:(int)displacement; + +@end diff --git a/HopperSDK/HPHopperServices.h b/HopperSDK/HPHopperServices.h new file mode 100644 index 0000000..c1ce471 --- /dev/null +++ b/HopperSDK/HPHopperServices.h @@ -0,0 +1,59 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import "CommonTypes.h" + +@class HopperUUID; + +@protocol HPDocument; +@protocol HPDetectedFileType; +@protocol HPLoaderOptionComponents; + +@protocol HPHopperServices + +// Global +- (NSInteger)hopperMajorVersion; +- (NSInteger)hopperMinorVersion; +- (NSInteger)hopperRevision; +- (nonnull NSString *)hopperVersionString; + +- (nullable NSObject *)currentDocument; +- (void)logMessage:(nonnull NSString *)message; + +// Build an UUID object from a string like XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +- (nonnull HopperUUID *)UUIDWithString:(nonnull NSString *)uuidString; + +// New detected type +- (nullable NSObject *)detectedType; + +// Information about attributes used in the ASM view +- (nullable NSDictionary *)ASMOperatorAttributes; +- (nullable NSDictionary *)ASMNumberAttributes; +- (nullable NSDictionary *)ASMLanguageAttributes; +- (nullable NSDictionary *)ASMSubLanguageAttributes; +- (nullable NSDictionary *)ASMCommentAttributes; + +- (void)colorizeASMString:(nonnull NSMutableAttributedString *)string + operatorPredicate:(nonnull BOOL(^)(unichar c))operatorPredicate + languageWordPredicate:(nonnull BOOL(^)(NSString * __nonnull s))languageWordPredicate + subLanguageWordPredicate:(nonnull BOOL(^)(NSString * __nonnull s))subLanguageWordPredicate; + +// Options for loaders +- (nullable NSObject *)addressComponentWithLabel:(nonnull NSString *)label; +- (nullable NSObject *)checkboxComponentWithLabel:(nonnull NSString *)label; +- (nullable NSObject *)cpuComponentWithLabel:(nonnull NSString *)label; +- (nullable NSObject *)addressComponentWithLabel:(nonnull NSString *)label andValue:(Address)value; +- (nullable NSObject *)checkboxComponentWithLabel:(nonnull NSString *)label checked:(BOOL)checked; +- (nullable NSObject *)stringListComponentWithLabel:(nonnull NSString *)label andList:(nonnull NSArray *)strings; +- (nullable NSObject *)comboBoxComponentWithLabel:(nonnull NSString *)label andList:(nonnull NSArray *)strings; + +@end diff --git a/HopperSDK/HPLoaderOptionComponents.h b/HopperSDK/HPLoaderOptionComponents.h new file mode 100644 index 0000000..81dc92b --- /dev/null +++ b/HopperSDK/HPLoaderOptionComponents.h @@ -0,0 +1,30 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import + +@class HopperUUID; + +@protocol HPLoaderOptionComponents + +@property (assign) Address addressValue; + +@property (assign) BOOL isChecked; + +@property (strong) HopperUUID *cpuUUID; +@property (strong) NSString *cpuFamily; +@property (strong) NSString *cpuSubFamily; + +@property (strong) NSArray *stringList; +@property (assign) NSUInteger selectedStringIndex; + +@end diff --git a/HopperSDK/HPProcedure.h b/HopperSDK/HPProcedure.h new file mode 100644 index 0000000..17f0433 --- /dev/null +++ b/HopperSDK/HPProcedure.h @@ -0,0 +1,50 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +@protocol HPTag; +@protocol HPBasicBlock; +@protocol HPSegment; +@protocol CPUContext; + +@protocol HPProcedure + +- (BOOL)bpBasedFrame; +- (int32_t)savedRegistersSize; +- (int32_t)framePointerOffset; +- (int32_t)purgedBytes; +- (int32_t)localsSize; + +- (NSUInteger)basicBlockCount; +- (NSObject *)firstBasicBlock; +- (NSObject *)basicBlockStartingAt:(Address)address; +- (NSObject *)basicBlockContainingInstructionAt:(Address)address; +- (NSObject *)basicBlockAtIndex:(NSUInteger)index; + +- (NSObject *)segment; + +- (Address)entryPoint; +- (NSArray *)allExitBlocks; // Array of BasicBlock + +// Stack +- (int16_t)stackPointerOffsetAt:(Address)address; + +// Variables +- (NSString *)variableNameForDisplacement:(int64_t)disp; +- (void)setVariableName:(NSString *)name forDisplacement:(int64_t)disp; +- (NSString *)resolvedVariableNameForDisplacement:(int64_t)disp usingCPUContext:(NSObject *)cpuContext; + +// Tags +- (void)addTag:(NSObject *)tag; +- (void)removeTag:(NSObject *)tag; +- (BOOL)hasTag:(NSObject *)tag; + +@end diff --git a/HopperSDK/HPSection.h b/HopperSDK/HPSection.h new file mode 100644 index 0000000..0fd08f0 --- /dev/null +++ b/HopperSDK/HPSection.h @@ -0,0 +1,40 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +@protocol HPSegment; + +@protocol HPSection + +@property (nonatomic, copy) NSString *sectionName; + +@property (nonatomic, assign) uint64_t fileOffset; +@property (nonatomic, assign) uint64_t fileLength; + +@property (nonatomic) BOOL pureCodeSection; +@property (nonatomic) BOOL pureDataSection; +@property (nonatomic) BOOL containsCode; +@property (nonatomic) BOOL pureCStringSection; + +- (Address)startAddress; +- (size_t)length; + +- (Address)endAddress; + +- (BOOL)hasDataOnDisk; + +- (NSObject *)segment; +- (NSObject *)previousSection; +- (NSObject *)nextSection; + +- (BOOL)sectionContainsAddress:(Address)address; + +@end diff --git a/HopperSDK/HPSegment.h b/HopperSDK/HPSegment.h new file mode 100644 index 0000000..8fe2b3e --- /dev/null +++ b/HopperSDK/HPSegment.h @@ -0,0 +1,57 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +@protocol HPSection; + +@protocol HPSegment + +@property (nonatomic, copy) NSString *segmentName; + +@property (nonatomic) uint64_t fileOffset; +@property (nonatomic) uint64_t fileLength; + +- (BOOL)hasMappedData; +- (NSData *)mappedData; +- (void)setMappedData:(NSData *)data; + +- (Address)startAddress; +- (Address)endAddress; +- (size_t)length; + +- (BOOL)containsVirtualAddress:(Address)virtualAddress; + +- (NSArray *> *)sections; +- (NSUInteger)sectionCount; + +- (NSArray *> *)procedures; + +- (NSObject *)nextSegment; +- (NSObject *)previousSegment; + +- (NSObject *)addSectionAt:(Address)address size:(size_t)length; +- (NSObject *)addSectionAt:(Address)address toExcludedAddress:(Address)endAddress; + +- (NSObject *)firstSection; +- (NSObject *)lastSection; +- (NSObject *)sectionNamed:(NSString *)name; + +- (NSObject *)sectionAtVirtualAddress:(Address)virtualAddress; + +- (NSString *)nameForVirtualAddress:(Address)virtualAddress; + +// XREFs +- (NSArray *)referencesToAddress:(Address)virtualAddress; +- (NSArray *)referencesFromAddress:(Address)virtualAddress; +- (void)removeReferencesOfAddress:(Address)referenced fromAddress:(Address)origin; +- (void)addReferencesToAddress:(Address)referenced fromAddress:(Address)origin; + +@end diff --git a/HopperSDK/HPTag.h b/HopperSDK/HPTag.h new file mode 100644 index 0000000..32a20cb --- /dev/null +++ b/HopperSDK/HPTag.h @@ -0,0 +1,19 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import + +@protocol HPTag + +- (NSString *)name; + +@end diff --git a/HopperSDK/Hopper.h b/HopperSDK/Hopper.h new file mode 100644 index 0000000..094c7fd --- /dev/null +++ b/HopperSDK/Hopper.h @@ -0,0 +1,38 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import "CommonTypes.h" +#import "HPHopperServices.h" +#import "HopperPlugin.h" + +// The Core Structure +#import "HPDocument.h" +#import "HPDisassembledFile.h" +#import "HPSegment.h" +#import "HPSection.h" +#import "HPProcedure.h" +#import "HPBasicBlock.h" +#import "HPFormattedInstructionInfo.h" +#import "HPTag.h" +#import "DisasmStruct.h" + +// Loader +#import "HPLoaderOptionComponents.h" +#import "HPDetectedFileType.h" +#import "FileLoader.h" + +// CPU Support +#import "CPUDefinition.h" +#import "CPUContext.h" + +// Tool +#import "HopperTool.h" diff --git a/HopperSDK/HopperPlugin.h b/HopperSDK/HopperPlugin.h new file mode 100644 index 0000000..43365ec --- /dev/null +++ b/HopperSDK/HopperPlugin.h @@ -0,0 +1,34 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import +#import "CommonTypes.h" + +@interface HopperUUID: NSObject +@end + +@protocol HPHopperServices; + +@protocol HopperPlugin + +- (nonnull instancetype)initWithHopperServices:(nonnull NSObject *)services; + +- (nonnull HopperUUID *)pluginUUID; +- (HopperPluginType)pluginType; + +- (nonnull NSString *)pluginName; +- (nonnull NSString *)pluginDescription; +- (nonnull NSString *)pluginAuthor; +- (nonnull NSString *)pluginCopyright; +- (nonnull NSString *)pluginVersion; + +@end diff --git a/HopperSDK/HopperTool.h b/HopperSDK/HopperTool.h new file mode 100644 index 0000000..81e6fc1 --- /dev/null +++ b/HopperSDK/HopperTool.h @@ -0,0 +1,28 @@ +// +// Hopper Disassembler SDK +// +// (c)2014 - Cryptic Apps SARL. All Rights Reserved. +// http://www.hopperapp.com +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// + +#import +#import "HopperPlugin.h" + +#define HPM_TITLE @"Title" +#define HPM_SELECTOR @"Selector" +#define HPM_SUBMENU @"Submenu" + +@protocol HopperTool + +/// Returns an array of menu description to be added to Hopper. +/// A description contains at least a HPM_TITLE key, and a HPM_SELECTOR or a HPM_SUBMENU key. +/// The HPM_SELECTOR is a string which contains a selector name, which will be resolved by +/// Hopper at runtime, using the NSSelectorFromString system method. +- (nonnull NSArray *> *)toolMenuDescription; + +@end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e7c264b --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2016 Keith Smiley (http://keith.so) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..02f4e08 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + xcodebuild -project "Swift Demangle.xcodeproj" -scheme Demangler test diff --git a/README.md b/README.md new file mode 100644 index 0000000..4871988 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# hopper-swift-demangle + +This is a [Hopper][hopper] plugin (not script) written in Swift for +demangling Swift symbols. + +## Example + +![example][example] + +## Installation + +Download the latest release from [here][releases]. Then: + +``` +$ unzip SwiftDemangle.hopperTool.zip +$ mkdir -p ~/Library/Application\ Support/Hopper/PlugIns/Tools/ +$ mv Swift\ Demangle.hopperTool ~/Library/Application\ Support/Hopper/PlugIns/Tools/ +``` + +## Usage + +Once you've installed the plugin you should have a new *Tool Plugins* +menu item. There you can see the added Swift demangle commands. + +![usage][usage] + +[hopper]: http://hopperapp.com/ +[example]: https://raw.githubusercontent.com/keith/hopper-swift-demangle/master/screenshots/example.png +[releases]: https://github.com/keith/hopper-swift-demangle/releases +[usage]: https://raw.githubusercontent.com/keith/hopper-swift-demangle/master/screenshots/usage.png diff --git a/Swift Demangle.xcodeproj/project.pbxproj b/Swift Demangle.xcodeproj/project.pbxproj new file mode 100644 index 0000000..003ff73 --- /dev/null +++ b/Swift Demangle.xcodeproj/project.pbxproj @@ -0,0 +1,649 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C25C61611CFA7AC8000A09AC /* SwiftDemangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C61601CFA7AC8000A09AC /* SwiftDemangle.swift */; }; + C25C61771CFBE500000A09AC /* Demangler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25C616D1CFBE500000A09AC /* Demangler.framework */; }; + C25C617C1CFBE500000A09AC /* DemanglerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C617B1CFBE500000A09AC /* DemanglerTests.swift */; }; + C25C61841CFBE511000A09AC /* Demangler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25C616D1CFBE500000A09AC /* Demangler.framework */; }; + C25C61881CFBE52F000A09AC /* Demangler.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C25C616D1CFBE500000A09AC /* Demangler.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C25C618A1CFBE54C000A09AC /* Demangler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C61891CFBE54C000A09AC /* Demangler.swift */; }; + C25C618C1CFBEC4D000A09AC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C618B1CFBEC4D000A09AC /* String+Extension.swift */; }; + C25C618E1CFBEC64000A09AC /* StringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C618D1CFBEC64000A09AC /* StringExtensionTests.swift */; }; + C2D4E5281CFCC4C700820B0B /* NSBundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5271CFCC4C700820B0B /* NSBundle+Extension.swift */; }; + C2D4E52B1CFCDC4700820B0B /* HPDocument+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E52A1CFCDC4700820B0B /* HPDocument+Extension.swift */; }; + C2D4E52D1CFD059200820B0B /* SwiftDemangle+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E52C1CFD059200820B0B /* SwiftDemangle+Actions.swift */; }; + C2D4E52F1CFD188F00820B0B /* ShouldDemangleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E52E1CFD188F00820B0B /* ShouldDemangleTests.swift */; }; + C2D4E5311CFD18E100820B0B /* DemangleObjCSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5301CFD18E100820B0B /* DemangleObjCSelectorTests.swift */; }; + C2D4E5331CFD40EB00820B0B /* InternalDemangler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5321CFD40EB00820B0B /* InternalDemangler.swift */; }; + C2D4E5351CFD411C00820B0B /* LibraryDemangler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5341CFD411C00820B0B /* LibraryDemangler.swift */; }; + C2D4E5371CFD413500820B0B /* ShellDemangler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5361CFD413500820B0B /* ShellDemangler.swift */; }; + C2D4E5391CFD5ABA00820B0B /* SwiftDemangle+DemangleAll.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D4E5381CFD5ABA00820B0B /* SwiftDemangle+DemangleAll.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C25C61781CFBE500000A09AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C25C613B1CFA7A84000A09AC /* Project object */; + proxyType = 1; + remoteGlobalIDString = C25C616C1CFBE500000A09AC; + remoteInfo = Demangler; + }; + C25C61851CFBE51E000A09AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C25C613B1CFA7A84000A09AC /* Project object */; + proxyType = 1; + remoteGlobalIDString = C25C616C1CFBE500000A09AC; + remoteInfo = Demangler; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C25C61871CFBE527000A09AC /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C25C61881CFBE52F000A09AC /* Demangler.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C25C61431CFA7A84000A09AC /* Swift Demangle.hopperTool */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Swift Demangle.hopperTool"; sourceTree = BUILT_PRODUCTS_DIR; }; + C25C61461CFA7A84000A09AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C25C614D1CFA7AB2000A09AC /* CommonTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonTypes.h; sourceTree = ""; }; + C25C614E1CFA7AB2000A09AC /* CPUContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUContext.h; sourceTree = ""; }; + C25C614F1CFA7AB2000A09AC /* CPUDefinition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUDefinition.h; sourceTree = ""; }; + C25C61501CFA7AB2000A09AC /* DisasmStruct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisasmStruct.h; sourceTree = ""; }; + C25C61511CFA7AB2000A09AC /* FileLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileLoader.h; sourceTree = ""; }; + C25C61521CFA7AB2000A09AC /* Hopper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Hopper.h; sourceTree = ""; }; + C25C61531CFA7AB2000A09AC /* HopperPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HopperPlugin.h; sourceTree = ""; }; + C25C61541CFA7AB2000A09AC /* HopperTool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HopperTool.h; sourceTree = ""; }; + C25C61551CFA7AB2000A09AC /* HPBasicBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPBasicBlock.h; sourceTree = ""; }; + C25C61561CFA7AB2000A09AC /* HPDetectedFileType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPDetectedFileType.h; sourceTree = ""; }; + C25C61571CFA7AB2000A09AC /* HPDisassembledFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPDisassembledFile.h; sourceTree = ""; }; + C25C61581CFA7AB2000A09AC /* HPDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPDocument.h; sourceTree = ""; }; + C25C61591CFA7AB2000A09AC /* HPFormattedInstructionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPFormattedInstructionInfo.h; sourceTree = ""; }; + C25C615A1CFA7AB2000A09AC /* HPHopperServices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPHopperServices.h; sourceTree = ""; }; + C25C615B1CFA7AB2000A09AC /* HPLoaderOptionComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPLoaderOptionComponents.h; sourceTree = ""; }; + C25C615C1CFA7AB2000A09AC /* HPProcedure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPProcedure.h; sourceTree = ""; }; + C25C615D1CFA7AB2000A09AC /* HPSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPSection.h; sourceTree = ""; }; + C25C615E1CFA7AB2000A09AC /* HPSegment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPSegment.h; sourceTree = ""; }; + C25C615F1CFA7AB2000A09AC /* HPTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPTag.h; sourceTree = ""; }; + C25C61601CFA7AC8000A09AC /* SwiftDemangle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftDemangle.swift; sourceTree = ""; }; + C25C61621CFA7B15000A09AC /* Swift Demangle-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Swift Demangle-Bridging-Header.h"; sourceTree = ""; }; + C25C616D1CFBE500000A09AC /* Demangler.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Demangler.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C25C61711CFBE500000A09AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C25C61761CFBE500000A09AC /* DemanglerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemanglerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C25C617B1CFBE500000A09AC /* DemanglerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemanglerTests.swift; sourceTree = ""; }; + C25C617D1CFBE500000A09AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C25C61891CFBE54C000A09AC /* Demangler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Demangler.swift; sourceTree = ""; }; + C25C618B1CFBEC4D000A09AC /* String+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + C25C618D1CFBEC64000A09AC /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; + C2D4E5271CFCC4C700820B0B /* NSBundle+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBundle+Extension.swift"; sourceTree = ""; }; + C2D4E52A1CFCDC4700820B0B /* HPDocument+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HPDocument+Extension.swift"; sourceTree = ""; }; + C2D4E52C1CFD059200820B0B /* SwiftDemangle+Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftDemangle+Actions.swift"; sourceTree = ""; }; + C2D4E52E1CFD188F00820B0B /* ShouldDemangleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShouldDemangleTests.swift; sourceTree = ""; }; + C2D4E5301CFD18E100820B0B /* DemangleObjCSelectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemangleObjCSelectorTests.swift; sourceTree = ""; }; + C2D4E5321CFD40EB00820B0B /* InternalDemangler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalDemangler.swift; sourceTree = ""; }; + C2D4E5341CFD411C00820B0B /* LibraryDemangler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryDemangler.swift; sourceTree = ""; }; + C2D4E5361CFD413500820B0B /* ShellDemangler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShellDemangler.swift; sourceTree = ""; }; + C2D4E5381CFD5ABA00820B0B /* SwiftDemangle+DemangleAll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftDemangle+DemangleAll.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C25C61401CFA7A84000A09AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C25C61841CFBE511000A09AC /* Demangler.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C25C61691CFBE500000A09AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C25C61731CFBE500000A09AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C25C61771CFBE500000A09AC /* Demangler.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C25C613A1CFA7A84000A09AC = { + isa = PBXGroup; + children = ( + C25C61451CFA7A84000A09AC /* Swift Demangle */, + C25C616E1CFBE500000A09AC /* Demangler */, + C25C617A1CFBE500000A09AC /* DemanglerTests */, + C25C614C1CFA7AB2000A09AC /* HopperSDK */, + C25C61441CFA7A84000A09AC /* Products */, + ); + sourceTree = ""; + }; + C25C61441CFA7A84000A09AC /* Products */ = { + isa = PBXGroup; + children = ( + C25C61431CFA7A84000A09AC /* Swift Demangle.hopperTool */, + C25C616D1CFBE500000A09AC /* Demangler.framework */, + C25C61761CFBE500000A09AC /* DemanglerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C25C61451CFA7A84000A09AC /* Swift Demangle */ = { + isa = PBXGroup; + children = ( + C2D4E52A1CFCDC4700820B0B /* HPDocument+Extension.swift */, + C2D4E5271CFCC4C700820B0B /* NSBundle+Extension.swift */, + C25C61601CFA7AC8000A09AC /* SwiftDemangle.swift */, + C2D4E52C1CFD059200820B0B /* SwiftDemangle+Actions.swift */, + C2D4E5381CFD5ABA00820B0B /* SwiftDemangle+DemangleAll.swift */, + C25C61931CFCBFD6000A09AC /* Supporting Files */, + ); + path = "Swift Demangle"; + sourceTree = ""; + }; + C25C614C1CFA7AB2000A09AC /* HopperSDK */ = { + isa = PBXGroup; + children = ( + C25C614E1CFA7AB2000A09AC /* CPUContext.h */, + C25C614F1CFA7AB2000A09AC /* CPUDefinition.h */, + C25C614D1CFA7AB2000A09AC /* CommonTypes.h */, + C25C61501CFA7AB2000A09AC /* DisasmStruct.h */, + C25C61511CFA7AB2000A09AC /* FileLoader.h */, + C25C61551CFA7AB2000A09AC /* HPBasicBlock.h */, + C25C61561CFA7AB2000A09AC /* HPDetectedFileType.h */, + C25C61571CFA7AB2000A09AC /* HPDisassembledFile.h */, + C25C61581CFA7AB2000A09AC /* HPDocument.h */, + C25C61591CFA7AB2000A09AC /* HPFormattedInstructionInfo.h */, + C25C615A1CFA7AB2000A09AC /* HPHopperServices.h */, + C25C615B1CFA7AB2000A09AC /* HPLoaderOptionComponents.h */, + C25C615C1CFA7AB2000A09AC /* HPProcedure.h */, + C25C615D1CFA7AB2000A09AC /* HPSection.h */, + C25C615E1CFA7AB2000A09AC /* HPSegment.h */, + C25C615F1CFA7AB2000A09AC /* HPTag.h */, + C25C61521CFA7AB2000A09AC /* Hopper.h */, + C25C61531CFA7AB2000A09AC /* HopperPlugin.h */, + C25C61541CFA7AB2000A09AC /* HopperTool.h */, + ); + path = HopperSDK; + sourceTree = ""; + }; + C25C616E1CFBE500000A09AC /* Demangler */ = { + isa = PBXGroup; + children = ( + C25C61891CFBE54C000A09AC /* Demangler.swift */, + C2D4E5321CFD40EB00820B0B /* InternalDemangler.swift */, + C2D4E5341CFD411C00820B0B /* LibraryDemangler.swift */, + C2D4E5361CFD413500820B0B /* ShellDemangler.swift */, + C25C618B1CFBEC4D000A09AC /* String+Extension.swift */, + C25C61921CFCBFCD000A09AC /* Supporting Files */, + ); + path = Demangler; + sourceTree = ""; + }; + C25C617A1CFBE500000A09AC /* DemanglerTests */ = { + isa = PBXGroup; + children = ( + C2D4E5301CFD18E100820B0B /* DemangleObjCSelectorTests.swift */, + C25C617B1CFBE500000A09AC /* DemanglerTests.swift */, + C2D4E52E1CFD188F00820B0B /* ShouldDemangleTests.swift */, + C25C618D1CFBEC64000A09AC /* StringExtensionTests.swift */, + C25C61911CFCBFC1000A09AC /* Supporting Files */, + ); + path = DemanglerTests; + sourceTree = ""; + }; + C25C61911CFCBFC1000A09AC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C25C617D1CFBE500000A09AC /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + C25C61921CFCBFCD000A09AC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C25C61711CFBE500000A09AC /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + C25C61931CFCBFD6000A09AC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C25C61461CFA7A84000A09AC /* Info.plist */, + C25C61621CFA7B15000A09AC /* Swift Demangle-Bridging-Header.h */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C25C616A1CFBE500000A09AC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C25C61421CFA7A84000A09AC /* Swift Demangle */ = { + isa = PBXNativeTarget; + buildConfigurationList = C25C61491CFA7A84000A09AC /* Build configuration list for PBXNativeTarget "Swift Demangle" */; + buildPhases = ( + C25C613F1CFA7A84000A09AC /* Sources */, + C25C61401CFA7A84000A09AC /* Frameworks */, + C25C61871CFBE527000A09AC /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + C25C61861CFBE51E000A09AC /* PBXTargetDependency */, + ); + name = "Swift Demangle"; + productName = "Swift Demangle"; + productReference = C25C61431CFA7A84000A09AC /* Swift Demangle.hopperTool */; + productType = "com.apple.product-type.bundle"; + }; + C25C616C1CFBE500000A09AC /* Demangler */ = { + isa = PBXNativeTarget; + buildConfigurationList = C25C617E1CFBE500000A09AC /* Build configuration list for PBXNativeTarget "Demangler" */; + buildPhases = ( + C25C61681CFBE500000A09AC /* Sources */, + C25C61691CFBE500000A09AC /* Frameworks */, + C25C616A1CFBE500000A09AC /* Headers */, + C25C616B1CFBE500000A09AC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Demangler; + productName = Demangler; + productReference = C25C616D1CFBE500000A09AC /* Demangler.framework */; + productType = "com.apple.product-type.framework"; + }; + C25C61751CFBE500000A09AC /* DemanglerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C25C61811CFBE500000A09AC /* Build configuration list for PBXNativeTarget "DemanglerTests" */; + buildPhases = ( + C25C61721CFBE500000A09AC /* Sources */, + C25C61731CFBE500000A09AC /* Frameworks */, + C25C61741CFBE500000A09AC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C25C61791CFBE500000A09AC /* PBXTargetDependency */, + ); + name = DemanglerTests; + productName = DemanglerTests; + productReference = C25C61761CFBE500000A09AC /* DemanglerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C25C613B1CFA7A84000A09AC /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0730; + ORGANIZATIONNAME = smileykeith; + TargetAttributes = { + C25C61421CFA7A84000A09AC = { + CreatedOnToolsVersion = 7.3.1; + }; + C25C616C1CFBE500000A09AC = { + CreatedOnToolsVersion = 7.3.1; + }; + C25C61751CFBE500000A09AC = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = C25C613E1CFA7A84000A09AC /* Build configuration list for PBXProject "Swift Demangle" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C25C613A1CFA7A84000A09AC; + productRefGroup = C25C61441CFA7A84000A09AC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C25C61421CFA7A84000A09AC /* Swift Demangle */, + C25C616C1CFBE500000A09AC /* Demangler */, + C25C61751CFBE500000A09AC /* DemanglerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C25C616B1CFBE500000A09AC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C25C61741CFBE500000A09AC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C25C613F1CFA7A84000A09AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2D4E52D1CFD059200820B0B /* SwiftDemangle+Actions.swift in Sources */, + C2D4E52B1CFCDC4700820B0B /* HPDocument+Extension.swift in Sources */, + C2D4E5281CFCC4C700820B0B /* NSBundle+Extension.swift in Sources */, + C2D4E5391CFD5ABA00820B0B /* SwiftDemangle+DemangleAll.swift in Sources */, + C25C61611CFA7AC8000A09AC /* SwiftDemangle.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C25C61681CFBE500000A09AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C25C618C1CFBEC4D000A09AC /* String+Extension.swift in Sources */, + C25C618A1CFBE54C000A09AC /* Demangler.swift in Sources */, + C2D4E5331CFD40EB00820B0B /* InternalDemangler.swift in Sources */, + C2D4E5371CFD413500820B0B /* ShellDemangler.swift in Sources */, + C2D4E5351CFD411C00820B0B /* LibraryDemangler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C25C61721CFBE500000A09AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C25C618E1CFBEC64000A09AC /* StringExtensionTests.swift in Sources */, + C2D4E5311CFD18E100820B0B /* DemangleObjCSelectorTests.swift in Sources */, + C25C617C1CFBE500000A09AC /* DemanglerTests.swift in Sources */, + C2D4E52F1CFD188F00820B0B /* ShouldDemangleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C25C61791CFBE500000A09AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C25C616C1CFBE500000A09AC /* Demangler */; + targetProxy = C25C61781CFBE500000A09AC /* PBXContainerItemProxy */; + }; + C25C61861CFBE51E000A09AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C25C616C1CFBE500000A09AC /* Demangler */; + targetProxy = C25C61851CFBE51E000A09AC /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C25C61471CFA7A84000A09AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + C25C61481CFA7A84000A09AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + C25C614A1CFA7A84000A09AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES; + COMBINE_HIDPI_IMAGES = YES; + DEPLOYMENT_LOCATION = YES; + DSTROOT = "$(HOME)"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Swift Demangle/Supporting Files/Info.plist"; + INSTALL_PATH = "/Library/Application Support/Hopper/PlugIns/Tools"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.smileykeith.Swift-Demangle"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Swift Demangle/Supporting Files/Swift Demangle-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + WRAPPER_EXTENSION = hopperTool; + }; + name = Debug; + }; + C25C614B1CFA7A84000A09AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES; + COMBINE_HIDPI_IMAGES = YES; + DEPLOYMENT_LOCATION = YES; + DSTROOT = "$(HOME)"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Swift Demangle/Supporting Files/Info.plist"; + INSTALL_PATH = "/Library/Application Support/Hopper/PlugIns/Tools"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.smileykeith.Swift-Demangle"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Swift Demangle/Supporting Files/Swift Demangle-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + WRAPPER_EXTENSION = hopperTool; + }; + name = Release; + }; + C25C617F1CFBE500000A09AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Demangler/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.smileykeith.Demangler; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C25C61801CFBE500000A09AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Demangler/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.smileykeith.Demangler; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C25C61821CFBE500000A09AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "DemanglerTests/Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.smileykeith.DemanglerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C25C61831CFBE500000A09AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "DemanglerTests/Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.smileykeith.DemanglerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C25C613E1CFA7A84000A09AC /* Build configuration list for PBXProject "Swift Demangle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C25C61471CFA7A84000A09AC /* Debug */, + C25C61481CFA7A84000A09AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C25C61491CFA7A84000A09AC /* Build configuration list for PBXNativeTarget "Swift Demangle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C25C614A1CFA7A84000A09AC /* Debug */, + C25C614B1CFA7A84000A09AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C25C617E1CFBE500000A09AC /* Build configuration list for PBXNativeTarget "Demangler" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C25C617F1CFBE500000A09AC /* Debug */, + C25C61801CFBE500000A09AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C25C61811CFBE500000A09AC /* Build configuration list for PBXNativeTarget "DemanglerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C25C61821CFBE500000A09AC /* Debug */, + C25C61831CFBE500000A09AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C25C613B1CFA7A84000A09AC /* Project object */; +} diff --git a/Swift Demangle.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Swift Demangle.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..a2df7e8 --- /dev/null +++ b/Swift Demangle.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Demangler.xcscheme b/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Demangler.xcscheme new file mode 100644 index 0000000..7b52974 --- /dev/null +++ b/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Demangler.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Swift Demangle.xcscheme b/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Swift Demangle.xcscheme new file mode 100644 index 0000000..6705d5f --- /dev/null +++ b/Swift Demangle.xcodeproj/xcshareddata/xcschemes/Swift Demangle.xcscheme @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swift Demangle/HPDocument+Extension.swift b/Swift Demangle/HPDocument+Extension.swift new file mode 100644 index 0000000..3653352 --- /dev/null +++ b/Swift Demangle/HPDocument+Extension.swift @@ -0,0 +1,35 @@ +extension HPDocument { + func wait(withReason reason: String, forWork work: (document: HPDocument, file: HPDisassembledFile, + shouldCancel: UnsafeMutablePointer) -> Void) + { + if self.isWaiting() { + return self.logErrorStringMessage("Already waiting on something") + } + + guard let file = self.disassembledFile() else { + return self.logErrorStringMessage("Document doesn't have a disassembled file yet") + } + + var shouldCancel = false + self.beginToWait(reason) + +// +// For some reason passing a cancelBlock crashes 100%. But not if you do it via cycript :/ +// Hopefully I can figure this out since it can take a long time to demangle large executables +// +// self.beginToWait(reason, cancelBlock: { [weak self] in +// shouldCancel = true +// self?.logErrorStringMessage("Attempting to cancel") +// }) +// + + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { + work(document: self, file: file, shouldCancel: &shouldCancel) + + dispatch_async(dispatch_get_main_queue()) { + self.updateStaticNames() + self.endWaiting() + } + } + } +} diff --git a/Swift Demangle/NSBundle+Extension.swift b/Swift Demangle/NSBundle+Extension.swift new file mode 100644 index 0000000..86fc44f --- /dev/null +++ b/Swift Demangle/NSBundle+Extension.swift @@ -0,0 +1,7 @@ +import Foundation + +extension NSBundle { + var pluginVersion: String { + return self.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" + } +} diff --git a/Swift Demangle/Supporting Files/Info.plist b/Swift Demangle/Supporting Files/Info.plist new file mode 100644 index 0000000..b6574fd --- /dev/null +++ b/Swift Demangle/Supporting Files/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2016 smileykeith. All rights reserved. + NSPrincipalClass + SwiftDemangle + + diff --git a/Swift Demangle/Supporting Files/Swift Demangle-Bridging-Header.h b/Swift Demangle/Supporting Files/Swift Demangle-Bridging-Header.h new file mode 100644 index 0000000..f936565 --- /dev/null +++ b/Swift Demangle/Supporting Files/Swift Demangle-Bridging-Header.h @@ -0,0 +1 @@ +#import "Hopper.h" diff --git a/Swift Demangle/SwiftDemangle+Actions.swift b/Swift Demangle/SwiftDemangle+Actions.swift new file mode 100644 index 0000000..73d05d9 --- /dev/null +++ b/Swift Demangle/SwiftDemangle+Actions.swift @@ -0,0 +1,61 @@ +import Demangler +import Foundation + +private enum Action { + case DemangleClosest + case DemangleAll +} + +extension SwiftDemangle { + @objc + func demangleClosestName() { + self.performAction(.DemangleClosest) + } + + @objc + func demangleAllProcedures() { + self.performAction(.DemangleAll) + } + + private func performAction(action: Action) { + let function: HPDocument -> Void + switch action { + case .DemangleClosest: + function = self.demangleClosestName + case .DemangleAll: + function = self.demangleAllProcedures + } + + if let document = self.services.currentDocument() { + function(document) + } else { + self.services.logMessage("No document currently loaded") + } + } + + // MARK: - Actions + + private func demangleClosestName(withDocument document: HPDocument) { + document.wait(withReason: "Demangling Closest Name") { document, file, _ in + let address = file.nearestNamedAddressBeforeVirtualAddress(document.currentAddress()) + let mangledString = file.nameForVirtualAddress(address) + let demangleResult = self.demangler.demangle(string: mangledString) + self.handle(demangleResult: demangleResult, forAddress: address, mangledString: mangledString, + file: file, document: document) + } + } + + func handle(demangleResult result: DemangleResult, forAddress address: Address, + mangledString: String?, file: HPDisassembledFile, document: HPDocument) + { + switch result { + case .Success(let demangledString): + file.setName(demangledString, forVirtualAddress: address, reason: .NCReason_Script) + document.logStringMessage("Demangled '\(mangledString ?? "")' -> '\(demangledString)'") + case .Ignored(let ignoredString): + document.logStringMessage("Ignoring '\(ignoredString)'") + case .Failed(let failedString): + document.logStringMessage("Failed to demangle '\(failedString)'") + } + } +} diff --git a/Swift Demangle/SwiftDemangle+DemangleAll.swift b/Swift Demangle/SwiftDemangle+DemangleAll.swift new file mode 100644 index 0000000..5bdb8fb --- /dev/null +++ b/Swift Demangle/SwiftDemangle+DemangleAll.swift @@ -0,0 +1,77 @@ +import Foundation + +extension SwiftDemangle { + func demangleAllProcedures(withDocument document: HPDocument) { + if self.showAnalysisAlertAndCancel(forDocument: document) { + return + } + + document.wait(withReason: "Demangling All Procedures") { document, file, shouldCancel in + self.demangleAllProcedures(withDocument: document, file: file, shouldCancel: shouldCancel) + } + } + + private func demangleAllProcedures(withDocument document: HPDocument, + file: HPDisassembledFile, + shouldCancel: UnsafeMutablePointer) + { + let segments = file.segments() + var procedureCount: Int? + + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { + let totalProcedureCount = segments.reduce(0) { accumulator, segment in + return accumulator + segment.procedures().count + } + + procedureCount = totalProcedureCount + document.logStringMessage("Found \(totalProcedureCount) procedures") + } + + var processedCount = 0 + for segment in segments { + for procedure in segment.procedures() { + if shouldCancel.memory { + let countString = procedureCount.map(String.init) ?? "Unknown" + return document.logErrorStringMessage( + "Cancelling after demangling \(processedCount) of \(countString) procedures") + } + + if let procedureCount = procedureCount { + let progressString = "Demangling \(processedCount + 1) of \(procedureCount)" + let percentDone = Float(processedCount) / Float(procedureCount) + document.updateWaitingValue(percentDone, desc: progressString) + } else { + document.updateWaitingValue(1, desc: "Demangling \(processedCount + 1)") + } + + let procedureAddress = procedure.entryPoint() + let mangledString: String? = segment.nameForVirtualAddress(procedureAddress) + let demangleResult = self.demangler.demangle(string: mangledString) + self.handle(demangleResult: demangleResult, forAddress: procedureAddress, + mangledString: mangledString, file: file, document: document) + + processedCount += 1 + } + } + } + + + /** + Show an alert indicating analysis is still in progress and not all procedures will be demangled. + (Since those that are not yet loaded cannot be demangled) + + - returns: true if the user decides that we should cancel + */ + private func showAnalysisAlertAndCancel(forDocument document: HPDocument) -> Bool { + if document.disassembledFile()?.analysisInProgress() == true { + let response = document.displayAlertWithMessageText("File is still being analyzed", + defaultButton: "Cancel", alternateButton: "Continue", otherButton: nil, + informativeText: "Would you like to demangle what has already been loaded?") + + // Cancel Button Pressed + return response == 1 + } + + return false + } +} diff --git a/Swift Demangle/SwiftDemangle.swift b/Swift Demangle/SwiftDemangle.swift new file mode 100644 index 0000000..6346d36 --- /dev/null +++ b/Swift Demangle/SwiftDemangle.swift @@ -0,0 +1,58 @@ +import Demangler +import Foundation + +@objc +final class SwiftDemangle: NSObject, HopperTool { + var services: HPHopperServices + var demangler = Demangler() + + init(hopperServices: HPHopperServices) { + self.services = hopperServices + } + + func pluginUUID() -> HopperUUID { + return self.services.UUIDWithString("C8FAB152-90FC-4C70-9FA2-4828A2DF322F") + } + + func pluginName() -> String { + return "Swift Demangle" + } + + func pluginAuthor() -> String { + return "Keith Smiley" + } + + func pluginVersion() -> String { + return NSBundle.mainBundle().pluginVersion + } + + func pluginCopyright() -> String { + return "©2016 Keith Smiley" + } + + func pluginDescription() -> String { + return "Demangle Swift Selectors" + } + + func pluginType() -> HopperPluginType { + return .Plugin_Tool + } + + func toolMenuDescription() -> [[String: AnyObject]] { + return [ + [ + "Title": "Demangle Swift", + "Submenu": [ + [ + "Title": "Demangle Entire File", + "Selector": "demangleAllProcedures", + ], + [ + "Title": "Demangle Closest Name", + "Selector": "demangleClosestName", + ], + ] + ], + ] + } +} diff --git a/screenshots/example.png b/screenshots/example.png new file mode 100644 index 0000000..501dbe7 Binary files /dev/null and b/screenshots/example.png differ diff --git a/screenshots/usage.png b/screenshots/usage.png new file mode 100644 index 0000000..a6f879f Binary files /dev/null and b/screenshots/usage.png differ