xcframework_converter 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b2d19f2b78a5ddedb9c26708a8ead42cfe3a76a21dfeeb244f6f9e49f835ef2
4
- data.tar.gz: 5334b7a2cea325d36a5e15ca30cd14aafd47c49a40f99fb0ff5e2ff45094edd9
3
+ metadata.gz: 8ed138cd025eb265a64d714f9342520dd10ca44c9d78120a20eb6695623f1e86
4
+ data.tar.gz: 870d95ae53553761641a9572a6e08f58103435f4312035749794353c2d34979c
5
5
  SHA512:
6
- metadata.gz: 519151d17e2cb3aa6c4140ee9bf4c05b55b4deb1582508271d99e6d9fb26a4f8c450b60b9f9b3c9ed590d8fd7c7b352ec1624e5a294b3a31700bf74a626c4205
7
- data.tar.gz: fc7b29d598afe23fc1d3047dcc2446bb160986da8b8297b81ef9b8103e18635104f0ea043898c4c7ff3d27b03e1a38c673ece942c8b084bbc1292d0ab9eccd40
6
+ metadata.gz: f5f9e9d85f7385b2a1f7406b5942741c7060c9263e751a9e59bdeb13c234e117c0ba1f58a3f3f2c0f509d797b76c9b51afbb12131fde7bdfefe9940a29e4ac4d
7
+ data.tar.gz: a2201235913e84433ee593288511da552b23cdf9e0e09ca0efdcc525f82ebd2e884d9d134b71f7323ed07a7b1ea88d70d49406492c51e2834afa3af02c1071e9
@@ -0,0 +1,28 @@
1
+ // swift-tools-version:5.3
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "arm64-to-sim",
6
+ platforms: [
7
+ .macOS(.v11)
8
+ ],
9
+ products: [
10
+ .executable(name: "arm64-to-sim", targets: ["arm64-to-sim"])
11
+ ],
12
+ dependencies: [
13
+ ],
14
+ targets: [
15
+ .target(
16
+ name: "arm64-to-sim",
17
+ dependencies: [ "Arm64ToSimLib" ]),
18
+ .target(
19
+ name: "Arm64ToSimLib",
20
+ dependencies: []),
21
+ .testTarget(
22
+ name: "Tests",
23
+ dependencies: ["Arm64ToSimLib"],
24
+ resources: [
25
+ .copy("TestResources"),
26
+ ])
27
+ ]
28
+ )
@@ -7,12 +7,12 @@ extension Data {
7
7
  let lc: load_command = withUnsafeBytes { $0.load(as: load_command.self) }
8
8
  return lc.cmd
9
9
  }
10
-
10
+
11
11
  var commandSize: Int {
12
12
  let lc: load_command = withUnsafeBytes { $0.load(as: load_command.self) }
13
13
  return Int(lc.cmdsize)
14
14
  }
15
-
15
+
16
16
  func asStruct<T>(fromByteOffset offset: Int = 0) -> T {
17
17
  return withUnsafeBytes { $0.load(fromByteOffset: offset, as: T.self) }
18
18
  }
@@ -35,65 +35,75 @@ extension FileHandle {
35
35
  }
36
36
  }
37
37
 
