Skip to content

Commit 6debf60

Browse files
authored
Merge pull request #177 from p-x9/feature/cfstring
Support table of `CFString`
2 parents 853df00 + 1c1f74b commit 6debf60

File tree

9 files changed

+306
-15
lines changed

9 files changed

+306
-15
lines changed

Sources/MachOKit/DyldCacheLoaded.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ extension DyldCacheLoaded {
350350
return nil
351351
}
352352

353-
let text: any SegmentCommandProtocol
354353
let __objc_opt_ro: any SectionProtocol
355354

356355
if libobjc.is64Bit {
@@ -360,7 +359,6 @@ extension DyldCacheLoaded {
360359
}) else {
361360
return nil
362361
}
363-
text = _text
364362
__objc_opt_ro = section
365363
} else {
366364
guard let _text = libobjc.loadCommands.text,
@@ -369,16 +367,16 @@ extension DyldCacheLoaded {
369367
}) else {
370368
return nil
371369
}
372-
text = _text
373370
__objc_opt_ro = section
374371
}
375372

376373
guard let start = __objc_opt_ro.startPtr(
377-
in: text,
378374
vmaddrSlide: vmaddrSlide
379375
) else { return nil }
376+
380377
let layout: OldObjCOptimization.Layout = start
381378
.autoBoundPointee()
379+
382380
return .init(
383381
layout: layout,
384382
offset: Int(bitPattern: start) - Int(bitPattern: ptr)

Sources/MachOKit/LoadCommand/Model/Section.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,27 @@ public protocol SectionProtocol: LayoutWrapper {
3434

3535
/// Get the pointer where this section starts
3636
/// - Parameters:
37-
/// - segment: Segment to which this section belongs
3837
/// - vmaddrSlide: slide
3938
/// - Returns: Pointer where this section starts
40-
func startPtr(
41-
in segment: any SegmentCommandProtocol,
42-
vmaddrSlide: Int
43-
) -> UnsafeRawPointer?
39+
func startPtr(vmaddrSlide: Int) -> UnsafeRawPointer?
4440

41+
/// Get the data in this section as a string table
42+
///
4543
/// returns nil except when type is `cstring_literals
44+
///
45+
/// - Parameters:
46+
/// - vmaddrSlide: slide
47+
/// - Returns: string table
4648
func strings(
47-
in segment: any SegmentCommandProtocol,
4849
vmaddrSlide: Int
4950
) -> MachOImage.Strings?
5051

52+
/// Get the data in this section as a string table
53+
///
5154
/// returns nil except when type is `cstring_literals
55+
///
56+
/// - Parameter machO: MachOFile to which `self` belongs
57+
/// - Returns: string table
5258
func strings(in machO: MachOFile) -> MachOFile.Strings?
5359

5460
/// relocation informations.
@@ -62,11 +68,22 @@ public protocol SectionProtocol: LayoutWrapper {
6268
}
6369

6470
extension SectionProtocol {
71+
/// Get the pointer where this section starts
72+
/// - Parameters:
73+
/// - segment: Segment to which this section belongs
74+
/// - vmaddrSlide: slide
75+
/// - Returns: Pointer where this section starts
76+
@available(*, deprecated, renamed: "startPtr(vmaddrSlide:)", message: "No need to provide segment.")
6577
public func startPtr(in segment: any SegmentCommandProtocol, vmaddrSlide: Int) -> UnsafeRawPointer? {
6678
segment.startPtr(vmaddrSlide: vmaddrSlide)?
6779
.advanced(by: -segment.fileOffset)
6880
.advanced(by: offset)
6981
}
82+
83+
public func startPtr(vmaddrSlide: Int) -> UnsafeRawPointer? {
84+
let address = vmaddrSlide + address
85+
return UnsafeRawPointer(bitPattern: address)
86+
}
7087
}
7188

7289
public struct Section: SectionProtocol {
@@ -194,13 +211,27 @@ extension Section64 {
194211
}
195212

196213
extension SectionProtocol {
214+
/// Get the data in this section as a string table
215+
///
216+
/// returns nil except when type is `cstring_literals
217+
///
218+
/// - Parameters:
219+
/// - segment: sgment to which this section belongs
220+
/// - vmaddrSlide: slide
221+
/// - Returns: string table
222+
@available(*, deprecated, renamed: "strings(vmaddrSlide:)", message: "No need to provide segment")
197223
public func strings(
198224
in segment: any SegmentCommandProtocol,
199225
vmaddrSlide: Int
226+
) -> MachOImage.Strings? {
227+
strings(vmaddrSlide: vmaddrSlide)
228+
}
229+
230+
public func strings(
231+
vmaddrSlide: Int
200232
) -> MachOImage.Strings? {
201233
guard flags.type == .cstring_literals else { return nil }
202234
guard let basePointer = startPtr(
203-
in: segment,
204235
vmaddrSlide: vmaddrSlide
205236
) else {
206237
return nil

Sources/MachOKit/MachOFile.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,36 @@ extension MachOFile {
483483
}
484484
}
485485

486+
extension MachOFile {
487+
public var cfStrings64: DataSequence<CFString64>? {
488+
guard let section = sections64.first(where: {
489+
$0.sectionName == "__cfstring"
490+
}) else { return nil }
491+
492+
let offset = headerStartOffset + section.offset
493+
let count = section.size / CFString64.layoutSize
494+
495+
return fileHandle.readDataSequence(
496+
offset: numericCast(offset),
497+
numberOfElements: count
498+
)
499+
}
500+
501+
public var cfStrings32: DataSequence<CFString32>? {
502+
guard let section = sections32.first(where: {
503+
$0.sectionName == "__cfstring"
504+
}) else { return nil }
505+
506+
let offset = headerStartOffset + section.offset
507+
let count = section.size / CFString32.layoutSize
508+
509+
return fileHandle.readDataSequence(
510+
offset: numericCast(offset),
511+
numberOfElements: count
512+
)
513+
}
514+
}
515+
486516
extension MachOFile {
487517
// https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/common/MetadataVisitor.cpp#L262
488518
public func resolveRebase(at offset: UInt64) -> UInt64? {

Sources/MachOKit/MachOImage.swift

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,13 +283,13 @@ extension MachOImage {
283283
$0.sectionName == "__cstring"
284284
}
285285
guard let cstrings else { return nil }
286-
return cstrings.strings(in: text, vmaddrSlide: vmaddrSlide)
286+
return cstrings.strings(vmaddrSlide: vmaddrSlide)
287287
} else if let text = loadCommands.text {
288288
let cstrings = text.sections(cmdsStart: cmdsStartPtr).first {
289289
$0.sectionName == "__cstring"
290290
}
291291
guard let cstrings else { return nil }
292-
return cstrings.strings(in: text, vmaddrSlide: vmaddrSlide)
292+
return cstrings.strings(vmaddrSlide: vmaddrSlide)
293293
}
294294
return nil
295295
}
@@ -301,15 +301,15 @@ extension MachOImage {
301301
return segments.flatMap { segment in
302302
segment.sections(cmdsStart: cmdsStartPtr)
303303
.compactMap { section in
304-
section.strings(in: segment, vmaddrSlide: vmaddrSlide)
304+
section.strings(vmaddrSlide: vmaddrSlide)
305305
}
306306
}
307307
} else {
308308
let segments = loadCommands.infos(of: LoadCommand.segment)
309309
return segments.flatMap { segment in
310310
segment.sections(cmdsStart: cmdsStartPtr)
311311
.compactMap { section in
312-
section.strings(in: segment, vmaddrSlide: vmaddrSlide)
312+
section.strings(vmaddrSlide: vmaddrSlide)
313313
}
314314
}
315315
}
@@ -698,3 +698,41 @@ extension MachOImage {
698698
return nil
699699
}
700700
}
701+
702+
extension MachOImage {
703+
public var cfStrings64: MemorySequence<CFString64>? {
704+
guard let section = sections64.first(where: {
705+
$0.sectionName == "__cfstring"
706+
}) else { return nil }
707+
guard let vmaddrSlide else { return nil }
708+
709+
guard let ptr = section.startPtr(vmaddrSlide: vmaddrSlide) else {
710+
return nil
711+
}
712+
let count = section.size / CFString64.layoutSize
713+
714+
return .init(
715+
basePointer: ptr
716+
.assumingMemoryBound(to: CFString64.self),
717+
numberOfElements: count
718+
)
719+
}
720+
721+
public var cfStrings32: MemorySequence<CFString32>? {
722+
guard let section = sections32.first(where: {
723+
$0.sectionName == "__cfstring"
724+
}) else { return nil }
725+
guard let vmaddrSlide else { return nil }
726+
727+
guard let ptr = section.startPtr(vmaddrSlide: vmaddrSlide) else {
728+
return nil
729+
}
730+
let count = section.size / CFString32.layoutSize
731+
732+
return .init(
733+
basePointer: ptr
734+
.assumingMemoryBound(to: CFString32.self),
735+
numberOfElements: count
736+
)
737+
}
738+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// CFString.swift
3+
// MachOKit
4+
//
5+
// Created by p-x9 on 2025/02/01
6+
//
7+
//
8+
9+
import Foundation
10+
import MachOKitC
11+
12+
public protocol CFStringProtocol {
13+
/// Offset at which string data is stored
14+
var stringOffset: Int { get }
15+
/// Number (in terms of UTF-16 code pairs) of Unicode characters in a string.
16+
var stringSize: Int { get }
17+
18+
/// A Boolean value that indicates whether the String data is stored in unicode or not.
19+
var isUnicode: Bool { get }
20+
/// A Boolean value that indicates whether the String data is stored in 8-bit or not.
21+
var isEightBit: Bool { get }
22+
23+
/// Obtain a stored string as a `Swift.String`
24+
/// - Parameter machO: MachOFile to which `self` belongs
25+
/// - Returns: stored string
26+
func string(in machO: MachOFile) -> String?
27+
28+
/// Obtain a stored string as a `Swift.String`
29+
/// - Parameter machO: MachOImage to which `self` belongs
30+
/// - Returns: stored string
31+
func string(in machO: MachOImage) -> String?
32+
}
33+
34+
public struct CFString64: LayoutWrapper, CFStringProtocol {
35+
public typealias Layout = CF_CONST_STRING64
36+
37+
public var layout: Layout
38+
}
39+
40+
public struct CFString32: LayoutWrapper, CFStringProtocol {
41+
public typealias Layout = CF_CONST_STRING32
42+
43+
public var layout: Layout
44+
}
45+
46+
extension CFString64 {
47+
public var stringOffset: Int {
48+
numericCast(layout._ptr & 0x7ffffffff)
49+
}
50+
51+
public var stringSize: Int {
52+
numericCast(layout._length)
53+
}
54+
55+
// ref: https://github.com/apple-oss-distributions/CF/blob/dc54c6bb1c1e5e0b9486c1d26dd5bef110b20bf3/CFString.c#L208C1-L212C3
56+
/* !!! Note: Constant CFStrings use the bit patterns:
57+
C8 (11001000 = default allocator, not inline, not freed contents; 8-bit; has NULL byte; doesn't have length; is immutable)
58+
D0 (11010000 = default allocator, not inline, not freed contents; Unicode; is immutable)
59+
The bit usages should not be modified in a way that would effect these bit patterns.
60+
*/
61+
public var isUnicode: Bool {
62+
layout._base._cfinfo.0 == 0xD0 // FIXME: consider byte swapped environment (CF_INFO_BITS)
63+
}
64+
65+
public var isEightBit: Bool {
66+
layout._base._cfinfo.0 == 0xC8
67+
}
68+
}
69+
70+
extension CFString32 {
71+
public var stringOffset: Int {
72+
numericCast(layout._ptr)
73+
}
74+
75+
public var stringSize: Int {
76+
numericCast(layout._length)
77+
}
78+
79+
public var isUnicode: Bool {
80+
layout._base._cfinfo.0 == 0xD0
81+
}
82+
83+
public var isEightBit: Bool {
84+
layout._base._cfinfo.0 == 0xC8
85+
}
86+
}
87+
88+
extension CFStringProtocol {
89+
public func string(in machO: MachOFile) -> String? {
90+
let offset = machO.headerStartOffset + stringOffset
91+
if isUnicode {
92+
let data = machO.fileHandle.readData(
93+
offset: UInt64(offset),
94+
size: numericCast(stringSize) * numericCast(MemoryLayout<UInt16/*UniChar*/>.size)
95+
)
96+
return String(bytes: data, encoding: .utf16LittleEndian)
97+
} else {
98+
return machO.fileHandle.readString(
99+
offset: UInt64(offset),
100+
size: stringSize
101+
)
102+
}
103+
}
104+
105+
public func string(in machO: MachOImage) -> String? {
106+
guard let ptr = UnsafeRawPointer(bitPattern: UInt(stringOffset)) else {
107+
return nil
108+
}
109+
110+
if isUnicode {
111+
let data = Data(bytes: ptr, count: numericCast(stringSize) * numericCast(MemoryLayout<UInt16/*UniChar*/>.size))
112+
return String(bytes: data, encoding: .utf16LittleEndian)
113+
} else {
114+
return .init(
115+
cString: ptr.assumingMemoryBound(to: CChar.self),
116+
encoding: .ascii
117+
)
118+
}
119+
}
120+
}

Sources/MachOKit/Protocol/MachORepresentable.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public protocol MachORepresentable {
1414
associatedtype Symbols64: RandomAccessCollection<Symbol>
1515
associatedtype Symbols: RandomAccessCollection<Symbol>
1616
associatedtype IndirectSymbols: RandomAccessCollection<IndirectSymbol>
17+
associatedtype CFStrings32: RandomAccessCollection<CFString32>
18+
associatedtype CFStrings64: RandomAccessCollection<CFString64>
1719
associatedtype RebaseOperations: Sequence<RebaseOperation>
1820
associatedtype BindOperations: Sequence<BindOperation>
1921
associatedtype ExportTrie: Sequence<ExportTrieEntry>
@@ -82,6 +84,13 @@ public protocol MachORepresentable {
8284
/// Symbol strings is not included.
8385
var allCStrings: [String] { get }
8486

87+
/// List of CFStrings in all segments
88+
var cfStrings: [any CFStringProtocol]? { get }
89+
/// List of CFStrings in 64-bit architecture segments
90+
var cfStrings64: CFStrings64? { get }
91+
/// List of CFStrings in 32-bit architecture segments
92+
var cfStrings32: CFStrings32? { get }
93+
8594
/// Sequence of rebase operations
8695
var rebaseOperations: RebaseOperations? { get }
8796

@@ -279,6 +288,16 @@ extension MachORepresentable {
279288
}
280289
}
281290

291+
extension MachORepresentable {
292+
public var cfStrings: [any CFStringProtocol]? {
293+
if is64Bit {
294+
cfStrings64?.map { $0 as (any CFStringProtocol) }
295+
} else {
296+
cfStrings32?.map { $0 as (any CFStringProtocol) }
297+
}
298+
}
299+
}
300+
282301
extension MachORepresentable {
283302
public var bindingSymbols: [BindingSymbol] {
284303
guard let bindOperations else {

0 commit comments

Comments
 (0)