diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b95255 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcuserdata/* +SwiftDump/SwiftDump.xcodeproj/xcuserdata/* + + diff --git a/Demo/SwiftDump b/Demo/SwiftDump new file mode 100755 index 0000000..f49312c Binary files /dev/null and b/Demo/SwiftDump differ diff --git a/Demo/result.txt b/Demo/result.txt new file mode 100644 index 0000000..2715acb --- /dev/null +++ b/Demo/result.txt @@ -0,0 +1,35 @@ +enum MyEnum { + // <0x52, enum, isUnique, version 0, kindSpecificFlags 0x0> + // Access Function at 0x21c0 + case red + case blue + case yellow +} + +struct BaseStruct { + // <0x51, struct, isUnique, version 0, kindSpecificFlags 0x0> + // Access Function at 0x25a0 + let bbname: String; +} + +struct MyStruct { + // <0x51, struct, isUnique, version 0, kindSpecificFlags 0x0> + // Access Function at 0x29b0 + let sid: Int; + let sname: String; +} + +class BaseClass { + // <0x80000050, class, isUnique, version 0, kindSpecificFlags 0x8000> + // Access Function at 0x29c0 + let bcname: String; +} + +class MyClass : BaseClass { + // <0x40000050, class, isUnique, version 0, kindSpecificFlags 0x4000> + // Access Function at 0x2a00 + let cid: Int; + let cname: String; + let st: MyStruct?; +} + diff --git a/Demo/test b/Demo/test new file mode 100755 index 0000000..1f47358 Binary files /dev/null and b/Demo/test differ diff --git a/Demo/test.swift b/Demo/test.swift new file mode 100644 index 0000000..ee06c29 --- /dev/null +++ b/Demo/test.swift @@ -0,0 +1,28 @@ +enum MyEnum { + case red + case blue + case yellow +} + +struct BaseStruct { + var bbname: String = "BaseStruct" +} + + +struct MyStruct { + var sid: Int = 123; + var sname: String = "hello" +} + + +class BaseClass { + var bcname: String = "BaseClass" +} + + +final class MyClass : BaseClass { + + var cid: Int = 456; + var cname: String = "world" + var st: MyStruct? = nil; +} diff --git a/Doc/img_demo_result.jpg b/Doc/img_demo_result.jpg new file mode 100644 index 0000000..ec028cb Binary files /dev/null and b/Doc/img_demo_result.jpg differ diff --git a/Doc/macho.jpg b/Doc/macho.jpg new file mode 100644 index 0000000..5e72df2 Binary files /dev/null and b/Doc/macho.jpg differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1158640 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2020 neilwu (https://github.com/neil-wu) + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7db712 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ + +#### SwiftDump + +##### [中文文档](./README_zh.md) + +SwiftDump is a command-line tool for retriving the Swift Object info from Mach-O file. Similar to [class-dump](https://github.com/nygard/class-dump/), but the difference is that SwiftDump focus on swift 5 objects. For Mach-O files mixed with Objective-C and swift, you can combine class-dump with SwiftDump. + +There is alos a [Frida](https://www.frida.re/) version named [FridaSwiftDump](https://github.com/neil-wu/FridaSwiftDump/). + +You can either use`SwiftDump` for a Mach-O file or `FridaSwiftDump` for a foreground running app. + +If you are curious about the Mach-O format, check the image at the bottom of this article. + +![demo](./Doc/img_demo_result.jpg) + +#### Usage + +``` Text +USAGE: SwiftDump [--debug] [--arch ] [--version] + +ARGUMENTS: + MachO File + +OPTIONS: + -d, --debug Show debug log. + -a, --arch Choose architecture from a fat binary (only support x86_64/arm64). + (default: arm64) + -v, --version Version + -h, --help Show help information. +``` + +* SwiftDump ./TestMachO > result.txt +* SwiftDump -a x86_64 ./TestMachO > result.txt + +#### Features + +* Written entirely in swift, the project is tiny +* Dump swift 5 struct/class/enum/protocol +* Parse enum with payload case +* Support inheritance and protocol +* Since it is written in swift, the mangled names are demangled by swift's runtime function, such as `swift_getTypeByMangledNameInContext` and `swift_demangle_getDemangledName`. + +Thanks to the runtime function, SwiftDump can demangle complex type, such as RxSwift variable. For example, +`RxSwift.Queue<(eventTime: Foundation.Date, event: RxSwift.Event)>` + +#### TODO + +* Parse swift function address +* More + +#### Compile + +1. Clone the repo +2. Open SwiftDump.xcodeproj with Xcode +3. Modify 'Signing & Capabilities' to use your own id +4. Build & Run + +The default Mach-O file path is `Demo/test`, you can change it in `Xcode - Product - Scheme - Edit Scheme - Arguments` + +(Tested on Xcode Version 11.5 (11E608c), MacOS 10.15.5) + +#### Credit + +* [Machismo](https://github.com/g-Off/Machismo) : Parsing of Mach-O binaries using swift. +* [swift-argument-parser](https://github.com/apple/swift-argument-parser) : Straightforward, type-safe argument parsing for Swift. +* [Swift metadata](https://knight.sc/reverse%20engineering/2019/07/17/swift-metadata.html) : High level description of all the Swift 5 sections that can show up in a Swift binary. + + +#### License + +MIT + + +#### Mach-O File Format + +The following image shows how SwiftDump parse swift types from file `Demo/test`. You can open this file with [MachOView](https://github.com/gdbinit/MachOView). + +![demo](./Doc/macho.jpg) + + diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..ca25964 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,77 @@ + +#### SwiftDump + +SwiftDump是从Mach-O文件中获取swift对象定义的命令行工具,类似大家都用过的OC类dump工具[class-dump](https://github.com/nygard/class-dump/),SwiftDump专注于处理swift对象(当前只支持swift 5)。对于采用OC/Swift混编的Mach-O文件,你可以将 class-dump 和 SwiftDump结合起来使用。 + +同时,我在[Frida](https://www.frida.re/)中实现了一个简单版本 [FridaSwiftDump](https://github.com/neil-wu/FridaSwiftDump/)。 + +你可以根据需要选择使用,`SwiftDump`可以解析处理Mach-O文件,而`FridaSwiftDump`可以对一个前台运行的app进行解析。 + +如果你对解析Mach-O的过程感兴趣,请查看该文档最后的配图。 + +![demo](./Doc/img_demo_result.jpg) + +#### 用法 + +``` Text +USAGE: SwiftDump [--debug] [--arch ] [--version] + +ARGUMENTS: + MachO File + +OPTIONS: + -d, --debug Show debug log. + -a, --arch Choose architecture from a fat binary (only support x86_64/arm64). + (default: arm64) + -v, --version Version + -h, --help Show help information. +``` + +* SwiftDump ./TestMachO > result.txt +* SwiftDump -a x86_64 ./TestMachO > result.txt + +#### 特点 + +* 完全使用swift编写,项目小巧 +* 支持 dump swift 5 的 struct/class/enum/protocol +* 支持解析 enum with payload case +* 支持解析 swift类继承 和 protocol +* 由于采用swift编写,所以借助于swift的运行时函数来还原修饰符(demangle) 比如,`swift_getTypeByMangledNameInContext` 和 `swift_demangle_getDemangledName` + +受益于swift运行时函数, SwiftDump可以还原复杂的数据类型, 比如某个使用RxSwift声明的变量类型能达到如下的解析效果: +`RxSwift.Queue<(eventTime: Foundation.Date, event: RxSwift.Event)>` + +#### TODO + +* 考虑添加导出函数地址 +* 待定 + +#### Compile + +1. Clone the repo +2. Open SwiftDump.xcodeproj with Xcode +3. Modify 'Signing & Capabilities' to use your own id +4. Build & Run + +默认输入参数使用目录`Demo/test`的Mach-O文件, 你可以在Xcode里修改输入参数: `Xcode - Product - Scheme - Edit Scheme - Arguments` + +(Xcode Version 11.5 (11E608c), MacOS 10.15.5 测试通过) + +#### 感谢 + +* [Machismo](https://github.com/g-Off/Machismo) : 使用swift来读取Mach-O文件 +* [swift-argument-parser](https://github.com/apple/swift-argument-parser) : 解析命令行参数 +* [Swift metadata](https://knight.sc/reverse%20engineering/2019/07/17/swift-metadata.html) : High level description of all the Swift 5 sections that can show up in a Swift binary. + + +#### License + +MIT + + +#### Mach-O File Format + +下图展示了 SwiftDump 是如何从测试文件 `Demo/test` 解析 swift 类型的,你可以使用 [MachOView](https://github.com/gdbinit/MachOView) 打开这个测试文件,对照下图查看。 + +![demo](./Doc/macho.jpg) + diff --git a/SwiftDump/SwiftDump.xcodeproj/project.pbxproj b/SwiftDump/SwiftDump.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0c2e0a1 --- /dev/null +++ b/SwiftDump/SwiftDump.xcodeproj/project.pbxproj @@ -0,0 +1,452 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 6119CF3124A89DCC00CA153D /* SDObjCClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119CF3024A89DCC00CA153D /* SDObjCClass.swift */; }; + 6119CF3424A99D3500CA153D /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 6119CF3324A99D3500CA153D /* ArgumentParser */; }; + 6119D4E724A9C3C300CA153D /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4D224A9C3C300CA153D /* Segment.swift */; }; + 6119D4EA24A9C3C300CA153D /* MachOFatHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4D524A9C3C300CA153D /* MachOFatHeader.swift */; }; + 6119D4EC24A9C3C300CA153D /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4D824A9C3C300CA153D /* String+Extensions.swift */; }; + 6119D4ED24A9C3C300CA153D /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4D924A9C3C300CA153D /* Data+Extensions.swift */; }; + 6119D4EE24A9C3C300CA153D /* MachOHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4DA24A9C3C300CA153D /* MachOHeader.swift */; }; + 6119D4EF24A9C3C300CA153D /* LoadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4DB24A9C3C300CA153D /* LoadCommand.swift */; }; + 6119D4F424A9C3C300CA153D /* MachOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4E224A9C3C300CA153D /* MachOParser.swift */; }; + 6119D4F524A9C3C300CA153D /* MachOFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4E324A9C3C300CA153D /* MachOFile.swift */; }; + 6119D4F724A9C5E900CA153D /* SDFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D4F624A9C5E900CA153D /* SDFileLoader.swift */; }; + 6119D50B24A9E24F00CA153D /* Section64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D50A24A9E24F00CA153D /* Section64.swift */; }; + 6119D51824AB271F00CA153D /* SDPointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6119D51724AB271F00CA153D /* SDPointer.swift */; }; + 616EC6C3249D9CE700B6D6F1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6C2249D9CE700B6D6F1 /* main.swift */; }; + 616EC6CA249D9D6000B6D6F1 /* RuntimeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6C9249D9D6000B6D6F1 /* RuntimeBridge.swift */; }; + 616EC6CC249D9DD000B6D6F1 /* Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6CB249D9DD000B6D6F1 /* Ext.swift */; }; + 616EC6CE249D9FD000B6D6F1 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6CD249D9FD000B6D6F1 /* Log.swift */; }; + 616EC6D6249DAB4400B6D6F1 /* Consts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6D5249DAB4400B6D6F1 /* Consts.swift */; }; + 616EC6D8249DABA000B6D6F1 /* SDParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6D7249DABA000B6D6F1 /* SDParser.swift */; }; + 616EC6DF249E040400B6D6F1 /* SDNominalObj.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6DE249E040400B6D6F1 /* SDNominalObj.swift */; }; + 616EC6E524A04E8600B6D6F1 /* libswiftDemangle.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 616EC6E424A04E8600B6D6F1 /* libswiftDemangle.tbd */; }; + 616EC6E724A2E57700B6D6F1 /* ContextDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6E624A2E57700B6D6F1 /* ContextDescriptor.swift */; }; + 616EC6E924A31F7900B6D6F1 /* SDProtocolObj.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616EC6E824A31F7900B6D6F1 /* SDProtocolObj.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 616EC6BD249D9CE700B6D6F1 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6119CF3024A89DCC00CA153D /* SDObjCClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDObjCClass.swift; sourceTree = ""; }; + 6119D4D224A9C3C300CA153D /* Segment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Segment.swift; sourceTree = ""; }; + 6119D4D524A9C3C300CA153D /* MachOFatHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachOFatHeader.swift; sourceTree = ""; }; + 6119D4D824A9C3C300CA153D /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + 6119D4D924A9C3C300CA153D /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; + 6119D4DA24A9C3C300CA153D /* MachOHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachOHeader.swift; sourceTree = ""; }; + 6119D4DB24A9C3C300CA153D /* LoadCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadCommand.swift; sourceTree = ""; }; + 6119D4E224A9C3C300CA153D /* MachOParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachOParser.swift; sourceTree = ""; }; + 6119D4E324A9C3C300CA153D /* MachOFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachOFile.swift; sourceTree = ""; }; + 6119D4F624A9C5E900CA153D /* SDFileLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDFileLoader.swift; sourceTree = ""; }; + 6119D50A24A9E24F00CA153D /* Section64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section64.swift; sourceTree = ""; }; + 6119D51724AB271F00CA153D /* SDPointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDPointer.swift; sourceTree = ""; }; + 616EC6BF249D9CE700B6D6F1 /* SwiftDump */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftDump; sourceTree = BUILT_PRODUCTS_DIR; }; + 616EC6C2249D9CE700B6D6F1 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 616EC6C9249D9D6000B6D6F1 /* RuntimeBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeBridge.swift; sourceTree = ""; }; + 616EC6CB249D9DD000B6D6F1 /* Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ext.swift; sourceTree = ""; }; + 616EC6CD249D9FD000B6D6F1 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + 616EC6D5249DAB4400B6D6F1 /* Consts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consts.swift; sourceTree = ""; }; + 616EC6D7249DABA000B6D6F1 /* SDParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDParser.swift; sourceTree = ""; }; + 616EC6DE249E040400B6D6F1 /* SDNominalObj.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDNominalObj.swift; sourceTree = ""; }; + 616EC6E424A04E8600B6D6F1 /* libswiftDemangle.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftDemangle.tbd; path = usr/lib/swift/libswiftDemangle.tbd; sourceTree = SDKROOT; }; + 616EC6E624A2E57700B6D6F1 /* ContextDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextDescriptor.swift; sourceTree = ""; }; + 616EC6E824A31F7900B6D6F1 /* SDProtocolObj.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDProtocolObj.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 616EC6BC249D9CE700B6D6F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6119CF3424A99D3500CA153D /* ArgumentParser in Frameworks */, + 616EC6E524A04E8600B6D6F1 /* libswiftDemangle.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6119D4CD24A9C3C300CA153D /* MachO */ = { + isa = PBXGroup; + children = ( + 6119D4D924A9C3C300CA153D /* Data+Extensions.swift */, + 6119D4CF24A9C3C300CA153D /* Load Commands */, + 6119D4DB24A9C3C300CA153D /* LoadCommand.swift */, + 6119D4D524A9C3C300CA153D /* MachOFatHeader.swift */, + 6119D4E324A9C3C300CA153D /* MachOFile.swift */, + 6119D4DA24A9C3C300CA153D /* MachOHeader.swift */, + 6119D4E224A9C3C300CA153D /* MachOParser.swift */, + 6119D4D824A9C3C300CA153D /* String+Extensions.swift */, + ); + path = MachO; + sourceTree = ""; + }; + 6119D4CF24A9C3C300CA153D /* Load Commands */ = { + isa = PBXGroup; + children = ( + 6119D4D224A9C3C300CA153D /* Segment.swift */, + 6119D50A24A9E24F00CA153D /* Section64.swift */, + ); + path = "Load Commands"; + sourceTree = ""; + }; + 616EC6B6249D9CE700B6D6F1 = { + isa = PBXGroup; + children = ( + 616EC6C1249D9CE700B6D6F1 /* SwiftDump */, + 616EC6C0249D9CE700B6D6F1 /* Products */, + 616EC6E324A04E8500B6D6F1 /* Frameworks */, + ); + sourceTree = ""; + }; + 616EC6C0249D9CE700B6D6F1 /* Products */ = { + isa = PBXGroup; + children = ( + 616EC6BF249D9CE700B6D6F1 /* SwiftDump */, + ); + name = Products; + sourceTree = ""; + }; + 616EC6C1249D9CE700B6D6F1 /* SwiftDump */ = { + isa = PBXGroup; + children = ( + 6119D4CD24A9C3C300CA153D /* MachO */, + 616EC6CF249DA08900B6D6F1 /* Util */, + 616EC6C2249D9CE700B6D6F1 /* main.swift */, + 6119D4F624A9C5E900CA153D /* SDFileLoader.swift */, + 616EC6D7249DABA000B6D6F1 /* SDParser.swift */, + 616EC6D5249DAB4400B6D6F1 /* Consts.swift */, + 616EC6DE249E040400B6D6F1 /* SDNominalObj.swift */, + 616EC6E824A31F7900B6D6F1 /* SDProtocolObj.swift */, + 6119CF3024A89DCC00CA153D /* SDObjCClass.swift */, + 6119D51724AB271F00CA153D /* SDPointer.swift */, + ); + path = SwiftDump; + sourceTree = ""; + }; + 616EC6CF249DA08900B6D6F1 /* Util */ = { + isa = PBXGroup; + children = ( + 616EC6C9249D9D6000B6D6F1 /* RuntimeBridge.swift */, + 616EC6E624A2E57700B6D6F1 /* ContextDescriptor.swift */, + 616EC6CB249D9DD000B6D6F1 /* Ext.swift */, + 616EC6CD249D9FD000B6D6F1 /* Log.swift */, + ); + path = Util; + sourceTree = ""; + }; + 616EC6E324A04E8500B6D6F1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 616EC6E424A04E8600B6D6F1 /* libswiftDemangle.tbd */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 616EC6BE249D9CE700B6D6F1 /* SwiftDump */ = { + isa = PBXNativeTarget; + buildConfigurationList = 616EC6C6249D9CE700B6D6F1 /* Build configuration list for PBXNativeTarget "SwiftDump" */; + buildPhases = ( + 616EC6BB249D9CE700B6D6F1 /* Sources */, + 616EC6BC249D9CE700B6D6F1 /* Frameworks */, + 616EC6BD249D9CE700B6D6F1 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftDump; + packageProductDependencies = ( + 6119CF3324A99D3500CA153D /* ArgumentParser */, + ); + productName = SwiftDump; + productReference = 616EC6BF249D9CE700B6D6F1 /* SwiftDump */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 616EC6B7249D9CE700B6D6F1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = nw; + TargetAttributes = { + 616EC6BE249D9CE700B6D6F1 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 616EC6BA249D9CE700B6D6F1 /* Build configuration list for PBXProject "SwiftDump" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 616EC6B6249D9CE700B6D6F1; + packageReferences = ( + 6119CF3224A99D3500CA153D /* XCRemoteSwiftPackageReference "swift-argument-parser" */, + ); + productRefGroup = 616EC6C0249D9CE700B6D6F1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 616EC6BE249D9CE700B6D6F1 /* SwiftDump */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 616EC6BB249D9CE700B6D6F1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6119D4EC24A9C3C300CA153D /* String+Extensions.swift in Sources */, + 616EC6C3249D9CE700B6D6F1 /* main.swift in Sources */, + 6119D4E724A9C3C300CA153D /* Segment.swift in Sources */, + 6119CF3124A89DCC00CA153D /* SDObjCClass.swift in Sources */, + 616EC6D6249DAB4400B6D6F1 /* Consts.swift in Sources */, + 6119D4F724A9C5E900CA153D /* SDFileLoader.swift in Sources */, + 616EC6CA249D9D6000B6D6F1 /* RuntimeBridge.swift in Sources */, + 616EC6CE249D9FD000B6D6F1 /* Log.swift in Sources */, + 6119D51824AB271F00CA153D /* SDPointer.swift in Sources */, + 616EC6E724A2E57700B6D6F1 /* ContextDescriptor.swift in Sources */, + 616EC6DF249E040400B6D6F1 /* SDNominalObj.swift in Sources */, + 6119D4EA24A9C3C300CA153D /* MachOFatHeader.swift in Sources */, + 616EC6CC249D9DD000B6D6F1 /* Ext.swift in Sources */, + 6119D4ED24A9C3C300CA153D /* Data+Extensions.swift in Sources */, + 6119D4F424A9C3C300CA153D /* MachOParser.swift in Sources */, + 6119D4EE24A9C3C300CA153D /* MachOHeader.swift in Sources */, + 616EC6D8249DABA000B6D6F1 /* SDParser.swift in Sources */, + 6119D50B24A9E24F00CA153D /* Section64.swift in Sources */, + 6119D4EF24A9C3C300CA153D /* LoadCommand.swift in Sources */, + 616EC6E924A31F7900B6D6F1 /* SDProtocolObj.swift in Sources */, + 6119D4F524A9C3C300CA153D /* MachOFile.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 616EC6C4249D9CE700B6D6F1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 616EC6C5249D9CE700B6D6F1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 616EC6C7249D9CE700B6D6F1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/SwiftDump", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/swift", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 616EC6C8249D9CE700B6D6F1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/SwiftDump", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/swift", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 616EC6BA249D9CE700B6D6F1 /* Build configuration list for PBXProject "SwiftDump" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 616EC6C4249D9CE700B6D6F1 /* Debug */, + 616EC6C5249D9CE700B6D6F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 616EC6C6249D9CE700B6D6F1 /* Build configuration list for PBXNativeTarget "SwiftDump" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 616EC6C7249D9CE700B6D6F1 /* Debug */, + 616EC6C8249D9CE700B6D6F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 6119CF3224A99D3500CA153D /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-argument-parser"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.2.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 6119CF3324A99D3500CA153D /* ArgumentParser */ = { + isa = XCSwiftPackageProductDependency; + package = 6119CF3224A99D3500CA153D /* XCRemoteSwiftPackageReference "swift-argument-parser" */; + productName = ArgumentParser; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 616EC6B7249D9CE700B6D6F1 /* Project object */; +} diff --git a/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..cd161a3 --- /dev/null +++ b/SwiftDump/SwiftDump.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "eb51f949cdd0c9d88abba9ce79d37eb7ea1231d0", + "version": "0.2.0" + } + } + ] + }, + "version": 1 +} diff --git a/SwiftDump/SwiftDump.xcodeproj/xcshareddata/xcschemes/SwiftDump.xcscheme b/SwiftDump/SwiftDump.xcodeproj/xcshareddata/xcschemes/SwiftDump.xcscheme new file mode 100644 index 0000000..224750f --- /dev/null +++ b/SwiftDump/SwiftDump.xcodeproj/xcshareddata/xcschemes/SwiftDump.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftDump/SwiftDump/Consts.swift b/SwiftDump/SwiftDump/Consts.swift new file mode 100644 index 0000000..be6a322 --- /dev/null +++ b/SwiftDump/SwiftDump/Consts.swift @@ -0,0 +1,30 @@ +// +// Consts.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + + +enum ESegment: String { + case TEXT = "__TEXT" + case DATA = "__DATA" + case DATA_CONST = "__DATA_CONST" + +} + + +enum ESection: String { + case swift5types = "__swift5_types" + case swift5proto = "__swift5_proto" + case swift5protos = "__swift5_protos" + + case swift5filemd = "__swift5_fieldmd" + case objc_classlist = "__objc_classlist" + +} + + diff --git a/SwiftDump/SwiftDump/MachO/Data+Extensions.swift b/SwiftDump/SwiftDump/MachO/Data+Extensions.swift new file mode 100644 index 0000000..8c72fb3 --- /dev/null +++ b/SwiftDump/SwiftDump/MachO/Data+Extensions.swift @@ -0,0 +1,23 @@ +// +// Data+Extensions.swift +// Machismo +// +// Created by Geoffrey Foster on 2018-05-13. +// Copyright © 2018 g-Off.net. All rights reserved. +// + +import Foundation + +extension Data { + subscript(_ arch: MachOFatArch) -> Data { + return Data(self[arch.offset..<(arch.offset + arch.size)]) + } + + func extract(_ type: T.Type, offset: Int = 0) -> T { + let data = self[offset...size]; + let ret = data.withUnsafeBytes { (ptr:UnsafeRawBufferPointer) -> T in + return ptr.load(as: T.self) + } + return ret; + } +} diff --git a/SwiftDump/SwiftDump/MachO/Load Commands/RPath.swift b/SwiftDump/SwiftDump/MachO/Load Commands/RPath.swift new file mode 100644 index 0000000..0625d5c --- /dev/null +++ b/SwiftDump/SwiftDump/MachO/Load Commands/RPath.swift @@ -0,0 +1,24 @@ +// +// RPath.swift +// Machismo +// +// Created by Geoffrey Foster on 2018-05-12. +// Copyright © 2018 g-Off.net. All rights reserved. +// + +import Foundation +import MachO + +extension LoadCommand { + public struct RPath: LoadCommandType { + let path: String + + init(loadCommand: LoadCommand) { + var command = loadCommand.data.extract(rpath_command.self, offset: loadCommand.offset) + if loadCommand.byteSwapped { + swap_rpath_command(&command, byteSwappedOrder) + } + self.path = String(data: loadCommand.data, offset: loadCommand.offset, commandSize: loadCommand.size, loadCommandString: command.path) + } + } +} diff --git a/SwiftDump/SwiftDump/MachO/Load Commands/Section64.swift b/SwiftDump/SwiftDump/MachO/Load Commands/Section64.swift new file mode 100644 index 0000000..59e8d20 --- /dev/null +++ b/SwiftDump/SwiftDump/MachO/Load Commands/Section64.swift @@ -0,0 +1,45 @@ +// +// Section64.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation +import MachO + +public struct Section64 { + private(set) var sectname: String + private(set) var segname: String + + private(set) var info:section_64 + + var align: UInt32 { + return UInt32(pow(Double(2), Double(info.align))) + } + var fileOffset: UInt32 { + return info.offset; + } + + var num:Int { + let num: Int = Int(info.size) / Int(align); + return num; + } + + + init(section: section_64) { + segname = String(section.segname); + + var strSect = String(section.sectname); + // max len is 16 + if (strSect.count > 16) { + strSect = String(strSect.prefix(16)); + } + sectname = strSect; + self.info = section; + } + + +} + diff --git a/SwiftDump/SwiftDump/MachO/Load Commands/Segment.swift b/SwiftDump/SwiftDump/MachO/Load Commands/Segment.swift new file mode 100644 index 0000000..8062c96 --- /dev/null +++ b/SwiftDump/SwiftDump/MachO/Load Commands/Segment.swift @@ -0,0 +1,79 @@ +// +// Segment.swift +// Machismo +// +// Created by Geoffrey Foster on 2018-05-06. +// Copyright © 2018 g-Off.net. All rights reserved. +// + +import Foundation +import MachO + + +extension MachOLoadCommand { + public struct Segment: MachOLoadCommandType { + + // public var cmd: UInt32 /* for 64-bit architectures */ /* LC_SEGMENT_64 */ + // public var cmdsize: UInt32 /* includes sizeof section_64 structs */ + // public var segname: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8) /* segment name */ + // public var vmaddr: UInt64 /* memory address of this segment */ + // public var vmsize: UInt64 /* memory size of this segment */ + // public var fileoff: UInt64 /* file offset of this segment */ + // public var filesize: UInt64 /* amount to map from the file */ + // public var maxprot: vm_prot_t /* maximum VM protection */ + // public var initprot: vm_prot_t /* initial VM protection */ + // public var nsects: UInt32 /* number of sections in segment */ + // public var flags: UInt32 /* flags */ + + public let name: String + + private(set) var command64: segment_command_64? = nil; // neilwu added + private(set) var command: segment_command? = nil; // + + private(set) var sections:[Section64] = [] + + init(command: segment_command_64) { + self.name = String(command.segname) + self.command64 = command + } + + init(command: segment_command) { + self.name = String(command.segname) + self.command = command; + } + + init(loadCommand: MachOLoadCommand) { + if loadCommand.command == LC_SEGMENT_64 { + var segmentCommand64:segment_command_64 = loadCommand.data.extract(segment_command_64.self, offset: loadCommand.offset) + if loadCommand.byteSwapped { + swap_segment_command_64(&segmentCommand64, byteSwappedOrder) + } + self.init(command: segmentCommand64) + + if (segmentCommand64.nsects <= 0) { + return; + } + let sectionOffset = loadCommand.offset + 0x48; // 0x48=sizeof(segment_command_64) + + for i in 0.. MachOLoadCommandType? { + switch Int(command) { + case Int(LC_SEGMENT), Int(LC_SEGMENT_64): + return Segment(loadCommand: self) + default: + return nil + } + } +} diff --git a/SwiftDump/SwiftDump/MachO/MachOFatHeader.swift b/SwiftDump/SwiftDump/MachO/MachOFatHeader.swift new file mode 100644 index 0000000..5e2c696 --- /dev/null +++ b/SwiftDump/SwiftDump/MachO/MachOFatHeader.swift @@ -0,0 +1,74 @@ +// +// FatHeader.swift +// Machismo +// +// Created by Geoffrey Foster on 2018-05-13. +// Copyright © 2018 g-Off.net. All rights reserved. +// + +import Foundation +import MachO.fat + +public struct MachOFatArch { + public var cputype: cpu_type_t /* cpu specifier (int) */ + public var cpusubtype: cpu_subtype_t /* machine specifier (int) */ + public var offset: UInt64 /* file offset to this object file */ + public var size: UInt64 /* size of this object file */ + public var align: UInt32 /* alignment as a power of 2 */ + + init(arch: fat_arch_64) { + self.cputype = arch.cputype + self.cpusubtype = arch.cpusubtype + self.offset = arch.offset + self.size = arch.size + self.align = arch.align + } + + init(arch: fat_arch) { + self.cputype = arch.cputype + self.cpusubtype = arch.cpusubtype + self.offset = UInt64(arch.offset) + self.size = UInt64(arch.size) + self.align = arch.align + } +} + +public struct MachOFatHeader { + + public let architectures: [MachOFatArch]; + + init?(data: Data) { + let magic = data.extract(UInt32.self) + guard [FAT_MAGIC, FAT_MAGIC_64, FAT_CIGAM, FAT_CIGAM_64].contains(magic) else { return nil } + + var header = data.extract(fat_header.self) + let is64Bit = [FAT_MAGIC_64, FAT_CIGAM_64].contains(magic) + let byteSwapped = [FAT_CIGAM, FAT_CIGAM_64].contains(magic) + if [FAT_CIGAM, FAT_CIGAM_64].contains(magic) { + swap_fat_header(&header, byteSwappedOrder) + } + var offset = MemoryLayout.size(ofValue: header) + var architectures: [MachOFatArch] = [] + if is64Bit { + for _ in 0.. MachAttributes { + let magic = data.extract(UInt32.self) + let is64Bit = magic == MH_MAGIC_64 || magic == MH_CIGAM_64 + let isByteSwapped = magic == MH_CIGAM || magic == MH_CIGAM_64 + return MachAttributes(is64Bit: is64Bit, isByteSwapped: isByteSwapped) + } + + private static func header(from data: Data, attributes: MachAttributes) -> MachOHeader { + if attributes.is64Bit { + let header = data.extract(mach_header_64.self) + return MachOHeader(header: header) + } else { + let header = data.extract(mach_header.self) + return MachOHeader(header: header) + } + } + + private static func segmentCommands(from data: Data, header: MachOHeader, attributes: MachAttributes) -> [MachOLoadCommandType] { + var segmentCommands: [MachOLoadCommandType] = [] + var offset = header.size + for _ in 0.. String in + return pointer.withMemoryRebound(to: UInt8.self, capacity: rawCStringSize, { + return String(cString: $0) + }) + } + self.init(string) + } + + /* + * A variable length string in a load command is represented by an lc_str + * union. The strings are stored just after the load command structure and + * the offset is from the start of the load command structure. The size + * of the string is reflected in the cmdsize field of the load command. + * Once again any padded bytes to bring the cmdsize field to a multiple + * of 4 bytes must be zero. + */ + init(data: Data, offset: Int, commandSize: Int, loadCommandString: lc_str) { + let loadCommandStringOffset = Int(loadCommandString.offset) + let stringOffset = offset + loadCommandStringOffset + let length = commandSize - loadCommandStringOffset + self = String(data: data[stringOffset..<(stringOffset + length)], encoding: .utf8)!.trimmingCharacters(in: .controlCharacters) + } + + init(loadCommand: MachOLoadCommand, string: lc_str) { + let stringOffset = loadCommand.offset + Int(string.offset) + self = String(data: loadCommand.data[stringOffset..<(loadCommand.offset + loadCommand.size)], encoding: .utf8)!.trimmingCharacters(in: .controlCharacters) + } +} diff --git a/SwiftDump/SwiftDump/SDFileLoader.swift b/SwiftDump/SwiftDump/SDFileLoader.swift new file mode 100644 index 0000000..5c51664 --- /dev/null +++ b/SwiftDump/SwiftDump/SDFileLoader.swift @@ -0,0 +1,84 @@ +// +// FileLoaderNew.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +final class SDFileLoader { + private let filePath: String; + + private(set) var machoFile: MachOFile? = nil; + + init(file: String) { + self.filePath = file; + } + + func load(cpu: MachOCpuType) -> Bool { + + Log("load file from \(self.filePath)") + let fileURL = URL(fileURLWithPath: self.filePath); + do { + let fileObj = try MachOFile(url: fileURL, cpu: cpu); + self.machoFile = fileObj; + Log("load file success") + return true; + } catch { + LogError("load fail, error \(error.localizedDescription)"); + } + + return false; + } + + func getSegment(of seg: ESegment) -> MachOLoadCommand.Segment? { + //return self.macho?.segments(withName: seg.rawValue).first?.value + guard let machoFile = self.machoFile else { + return nil; + } + for cmd in machoFile.commands { + //seg.rawValue + if let segment = cmd as? MachOLoadCommand.Segment { + if (seg.rawValue == segment.name) { + return segment; + } + } + } + return nil; + } + + func getSection(of section: ESection, seg: ESegment) -> Section64? { + guard let segObj:MachOLoadCommand.Segment = self.getSegment(of: seg) else { + return nil; + } + let ret = segObj.sections.first { (sect:Section64) -> Bool in + return sect.sectname == section.rawValue; + } + return ret; + } + + func readU32(_ archPtr: SDPointer) -> UInt32 { + return self.machoFile?.dataSlice.readU32(offset: Int(archPtr.address)) ?? 0; + } + + func readU64(_ archPtr: SDPointer) -> UInt64 { + return self.machoFile?.dataSlice.readU64(offset: Int(archPtr.address)) ?? 0; + } + + func readS32(_ archPtr: SDPointer) -> Int32 { + return self.machoFile?.dataSlice.readS32(offset: Int(archPtr.address)) ?? 0; + } + + func readMove(_ ptr: SDPointer) -> SDPointer { + let val = readU32(ptr); + return ptr.add(Int64(val)); + } + + func readStr(_ ptr: SDPointer) -> String? { + return self.machoFile?.dataSlice.readCString(from: Int(ptr.address)) + } + +} + diff --git a/SwiftDump/SwiftDump/SDNominalObj.swift b/SwiftDump/SwiftDump/SDNominalObj.swift new file mode 100644 index 0000000..c3c80bf --- /dev/null +++ b/SwiftDump/SwiftDump/SDNominalObj.swift @@ -0,0 +1,76 @@ +// +// NominalObj.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + + +final class SDNominalObjField { + var name: String = ""; + var type: String = ""; + + var namePtr: SDPointer = SDPointer(addr: 0) + var typePtr: SDPointer = SDPointer(addr: 0) +} + +final class SDNominalObj { + + var typeName: String = ""; // type name + var contextDescriptorFlag: SDContextDescriptorFlags = SDContextDescriptorFlags(0); // default + var fields: [SDNominalObjField] = []; + + var mangledTypeName: String = ""; // if someone else define this type as property, you can use this to retrive the name + var nominalOffset: Int64 = 0; // Context Descriptor offset + var accessorOffset: UInt64 = 0; // Access Function address + + var protocols:[String] = []; + var superClassName: String = ""; + + var dumpDefine: String { + let intent: String = " "; + var str: String = ""; + let kind = contextDescriptorFlag.kind; + str += "\(kind) " + typeName; + if (!superClassName.isEmpty) { + str += " : " + superClassName; + } + if (protocols.count > 0) { + let superStr: String = protocols.joined(separator: ",") + let tmp: String = superClassName.isEmpty ? " : " : ""; + str += tmp + superStr; + } + str += " {\n"; + + str += intent + "// \(contextDescriptorFlag)\n"; + if (accessorOffset > 0) { + str += intent + "// Access Function at \(accessorOffset.hex) \n"; + } + + for field in fields { + var fs: String = intent; + if kind == .Enum { + if (field.type.isEmpty) { + fs += "case \(field.name)\n"; // without payload + } else { + let tmp = field.type.hasPrefix("(") ? field.type : "(" + field.type + ")"; + fs += "case \(field.name)\(tmp)\n"; // enum with payload + } + + } else { + fs += "let \(field.name): \(field.type);\n"; + } + str += fs; + } + + str += "}\n"; + + return str; + } + +} + + diff --git a/SwiftDump/SwiftDump/SDObjCClass.swift b/SwiftDump/SwiftDump/SDObjCClass.swift new file mode 100644 index 0000000..a052872 --- /dev/null +++ b/SwiftDump/SwiftDump/SDObjCClass.swift @@ -0,0 +1,30 @@ +// +// SwiftObj.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +struct SDObjCClassROData { + var flags: UInt32 = 0; + var instanceStart: UInt32 = 0; + var instanceSize: UInt32 = 0; + var unknowField: UInt32 = 0; // + var instanceVarLayout: UInt64 = 0; + var nameAddr: UInt64 = 0; + var weakInstanceVarLayout: UInt64 = 0; +} +struct SDObjCClass { + var isaAddress: UInt64 = 0; // pointer + var superclassAddress: UInt64 = 0; // pointer + var cache: UInt64 = 0; // pointer + var mask:UInt32 = 0; + var occupied: UInt32 = 0; + var dataAddr: UInt64 = 0; +} + + + diff --git a/SwiftDump/SwiftDump/SDParser.swift b/SwiftDump/SwiftDump/SDParser.swift new file mode 100644 index 0000000..4926e3c --- /dev/null +++ b/SwiftDump/SwiftDump/SDParser.swift @@ -0,0 +1,653 @@ +// +// Parser.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +final class SDParser { + private(set) var protocolObjs:[SDProtocolObj] = []; + private(set) var cacheProtocolAddressMap:[UInt64: String] = [:]; + + private(set) var nominalObjs:[SDNominalObj] = []; + private(set) var cacheNominalOffsetMap:[Int64: String] = [:]; // used for name demangle + + private(set) var nominalProtoMap:[String: [String] ] = [:]; + private(set) var classNameInheritanceMap: [String : String] = [:]; // [className : SuperClassName] + + private var loader: SDFileLoader? = nil; + + init(with loader: SDFileLoader) { + self.loader = loader + } + + // mangledName -> TypeName + private var mangledNameMap:[String : String] = ["0x02f36d": "Int32", + "0x02cd6d": "Int16", "0x027b6e": "UInt16", + "0x022b6c": "UInt32", + "0x02b98502": "Int64", "0x02418a02" : "UInt64", + "0x02958802": "CGFloat"]; + + func parseSwiftProto() { + guard let loader = self.loader else { + return; + } + let sectionType = ESection.swift5proto; + Log("start parse section \(sectionType.rawValue)") + guard let typeSect:Section64 = loader.getSection(of: sectionType, seg: ESegment.TEXT) else { + Log("did not find section \(sectionType.rawValue)") + return; + } + + // This section contains an array of 32-bit signed integers. Each integer is a relative offset that points to a protocol conformance descriptor in the __TEXT.__const section. + if (4 != typeSect.align) { + Log("error! section \(sectionType.rawValue) is not 4 bytes align. align \(typeSect.align)") + return; + } + /* + type ProtocolConformanceDescriptor struct { + ProtocolDescriptor int32 + NominalTypeDescriptor int32 + ProtocolWitnessTable int32 + ConformanceFlags uint32 + }*/ + + let num: Int = typeSect.num; + let fileOffset: Int64 = Int64(typeSect.fileOffset); + + for i in 0.. \(protoName), nominalTypeDescriptorPtr \( nominalTypeDescriptorVal.hex ) => \(nominalName), protocolWitnessTablePtr \(protocolWitnessTablePtr.desc), conformanceFlags \(conformanceFlags.hex)"); + + Log("\(i) \(pcdPtr.desc) proto=\(protoName), nominal=\(nominalName)"); + + } + } + + func parseSwiftProtos() { + guard let loader = self.loader else { + return; + } + let sectionType = ESection.swift5protos; + Log("start parse section \(sectionType.rawValue)"); + + guard let typeSect:Section64 = loader.getSection(of: sectionType, seg: ESegment.TEXT) else { + Log("did not find section \(sectionType.rawValue)") + return; + } + // This section contains an array of 32-bit signed integers. Each integer is a relative offset that points to a protocol descriptor in the __TEXT.__const section. + if (4 != typeSect.align) { + Log("error! section \(sectionType.rawValue) is not 4 bytes align. align \(typeSect.align)") + return; + } + + let num: Int = typeSect.num; + let fileOffset: Int64 = Int64(typeSect.fileOffset); + + Log("section \(sectionType.rawValue) \(fileOffset.hex)") + /* + type ProtocolDescriptor struct { + Flags uint32 + Parent int32 + Name int32 + NumRequirementsInSignature uint32 + NumRequirements uint32 + AssociatedTypeNames int32 + [The generic requirements that form the requirement signature] + [The protocol requirements of the protocol] + }*/ + for i in 0.. 0) { + mangledNameMap[obj.mangledTypeName] = obj.typeName; + } + } + } + + private func resolveSuperClassName(_ nominalPtr: SDPointer) -> String { + //nominalPtr + let ptr = nominalPtr.add(4 * 5) + let superClassTypeVal = self.loader?.readS32(ptr) ?? 0; + if (superClassTypeVal == 0) { + return ""; + } + + var retName: String = ""; + + let superClassRefPtr = ptr.add( Int64(superClassTypeVal) ); + if let superRefStr = self.loader?.readStr(superClassRefPtr), !superRefStr.isEmpty { + if superRefStr.hasPrefix("0x") { + retName = self.mangledNameMap[superRefStr] ?? superRefStr; + } else { + retName = superRefStr; // resolve later + } + } + return retName; + } + + private func getISAClassName(of obj:SDObjCClass) -> String { + if (obj.isaAddress == 0) { + return ""; + } + + let dataSlice: Data? = self.loader?.machoFile?.dataSlice; + + guard let metaObj:SDObjCClass = dataSlice?.extract(SDObjCClass.self, offset: Int(obj.isaAddress & 0xFFFFFFFF)) else { + return ""; + } + if (metaObj.dataAddr == 0) { + return ""; + } + // find class name string + guard let dataObj:SDObjCClassROData = dataSlice?.extract(SDObjCClassROData.self, offset: Int(metaObj.dataAddr & 0xFFFFFFFF)) else { + return ""; + } + + let name: String = self.loader?.readStr(SDPointer(addr: dataObj.nameAddr & 0xFFFFFFFF)) ?? ""; + //print(" metaname:", dataObj.nameAddr.hex, name) + return name; + } + + private func getSuperClassName(of obj:SDObjCClass) -> String { + if (obj.superclassAddress == 0) { + return ""; + } + let dataSlice: Data? = self.loader?.machoFile?.dataSlice; + + guard let superClassObj:SDObjCClass = dataSlice?.extract(SDObjCClass.self, offset: Int(obj.superclassAddress & 0xFFFFFFFF)) else { + return ""; + } + + if (superClassObj.isaAddress == 0) { + // find class name string + if let dataObj:SDObjCClassROData = dataSlice?.extract(SDObjCClassROData.self, offset: Int(superClassObj.dataAddr & 0xFFFFFFFF)) { + // + let name: String = self.loader?.readStr(SDPointer(addr: dataObj.nameAddr & 0xFFFFFFFF)) ?? ""; + //print(" super dataObj:", dataObj.nameAddr.hex, name) + return name; + } + } else { + return getISAClassName(of: superClassObj); + } + + return ""; + } + private func demangleClassName(_ name: String) -> String { + var tmp: String = runtimeGetDemangledName(name); + tmp = removeSwiftModulePrefix(tmp) + return tmp; + } + + func parseSwiftOCClass() { + guard let loader = self.loader else { + return; + } + Log("start parse section \(ESection.objc_classlist.rawValue)") + let typeSect:Section64 + if let tmp:Section64 = loader.getSection(of: ESection.objc_classlist, seg: ESegment.DATA) { + typeSect = tmp; + Log("use seg \(ESegment.DATA.rawValue)"); + } else if let tmp:Section64 = loader.getSection(of: ESection.objc_classlist, seg: ESegment.DATA_CONST) { + typeSect = tmp; + Log("use seg \(ESegment.DATA_CONST.rawValue)"); + } else { + LogWarn("didn't find section \(ESection.objc_classlist.rawValue)") + return; + } + + let fileOffset: UInt64 = UInt64(typeSect.fileOffset); + + var metaClassNameMap:[UInt64: String] = [:]; // isaAddress : name + + // align is 8 + for i in 0.. 0) { + metaClassNameMap[obj.isaAddress] = metaClassName; + } else { + continue; + } + + let superClassName: String = demangleClassName(self.getSuperClassName(of: obj) ); + if (superClassName.count > 0) { + Log("\(i). \(metaClassName) : \(superClassName)") + self.classNameInheritanceMap[metaClassName] = superClassName; + } else { + Log("\(i). \(metaClassName)") + } + } + + } + + + func dumpAll() { + + for obj in self.protocolObjs { + let protoName: String = obj.name; + if let arr:[String] = self.nominalProtoMap[protoName] { + obj.superProtocols = arr; + } + print(obj.dumpDefine) + } + + for obj in nominalObjs { + + if let arr:[String] = self.nominalProtoMap[obj.typeName] { + obj.protocols = arr; + } + + var resoleSuperFromOC:Bool = obj.superClassName.isEmpty; + if (obj.superClassName.hasPrefix("0x")) { + if let tmp = self.mangledNameMap[obj.superClassName], !tmp.isEmpty { + obj.superClassName = tmp; + resoleSuperFromOC = false; + } + } else { + let tmp = runtimeGetDemangledName("$s" + obj.superClassName); + if (!tmp.hasPrefix("$s") && tmp != obj.superClassName) { + obj.superClassName = tmp; + } + } + if (resoleSuperFromOC) { + obj.superClassName = self.classNameInheritanceMap[obj.typeName] ?? obj.superClassName; + } + + + for field in obj.fields { + let ft: String = field.type; + if (ft.hasPrefix("0x")) { + if let fixName = mangledNameMap[ft] { + field.type = fixName; + } else { + // + field.type = fixMangledName(ft, startPtr: field.typePtr) + } + + } else if (ft != "String") { + let checkName: String = "$s" + ft; + let tmp: String = runtimeGetDemangledName(checkName) + if (tmp != checkName ) { + field.type = tmp; + } + } + } + print(obj.dumpDefine) + } + } + + func makeDemangledTypeName(_ type: String, header: String) -> String { + + let isArray:Bool = header.contains("Say") || header.contains("SDy"); + let suffix: String = isArray ? "G" : ""; + let fixName = "So\(type.count)\(type)C" + suffix; + return fixName; + } + + func fixMangledName(_ name: String, startPtr: SDPointer) -> String { + // symbolic-references + let hexName: String = name.removingPrefix("0x") + let dataArray: [UInt8] = hexName.hexBytes + //print(dataArray.map{ String(format: "0x%x", $0) }) + + var mangledName: String = ""; + var i: Int = 0; + + while i < dataArray.count { + let val = dataArray[i]; + if (val == 0x01) { + //find + let fromIdx:Int = i + 1; // ignore 0x01 + let toIdx:Int = i + 5; // 4 bytes + if (toIdx > dataArray.count) { + mangledName = mangledName + String(format: "%c", val); + i = i + 1; + continue; + } + let offsetArray:[UInt8] = Array(dataArray[fromIdx..= dataArray.count) { + mangledName = mangledName + result; // use original result + } else { + let fixName = makeDemangledTypeName(result, header: "") + mangledName = mangledName + fixName; + } + + i = i + 5; + } else if (val == 0x02) { + //indirectly + let fromIdx:Int = i + 1; // ignore 0x02 + let toIdx:Int = ((i + 4) > dataArray.count) ? ( i + (dataArray.count - i) ) : (i + 4); // 4 bytes + + let offsetArray:[UInt8] = Array(dataArray[fromIdx..= dataArray.count) { + mangledName = mangledName + result; + } else { + let fixName = makeDemangledTypeName(result, header: mangledName) + mangledName = mangledName + fixName + } + i = toIdx + 1; + } else { + //check next + mangledName = mangledName + String(format: "%c", val); + i = i + 1; + } + } + + let result: String = getTypeFromMangledName(mangledName) + if (result == mangledName) { + let tmp: String = runtimeGetDemangledName("$s" + mangledName) + if (tmp != ("$s" + mangledName)) { + return tmp; + } + } + return result; + } + + func resoleSymbolicRefDirectly(_ hexArray: [UInt8], ptr: SDPointer) -> String { + // {any-generic-type, protocol, opaque-type-decl-name} ::= '\x01' .{4} // Reference points directly to context descriptor + //print("resoleSymbolicRef", hexArray, ptr.desc) + let origHex: String = "0x01" + hexArray.hex; + let tmp = hexArray.reversed().hex; + + guard let address = Int64(tmp, radix: 16) else { + return origHex; + } + + let nominalArchPtr: SDPointer = ptr.add(address).fix(); + //print("ptr", tmpPtr.desc) + let nominalArchOffset: Int64 = Int64(nominalArchPtr.address); + return self.cacheNominalOffsetMap[nominalArchOffset] ?? origHex; // use hex as default value + } + + func resoleSymbolicRefIndirectly(_ hexArray: [UInt8], ptr: SDPointer) -> String { + // {any-generic-type, protocol, opaque-type-decl-name} ::= '\x02' .{4} // Reference points indirectly to context descriptor + let origHex: String = "0x02" + hexArray.hex; + let tmp = hexArray.reversed().hex; + + guard let address = Int64(tmp, radix: 16) else { + return origHex; + } + let addrPtr = ptr.add(address).fix() + + if let loader = self.loader { + // read the value from 'addrPtr' as address offset + let val:UInt32 = loader.readU32(addrPtr); + + let nominalArchOffset: Int64 = Int64(val); + return self.cacheNominalOffsetMap[nominalArchOffset] ?? origHex; + } + + return origHex; // use hex as default value + } +} + + + +func dumpStruct(loader: SDFileLoader, nominalPtr: SDPointer, fieldDescriptorPtr: SDPointer, to: SDNominalObj) { + //struct + //let numFields:UInt32 = loader.readU32(nominalPtr.add(4 * 5)); + //let fieldOffsetVectorOffset:UInt32 = loader.readU32(nominalPtr.add(4 * 6)); + //print(" numFields \(numFields), fieldOffsetVectorOffset \(fieldOffsetVectorOffset)") + dumpFieldDescriptor(loader: loader, fieldDescriptorPtr: fieldDescriptorPtr, to: to) +} + + +func dumpFieldDescriptor(loader: SDFileLoader, fieldDescriptorPtr: SDPointer, to: SDNominalObj) { + //swift5_filedmd, FieldDescriptor + + let numFields = loader.readU32(fieldDescriptorPtr.add( 4 + 4 + 2 + 2) ); + if (0 == numFields) { + return; + } + if (numFields >= 1000) { + //TODO: sometimes it may be a invalid value + Log("[dumpFieldDescriptor] \(numFields) too many fields of \(to.typeName), ignore format"); + return; + } + + let fieldStart:SDPointer = fieldDescriptorPtr.add(4 + 4 + 2 + 2 + 4); + for i in 0.. SDPointer { + var address: UInt64 = 0 + if (offset < 0) { + address = self.address - UInt64(abs(offset) ); + } else { + address = self.address + UInt64(offset); + } + return SDPointer(addr: address); + } + + func fix() -> SDPointer { + return SDPointer(addr: self.address & 0xFFFFFFFF); + } + + var desc: String { + return self.address.hex; + } +} + diff --git a/SwiftDump/SwiftDump/SDProtocolObj.swift b/SwiftDump/SwiftDump/SDProtocolObj.swift new file mode 100644 index 0000000..69fab2a --- /dev/null +++ b/SwiftDump/SwiftDump/SDProtocolObj.swift @@ -0,0 +1,36 @@ +// +// ProtocolObj.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +final class SDProtocolObj { + var flags: UInt32 = 0; + var name: String = ""; + var numRequirementsInSignature: UInt32 = 0; + var numRequirements: UInt32 = 0; + var associatedTypeNames: String = ""; // joined by " " + + var superProtocols:[String] = []; + + var dumpDefine: String { + let intent: String = " " + var str: String = "protocol \(name)"; + if (superProtocols.count > 0) { + let superStr: String = superProtocols.joined(separator: ",") + str += " : " + superStr; + } + str += " {\n"; + str += intent + "//flags \(flags.hex), numRequirements \(numRequirements)" + "\n" + for astype in associatedTypeNames.split(separator: " ") { + str += intent + "associatedtype " + String(astype) + "\n" + } + + str += "}\n" + return str; + } +} diff --git a/SwiftDump/SwiftDump/Util/ContextDescriptor.swift b/SwiftDump/SwiftDump/Util/ContextDescriptor.swift new file mode 100644 index 0000000..8768c92 --- /dev/null +++ b/SwiftDump/SwiftDump/Util/ContextDescriptor.swift @@ -0,0 +1,120 @@ +// +// File.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + + +// https://github.com/apple/swift/blob/a021e6ca020e667ce4bc8ee174e2de1cc0d9be73/include/swift/ABI/MetadataValues.h#L1183 +enum SDContextDescriptorKind: UInt8, CustomStringConvertible { + /// This context descriptor represents a module. + case Module = 0 + + /// This context descriptor represents an extension. + case Extension = 1 + + /// This context descriptor represents an anonymous possibly-generic context + /// such as a function body. + case Anonymous = 2 + + /// This context descriptor represents a protocol context. + case SwiftProtocol = 3 + + /// This context descriptor represents an opaque type alias. + case OpaqueType = 4 + + /// First kind that represents a type of any sort. + //case Type_First = 16 + + /// This context descriptor represents a class. + case Class = 16 // Type_First + + /// This context descriptor represents a struct. + case Struct = 17 // Type_First + 1 + + /// This context descriptor represents an enum. + case Enum = 18 // Type_First + 2 + + /// Last kind that represents a type of any sort. + case Type_Last = 31 + + case Unknow = 0xFF // It's not in swift source, this value only used for dump + + var description: String { + switch self { + case .Module: return "module"; + case .Extension: return "extension"; + case .Anonymous: return "anonymous"; + case .SwiftProtocol: return "protocol"; + case .OpaqueType: return "OpaqueType"; + case .Class: return "class"; + case .Struct: return "struct"; + case .Enum: return "enum"; + case .Type_Last: return "Type_Last"; + case .Unknow: return "unknow"; + } + } +} + +// https://github.com/apple/swift/blob/a021e6ca020e667ce4bc8ee174e2de1cc0d9be73/include/swift/ABI/MetadataValues.h#L1217 +struct SDContextDescriptorFlags:CustomStringConvertible { + let value: UInt32 + init(_ value: UInt32) { + self.value = value; + } + + /// The kind of context this descriptor describes. + var kind: SDContextDescriptorKind { + if let kind = SDContextDescriptorKind(rawValue: UInt8( value & 0x1F ) ) { + return kind; + } + return SDContextDescriptorKind.Unknow; + } + + /// Whether the context being described is generic. + var isGeneric: Bool { + return (value & 0x80) != 0; + } + + /// Whether this is a unique record describing the referenced context. + var isUnique: Bool { + return (value & 0x40) != 0; + } + + /// The format version of the descriptor. Higher version numbers may have + /// additional fields that aren't present in older versions. + var version: UInt8 { + return UInt8((value >> 8) & 0xFF); + } + + /// The most significant two bytes of the flags word, which can have + /// kind-specific meaning. + var kindSpecificFlags: UInt16 { + return UInt16((value >> 16) & 0xFFFF); + } + + var description: String { + let kindDesc: String = kind.description; + let kindSpecificFlagsStr: String = String(format: "0x%x", kindSpecificFlags); + + var desc: String = "<\(value.hex), \(kindDesc),"; + if isGeneric { + desc += " isGeneric," + } + if isUnique { + desc += " isUnique," + } else { + desc += " NotUnique," + } + + desc += " version \(version), kindSpecificFlags \(kindSpecificFlagsStr)>"; + return desc; + } +} + + + diff --git a/SwiftDump/SwiftDump/Util/Ext.swift b/SwiftDump/SwiftDump/Util/Ext.swift new file mode 100644 index 0000000..e609a8a --- /dev/null +++ b/SwiftDump/SwiftDump/Util/Ext.swift @@ -0,0 +1,164 @@ +// +// Ext.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +extension Array where Element == UInt8 { + var hex: String { + let tmp = self.reduce("") { (result, val:UInt8) -> String in + return result + String(format: "%02x", val); + } + return tmp; + } +} + +extension String { + func isAsciiStr() -> Bool { + return self.range(of: ".*[^A-Za-z0-9_$ ].*", options: .regularExpression) == nil; + } + + var hexData: Data { .init(hexa) } + var hexBytes: [UInt8] { .init(hexa) } + private var hexa: UnfoldSequence { + sequence(state: startIndex) { startIndex in + guard startIndex < self.endIndex else { return nil } + let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex + defer { startIndex = endIndex } + return UInt8(self[startIndex.. UnsafePointer? { + guard let data = self.data(using: String.Encoding.utf8) else { return nil } + + let buffer = UnsafeMutablePointer.allocate(capacity: data.count) + let stream = OutputStream(toBuffer: buffer, capacity: data.count) + + stream.open() + data.withUnsafeBytes { (bp:UnsafeRawBufferPointer) in + if let sp:UnsafePointer = bp.baseAddress?.bindMemory(to: UInt8.self, capacity: MemoryLayout.stride) { + stream.write(sp, maxLength: data.count) + } + } + + stream.close() + + return UnsafePointer(buffer) + } + func toCharPointer() -> UnsafePointer { + let strPtr:UnsafePointer = self.withCString { (ptr:UnsafePointer) -> UnsafePointer in + return ptr; + } + return strPtr; + } + + + public func removingPrefix(_ prefix: String) -> String { + guard hasPrefix(prefix) else { return self } + return String(dropFirst(prefix.count)) + } + public func removingSuffix(_ suffix: String) -> String { + guard hasSuffix(suffix) else { return self } + return String(dropLast(suffix.count)) + } +} + + + +extension Int64 { + var hex: String { + return String(format: "0x%llx", self); + } +} + +extension UInt64 { + var hex: String { + return String(format: "0x%llx", self); + } +} + + +extension Int { + var hex: String { + return String(format: "0x%llx", self); + } +} +extension Int32 { + var hex: String { + return String(format: "0x%llx", self); + } +} +extension UInt32 { + var hex: String { + return String(format: "0x%llx", self); + } +} + +extension Data { + + func readS8(offset: Int) -> Int8 { + return readValue(offset) ?? 0 + } + func readU8(offset: Int) -> UInt8 { + return readValue(offset) ?? 0 + } + + func readS16(offset: Int) -> Int16 { + return readValue(offset) ?? 0 + } + + func readS32(offset: Int) -> Int32 { + return readValue(offset) ?? 0 + } + + func readU32(offset: Int) -> UInt32 { + return readValue(offset) ?? 0 + } + func readU64(offset: Int) -> UInt64 { + return readValue(offset) ?? 0 + } + + + func readValue(_ offset: Int) -> Type? { + let val:Type? = self.withUnsafeBytes { (ptr:UnsafeRawBufferPointer) -> Type? in + return ptr.baseAddress?.advanced(by: offset).load(as: Type.self); + } + return val; + } + + func readCString(from: Int) -> String? { + if (from >= self.count) { + return nil; + } + var address: Int = (from); + var result:[UInt8] = []; + while true { + let val: UInt8 = self[address]; + if (val == 0) { + break; + } + address += 1; + result.append(val); + } + + if let str = String(bytes: result, encoding: String.Encoding.ascii) { + if (str.isAsciiStr()) { + return str; + } + } + + let tmp = result.reduce("0x") { (result, val:UInt8) -> String in + return result + String(format: "%02x", val); + } + return tmp; + } +} + + + + diff --git a/SwiftDump/SwiftDump/Util/Log.swift b/SwiftDump/SwiftDump/Util/Log.swift new file mode 100644 index 0000000..afa5aa6 --- /dev/null +++ b/SwiftDump/SwiftDump/Util/Log.swift @@ -0,0 +1,47 @@ +// +// Log.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +enum LogLevel: Int { + case debug = 1 + //case info = 2 + case warn = 3 + case error = 4 +} + +fileprivate var loglevel: LogLevel = .warn; + +func enableDebugLog() { + loglevel = .debug; +} + +func Log(_ msg: String, level:LogLevel = .debug, file: String = #file, method: String = #function, line: Int = #line) { + + if (level.rawValue < loglevel.rawValue ) { + return; + } + + var filename = file.components(separatedBy: "/").last ?? "unknowfile" + filename = filename.components(separatedBy: ".").first ?? filename + let methodPrefix: String = method.components(separatedBy: "(").first ?? method; + + let str: String = "[\(filename) \(methodPrefix)] \(msg)"; + print(str) +} + +func LogError(_ msg: String, file: String = #file, method: String = #function, line: Int = #line) { + let prefix: String = "[error]" + Log(prefix + msg, level: .error, file: file, method: method, line: line) +} + +func LogWarn(_ msg: String, file: String = #file, method: String = #function, line: Int = #line) { + let prefix: String = "[warn]" + Log(prefix + msg, level: .warn, file: file, method: method, line: line) +} + diff --git a/SwiftDump/SwiftDump/Util/RuntimeBridge.swift b/SwiftDump/SwiftDump/Util/RuntimeBridge.swift new file mode 100644 index 0000000..582ac03 --- /dev/null +++ b/SwiftDump/SwiftDump/Util/RuntimeBridge.swift @@ -0,0 +1,107 @@ +// +// RuntimeBridge.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation + +@_silgen_name("swift_getTypeByMangledNameInContext") +public func _getTypeByMangledNameInContext(_ name: UnsafePointer, + _ nameLength: Int, + genericContext: UnsafeRawPointer?, + genericArguments: UnsafeRawPointer?) -> Any.Type? + + +// size_t swift_demangle_getDemangledName(const char *MangledName, char *OutputBuffer,size_t Length) +@_silgen_name("swift_demangle_getDemangledName") +public func _getDemangledName(_ name:UnsafePointer?, output:UnsafeMutablePointer?, len:Int) -> Int; + + + +// ex. So8UIButtonCSg -> UIButton? +// if demangle fail, will return the origin string +// Only demangle str start with So/$So/_$so/_T +func canDemangleFromRuntime(_ instr: String) -> Bool { + return instr.hasPrefix("So") || instr.hasPrefix("$So") || instr.hasPrefix("_$So") || instr.hasPrefix("_T") +} +func runtimeGetDemangledName(_ instr: String) -> String { + var str: String = instr; + if (instr.hasPrefix("$s")) { + str = instr; + } else if (instr.hasPrefix("So")) { + str = "$s" + instr; + } else if (instr.hasPrefix("_T")) { + // + } else { + return instr; + } + + let strPtr:UnsafePointer = str.withCString { (ptr:UnsafePointer) -> UnsafePointer in + return ptr; + } + + let bufLen: Int = 128; // may be 128 is big enough + var buf:[Int8] = Array(repeating: 0, count: bufLen); + let retLen = _getDemangledName(strPtr, output: &buf, len: bufLen) + + if retLen > 0 && retLen < bufLen { + let resultBuf:[UInt8] = buf[0.. String { + if (canDemangleFromRuntime(str)) { + return runtimeGetDemangledName(str); + } + //check is ascii string + if (!str.isAsciiStr()) { + return str; + } + guard let ptr = str.toPointer() else { + return str; + } + guard let typeRet: Any.Type = _getTypeByMangledNameInContext(ptr, str.count, genericContext: nil, genericArguments: nil) else { + return str; + } + + let tstr: String = String(describing: typeRet) + //print("\(str) -> \(tstr)") + return fixOptionalTypeName(tstr); +} + +// Optional => Any.Type? +// Optional => Int? +func fixOptionalTypeName(_ typeName: String) -> String { + let prefix: String = "Optional"; + if (!typeName.hasPrefix(prefix)) { + return typeName; + } + var name: String = typeName.removingPrefix(prefix); + name = name.removingPrefix("<") + name = name.removingSuffix(">") + if (name.contains(" ")) { + return "(" + name + ")?" + } + return name + "?"; +} + +func removeSwiftModulePrefix(_ typeName: String) -> String { + if let idx = typeName.firstIndex(of: ".") { + let useIdx = typeName.index(after: idx) + return String(typeName.suffix(from: useIdx )); + } + + return typeName; +} + + + diff --git a/SwiftDump/SwiftDump/main.swift b/SwiftDump/SwiftDump/main.swift new file mode 100644 index 0000000..b7b92f7 --- /dev/null +++ b/SwiftDump/SwiftDump/main.swift @@ -0,0 +1,89 @@ +// +// main.swift +// SwiftDump +// +// Created by neilwu on 2020/6/26. +// Copyright © 2020 nw. All rights reserved. +// + +import Foundation +import ArgumentParser + +// https://github.com/apple/swift-argument-parser +// https://github.com/apple/swift/blob/master/docs/ABI/Mangling.rst#symbolic-references + +func runMain(file: String, cpu: MachOCpuType) { + let loader = SDFileLoader(file: file); + let isSuccess: Bool = loader.load(cpu: cpu); + if (!isSuccess) { + LogError("fail to load file") + return; + } + let parser = SDParser(with: loader); + parser.parseSwiftProtos(); // find all Protocol + parser.parseSwiftType(); // find all Type + parser.parseSwiftProto(); // parse after Protocol & Type + parser.parseSwiftOCClass(); + + parser.dumpAll(); +} + +fileprivate let SDVersion: String = "1.0"; +fileprivate let SDBuildTime: String = "2020-06-26"; + +struct SwiftDump: ParsableCommand { + + @Flag(name: .shortAndLong, help: "Show debug log.") + var debug: Bool = false; + + @Option(name: .shortAndLong, help: "Choose architecture from a fat binary (only support x86_64/arm64).") + var arch: String = "arm64" + + @Flag(name: .shortAndLong, help: "Version") + var version: Bool = false; + + @Argument(help: "MachO File") + var file: String = ""; + + + mutating func run() throws { + if (version) { + print("SwiftDump v\(SDVersion) \(SDBuildTime). Created by neilwu."); + if (file.isEmpty) { + return; + } + print("\n"); + } + + let isFileExist = FileManager.default.fileExists(atPath: file) + if (!isFileExist) { + print("input file [\(file)] does not exist") + return; + } + //print("[run] debug ", debug) + if (debug) { + enableDebugLog(); + } + #if DEBUG + //enableDebugLog(); + #endif + guard let archVal = self.getArchVal() else { + print("Fail to find architecture [\(self.arch)] in [\(file)], SwiftDump only support x86_64/arm64"); + return; + } + + runMain(file: file, cpu: archVal); + } + + func getArchVal() -> MachOCpuType? { + if let val = MachOCpuType(rawValue: self.arch) { + return val; + } + return nil; + } + + +} + +SwiftDump.main() +