38
- enum Transmogrifier {
39
- private static func readBinary(atPath path: String) -> (Data, [Data], Data) {
38
+ public enum Transmogrifier {
39
+ private static func readBinary(atPath path: String, isDynamic: Bool = false) -> (Data, [Data], Data) {
40
40
  guard let handle = FileHandle(forReadingAtPath: path) else {
41
41
  fatalError("Cannot open a handle for the file at \(path). Aborting.")
42
42
  }
43
-
43
+
44
44
  // chop up the file into a relevant number of segments
45
45
  let headerData = try! handle.read(upToCount: MemoryLayout<mach_header_64>.stride)!
46
-
46
+
47
47
  let header: mach_header_64 = headerData.asStruct()
48
48
  if header.magic != MH_MAGIC_64 || header.cputype != CPU_TYPE_ARM64 {
49
49
  fatalError("The file is not a correct arm64 binary. Try thinning (via lipo) or unarchiving (via ar) first.")
50
50
  }
51
-
51
+
52
52
  let loadCommandsData: [Data] = (0..<header.ncmds).map { _ in
53
53
  let loadCommandPeekData = try! handle.peek(upToCount: MemoryLayout<load_command>.stride)
54
54
  return try! handle.read(upToCount: Int(loadCommandPeekData!.commandSize))!
55
55
  }
56
-
56
+
57
+ if isDynamic {
58
+ let bytesToDiscard = abs(MemoryLayout<build_version_command>.stride - MemoryLayout<version_min_command>.stride)
59
+ _ = handle.readData(ofLength: bytesToDiscard)
60
+ }
61
+
57
62
  let programData = try! handle.readToEnd()!
58
-
63
+
59
64
  try! handle.close()
60
-
65
+
61
66
  return (headerData, loadCommandsData, programData)
62
67
  }
63
-
68
+
64
69
  private static func updateSegment64(_ data: Data, _ offset: UInt32) -> Data {
65
70
  // decode both the segment_command_64 and the subsequent section_64s
66
71
  var segment: segment_command_64 = data.asStruct()
67
-
72
+
68
73
  let sections: [section_64] = (0..<Int(segment.nsects)).map { index in
69
74
  let offset = MemoryLayout<segment_command_64>.stride + index * MemoryLayout<section_64>.stride
70
75
  return data.asStruct(fromByteOffset: offset)
71
76
  }
72
-
77
+
73
78
  // shift segment information by the offset
74
79
  segment.fileoff += UInt64(offset)
75
80
  segment.filesize += UInt64(offset)
76
81
  segment.vmsize += UInt64(offset)
77
-
82
+
78
83
  let offsetSections = sections.map { section -> section_64 in
79
- var section = section
80
- if section.flags != S_ZEROFILL {
81
- section.offset += UInt32(offset)
82
- section.reloff += section.reloff > 0 ? UInt32(offset) : 0
84
+ let sectionType = section.flags & UInt32(SECTION_TYPE)
85
+ switch Int32(sectionType) {
86
+ case S_ZEROFILL, S_GB_ZEROFILL, S_THREAD_LOCAL_ZEROFILL:
87
+ return section
88
+ case _: break
83
89
  }
90
+
91
+ var section = section
92
+ section.offset += UInt32(offset)
93
+ section.reloff += section.reloff > 0 ? UInt32(offset) : 0
84
94
  return section
85
95
  }
86
-
96
+
87
97
  var datas = [Data]()
88
98
  datas.append(Data(bytes: &segment, count: MemoryLayout<segment_command_64>.stride))
89
99
  datas.append(contentsOf: offsetSections.map { section in
90
100
  var section = section
91
101
  return Data(bytes: &section, count: MemoryLayout<section_64>.stride)
92
102
  })
93
-
103
+
94
104
  return datas.merge()
95
105
  }
96
-
106
+
97
107
  private static func updateVersionMin(_ data: Data, _ offset: UInt32, minos: UInt32, sdk: UInt32) -> Data {
98
108
  var command = build_version_command(cmd: UInt32(LC_BUILD_VERSION),
99
109
  cmdsize: UInt32(MemoryLayout<build_version_command>.stride),
@@ -101,68 +111,124 @@ enum Transmogrifier {
101
111
  minos: minos << 16 | 0 << 8 | 0,
102
112
  sdk: sdk << 16 | 0 << 8 | 0,
103
113
  ntools: 0)
104
-
114
+
105
115
  return Data(bytes: &command, count: MemoryLayout<build_version_command>.stride)
106
116
  }
107
-
117
+
108
118
  private static func updateDataInCode(_ data: Data, _ offset: UInt32) -> Data {
109
119
  var command: linkedit_data_command = data.asStruct()
110
120
  command.dataoff += offset
111
121
  return Data(bytes: &command, count: data.commandSize)
112
122
  }
113
-
123
+
114
124
  private static func updateSymTab(_ data: Data, _ offset: UInt32) -> Data {
115
125
  var command: symtab_command = data.asStruct()
116
126
  command.stroff += offset
117
127
  command.symoff += offset
118
128
  return Data(bytes: &command, count: data.commandSize)
119
129
  }
120
-
121
- static func processBinary(atPath path: String, minos: UInt32 = 13, sdk: UInt32 = 13) {
122
- guard CommandLine.arguments.count > 1 else {
123
- fatalError("Please add a path to command!")
124
- }
125
- let (headerData, loadCommandsData, programData) = readBinary(atPath: path)
126
-
127
- // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
128
- // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
129
- let offset = UInt32(abs(MemoryLayout<build_version_command>.stride - MemoryLayout<version_min_command>.stride))
130
-
130
+
131
+ private static func computeLoadCommandsEditor(_ loadCommandsData: [Data], isDynamic: Bool) -> ((Data, UInt32, UInt32) -> Data) {
132
+
133
+ if isDynamic {
134
+ return updateDylibFile
135
+ }
136
+
137
+ var contains_LC_VERSION_MIN_IPHONEOS = false
138
+ var contains_LC_BUILD_VERSION = false
139
+ for lc in loadCommandsData {
140
+ let loadCommand = UInt32(lc.loadCommand)
141
+ if loadCommand == LC_VERSION_MIN_IPHONEOS {
142
+ contains_LC_VERSION_MIN_IPHONEOS = true
143
+ } else if loadCommand == LC_BUILD_VERSION {
144
+ contains_LC_BUILD_VERSION = true
145
+ }
146
+ }
147
+
148
+ if contains_LC_VERSION_MIN_IPHONEOS == contains_LC_BUILD_VERSION {
149
+ if contains_LC_BUILD_VERSION == true {
150
+ fatalError("Bad Mach-O Object file: Both LC_VERSION_MIN_IPHONEOS and LC_BUILD_VERSION are present.\nEither one of them should be present")
151
+ } else {
152
+ fatalError("Bad Mach-O Object file: does not contain LC_VERSION_MIN_IPHONEOS or LC_BUILD_VERSION.\nEither one of them should be present")
153
+ }
154
+ }
155
+
156
+ if contains_LC_VERSION_MIN_IPHONEOS {
157
+ // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
158
+ // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
159
+ return updatePreiOS12ObjectFile
160
+ } else {
161
+ return updatePostiOS12ObjectFile
162
+ }
163
+ }
164
+
165
+
166
+ static func updatePostiOS12ObjectFile(lc: Data, minos: UInt32, sdk: UInt32) -> Data {
167
+ let cmd = Int32(bitPattern: lc.loadCommand)
168
+ switch cmd {
169
+ case LC_BUILD_VERSION:
170
+ return updateVersionMin(lc, 0, minos: minos, sdk: sdk)
171
+ default:
172
+ return lc
173
+ }
174
+ }
175
+
176
+ static func updatePreiOS12ObjectFile(lc: Data, minos: UInt32, sdk: UInt32) -> Data {
177
+ // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
178
+ // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
179
+ let offset = UInt32(abs(MemoryLayout<build_version_command>.stride - MemoryLayout<version_min_command>.stride))
180
+ let cmd = Int32(bitPattern: lc.loadCommand)
181
+ switch cmd {
182
+ case LC_SEGMENT_64:
183
+ return updateSegment64(lc, offset)
184
+ case LC_VERSION_MIN_IPHONEOS:
185
+ return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
186
+ case LC_DATA_IN_CODE, LC_LINKER_OPTIMIZATION_HINT:
187
+ return updateDataInCode(lc, offset)
188
+ case LC_SYMTAB:
189
+ return updateSymTab(lc, offset)
190
+ case LC_BUILD_VERSION:
191
+ return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
192
+ default:
193
+ return lc
194
+ }
195
+ }
196
+
197
+ static func updateDylibFile(lc: Data, minos: UInt32, sdk: UInt32) -> Data {
198
+ // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
199
+ // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
200
+ let offset = UInt32(abs(MemoryLayout<build_version_command>.stride - MemoryLayout<version_min_command>.stride))
201
+ let cmd = Int32(bitPattern: lc.loadCommand)
202
+ guard cmd != LC_BUILD_VERSION else {
203
+ fatalError("This arm64 binary already contains an LC_BUILD_VERSION load command!")
204
+ }
205
+ if cmd == LC_VERSION_MIN_IPHONEOS {
206
+ return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
207
+ }
208
+ return lc
209
+ }
210
+
211
+
212
+ public static func processBinary(atPath path: String, minos: UInt32 = 13, sdk: UInt32 = 13, isDynamic: Bool = false) {
213
+ let (headerData, loadCommandsData, programData) = readBinary(atPath: path, isDynamic: isDynamic)
214
+
215
+ let editor = computeLoadCommandsEditor(loadCommandsData, isDynamic: isDynamic)
216
+
131
217
  let editedCommandsData = loadCommandsData
132
- .map { (lc) -> Data in
133
- switch Int32(lc.loadCommand) {
134
- case LC_SEGMENT_64:
135
- return updateSegment64(lc, offset)
136
- case LC_VERSION_MIN_IPHONEOS:
137
- return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
138
- case LC_DATA_IN_CODE, LC_LINKER_OPTIMIZATION_HINT:
139
- return updateDataInCode(lc, offset)
140
- case LC_SYMTAB:
141
- return updateSymTab(lc, offset)
142
- case LC_BUILD_VERSION:
143
- fatalError("This arm64 binary already contains an LC_BUILD_VERSION load command!")
144
- default:
145
- return lc
146
- }
147
- }
148
- .merge()
149
-
218
+ .map { return editor($0, minos, sdk) }
219
+ .merge()
220
+
150
221
  var header: mach_header_64 = headerData.asStruct()
151
222
  header.sizeofcmds = UInt32(editedCommandsData.count)
152
-
223
+
153
224
  // reassemble the binary
154
225
  let reworkedData = [
155
226
  Data(bytes: &header, count: MemoryLayout<mach_header_64>.stride),
156
227
  editedCommandsData,
157
228
  programData
158
229
  ].merge()
159
-
230
+
160
231
  // save back to disk
161
232
  try! reworkedData.write(to: URL(fileURLWithPath: path))
162
233
  }
163
234
  }
164
-
165
- let binaryPath = CommandLine.arguments[1]
166
- let minos = UInt32(CommandLine.arguments[2]) ?? 13
167
- let sdk = UInt32(CommandLine.arguments[3]) ?? 13
168
- Transmogrifier.processBinary(atPath: binaryPath, minos: minos, sdk: sdk)
@@ -0,0 +1,73 @@
1
+ import Foundation
2
+ import XCTest
3
+ import Arm64ToSimLib
4
+
5
+ class Arm64ToSimTestCase: XCTestCase {
6
+
7
+
8
+ var tempDir: URL!
9
+ override func setUp() {
10
+ self.tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID.init().uuidString)
11
+ try! FileManager.default.createDirectory(at: self.tempDir, withIntermediateDirectories: false, attributes: nil)
12
+ copyFixtures()
13
+ }
14
+
15
+ override func tearDown() {
16
+ try! FileManager.default.removeItem(at: self.tempDir)
17
+ }
18
+
19
+ private func copyFixtures() {
20
+ let testResourcesPath = Bundle.module.resourcePath!.appending("/TestResources")
21
+ if let files = try? FileManager.default.contentsOfDirectory(atPath: testResourcesPath){
22
+ for file in files {
23
+ var isDir : ObjCBool = false
24
+ let fileURL = URL(fileURLWithPath: testResourcesPath).appendingPathComponent(file)
25
+ if FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir) {
26
+ if !isDir.boolValue {
27
+ try! FileManager.default.copyItem(at: fileURL, to: tempDir.appendingPathComponent(fileURL.lastPathComponent.replacingOccurrences(of: ".fixture", with: "")))
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ @discardableResult func runCommand(args: [String]) -> (String, Int32) {
35
+ let task = Process()
36
+ task.executableURL = URL(fileURLWithPath: args[0])
37
+ task.arguments = Array(args.dropFirst())
38
+ task.currentDirectoryURL = tempDir
39
+ let pipe = Pipe()
40
+ task.standardOutput = pipe
41
+ task.standardError = pipe
42
+ task.launch()
43
+ task.waitUntilExit()
44
+ let data = pipe.fileHandleForReading.readDataToEndOfFile()
45
+ let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
46
+ return (output!, task.terminationStatus)
47
+ }
48
+
49
+ private func testConvert(deviceTarget: String, simulatorTarget:String, file: StaticString = #file, line: UInt = #line) {
50
+ let (sysroot, _) = runCommand(args: ["/usr/bin/xcrun", "--show-sdk-path", "--sdk", "iphonesimulator"])
51
+ runCommand(args: ["/usr/bin/clang", "-isysroot", sysroot, "-target", simulatorTarget, "-c", "main.c", "-o", "main.arm64.ios.simulator.o"])
52
+ runCommand(args: ["/usr/bin/clang", "-isysroot", sysroot, "-target", deviceTarget, "-c", "return2.c", "-o", "return2.ios.device.o"])
53
+ let (loadCommandsOutput, _) = runCommand(args: ["/usr/bin/otool", "-l", "return2.ios.device.o" ])
54
+ print("LOAD_COMMANDS:")
55
+ for lc in loadCommandsOutput.split(separator: "\n").filter({$0.contains("cmd")}) {
56
+ print(lc)
57
+ }
58
+ let (_, link_status_failing) = runCommand(args: ["/usr/bin/clang", "-isysroot", sysroot, "-target", deviceTarget, "main.arm64.ios.simulator.o", "return2.ios.device.o"])
59
+ XCTAssert(link_status_failing != 0)
60
+ Transmogrifier.processBinary(atPath: tempDir.appendingPathComponent("return2.ios.device.o").path, minos: 13, sdk: 13, isDynamic: false)
61
+ let (_, link_status_success) = runCommand(args: ["/usr/bin/clang", "-isysroot", sysroot, "-target", "arm64-apple-ios-simulator", "main.arm64.ios.simulator.o", "return2.ios.device.o"])
62
+ XCTAssert(link_status_success == 0)
63
+ }
64
+
65
+ func testConvertPreiOS12FileFormatToSim() {
66
+ testConvert(deviceTarget: "arm64-apple-ios11", simulatorTarget: "arm64-apple-ios12-simulator")
67
+ }
68
+
69
+ func testConvertNewObjectFileFormatToSim() {
70
+ testConvert(deviceTarget: "arm64-apple-ios12", simulatorTarget: "arm64-apple-ios12-simulator")
71
+ }
72
+
73
+ }
@@ -0,0 +1,16 @@
1
+ import Foundation
2
+ import Arm64ToSimLib
3
+
4
+ guard CommandLine.arguments.count > 1 else {
5
+ fatalError("Please add a path to command!")
6
+ }
7
+
8
+ let binaryPath = CommandLine.arguments[1]
9
+ let minos = (CommandLine.arguments.count > 2 ? UInt32(CommandLine.arguments[2]) : nil) ?? 12
10
+ let sdk = (CommandLine.arguments.count > 3 ? UInt32(CommandLine.arguments[3]) : nil) ?? 13
11
+ let isDynamic = (CommandLine.arguments.count > 4 ? Bool(CommandLine.arguments[4]) : nil) ?? false
12
+ if isDynamic {
13
+ print("[arm64-to-sim] notice: running in dynamic framework mode")
14
+ }
15
+
16
+ Transmogrifier.processBinary(atPath: binaryPath, minos: minos, sdk: sdk, isDynamic: isDynamic)
@@ -42,8 +42,18 @@ module XCFrameworkConverter
42
42
  extracted_path.rmtree
43
43
  end
44
44
 
45
+ def gem_path(fragment)
46
+ Pathname.new(__FILE__).dirname.join('../..').join(fragment)
47
+ end
48
+
45
49
  def arm2sim_path
46
- Pathname.new(__FILE__).dirname.join('../arm2sim.swift')
50
+ @arm2sim_path ||= begin
51
+ warn 'Pre-building `arm64-to-sim` with SwiftPM'
52
+ Dir.chdir gem_path('lib/arm64-to-sim') do
53
+ system 'xcrun swift build -c release --arch arm64 --arch x86_64'
54
+ end
55
+ gem_path('lib/arm64-to-sim/.build/apple/Products/Release/arm64-to-sim')
56
+ end
47
57
  end
48
58
 
49
59
  def patch_arm_binary_static(slice)
@@ -55,7 +65,7 @@ module XCFrameworkConverter
55
65
  Dir[extracted_path_dir.join('*.o')].each do |object_file|
56
66
  file = MachO::MachOFile.new(object_file)
57
67
  sdk_version = file[:LC_VERSION_MIN_IPHONEOS].first.version_string.to_i
58
- `xcrun swift \"#{arm2sim_path}\" \"#{object_file}\" \"#{sdk_version}\" \"#{sdk_version}\"`
68
+ `\"#{arm2sim_path}\" \"#{object_file}\" \"#{sdk_version}\" \"#{sdk_version}\"`
59
69
  $stderr.printf '.'
60
70
  end
61
71
  $stderr.puts
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XCFrameworkConverter
4
- VERSION = '0.3.3'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcframework_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Makarov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-30 00:00:00.000000000 Z
11
+ date: 2022-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocoapods
@@ -51,7 +51,10 @@ files:
51
51
  - README.md
52
52
  - bin/xcfconvert
53
53
  - bin/xcfpatch
54
- - lib/arm2sim.swift
54
+ - lib/arm64-to-sim/Package.swift
55
+ - lib/arm64-to-sim/Sources/Arm64ToSimLib/Transmogrifier.swift
56
+ - lib/arm64-to-sim/Sources/Tests/Arm64ToSimTestCase.swift
57
+ - lib/arm64-to-sim/Sources/arm64-to-sim/main.swift
55
58
  - lib/xcframework_converter.rb
56
59
  - lib/xcframework_converter/arm_patcher.rb
57
60
  - lib/xcframework_converter/creation.rb