Skip to content

Commit 7b8fce7

Browse files
maleadtKeno
andcommitted
Add rudimentary support for fat MachO files
Co-authored-by: Keno Fischer <[email protected]>
1 parent 3180efb commit 7b8fce7

File tree

8 files changed

+116
-34
lines changed

8 files changed

+116
-34
lines changed

src/MachO/MachO.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ include("MachODynamicLink.jl")
3030
include("MachOStrTab.jl")
3131
include("MachOSymbol.jl")
3232

33-
# We do not yet support Fat (Universal) MachO binaries, as I have yet to come
34-
# up with a nice abstraction over them that fits in well with COFF/ELF.
33+
# These aren't complete implementations of the API
3534
include("MachOFat.jl")
3635

3736

3837
end # module MachO
38+

src/MachO/MachOFat.jl

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
1-
# Eventually, we will hopefully support multiarch MachO files
2-
@io struct MachOFatArch
1+
export MachOFatArch, MachOFatHeader, FatMachOHandle
2+
3+
abstract type MachOFatArchitecture end
4+
5+
@io struct MachOFatArch32 <: MachOFatArchitecture
36
cputype::UInt32
47
cpusubtype::UInt32
58
offset::UInt32
69
size::UInt32
710
align::UInt32
811
end
912

13+
@io struct MachOFatArch64 <: MachOFatArchitecture
14+
cputype::UInt64
15+
cpusubtype::UInt64
16+
offset::UInt64
17+
size::UInt64
18+
align::UInt32
19+
reserved::UInt32
20+
end
21+
1022
struct MachOFatHeader{H <: ObjectHandle} <: MachOHeader{H}
11-
archs::Vector{MachOFatArch}
12-
end
23+
magic::UInt32
24+
archs::Vector{MachOFatArchitecture}
25+
end
26+
27+
function StructIO.unpack(io::IO, T::Type{<:MachOFatHeader}, endian::Symbol)
28+
magic = read(io, UInt32)
29+
nfats = unpack(io, UInt32, endian)
30+
archtyp = macho_is64bit(magic) ? MachOFatArch64 : MachOFatArch32
31+
archs = Vector{archtyp}(undef, nfats)
32+
for i = 1:nfats
33+
archs[i] = unpack(io, archtyp, endian)
34+
end
35+
T(magic, archs)
36+
end
37+
38+
function show(io::IO, header::MachOFatHeader)
39+
println(io, "MachOFatHeader Header")
40+
println(io, " architectures: $(length(header.archs))")
41+
end
42+
43+
struct FatMachOHandle{T <: IO} <: AbstractMachOHandle{T}
44+
# Backing IO and start point within the IOStream of this MachO object
45+
io::T
46+
start::Int
47+
48+
# The parsed-out header of the MachO object
49+
header::MachOFatHeader
50+
51+
# The path of the file this was created with, if it exists
52+
path::String
53+
end
54+
55+
function readmeta(io::IO,::Type{FatMachOHandle})
56+
start = position(io)
57+
header_type, endianness = readmeta(io, AbstractMachOHandle)
58+
(header_type <: MachOFatHeader) || throw(MagicMismatch("Binary is not fat"))
59+
60+
# Unpack the header
61+
header = unpack(io, header_type, endianness)
62+
return FatMachOHandle(io, start, header, path(io))
63+
end
64+
65+
# Iteration
66+
keys(h::FatMachOHandle) = 1:length(h)
67+
iterate(h::FatMachOHandle, idx=1) = idx > length(h) ? nothing : (h[idx], idx+1)
68+
lastindex(h::FatMachOHandle) = lastindex(h.header.archs)
69+
length(h::FatMachOHandle) = length(h.header.archs)
70+
eltype(::Type{S}) where {S <: FatMachOHandle} = MachOLoadCmdRef
71+
function getindex(h::FatMachOHandle, idx)
72+
seek(h.io, h.start + h.header.archs[idx].offset)
73+
readmeta(h.io, MachOHandle)
74+
end
75+
76+
function show(io::IO, oh::FatMachOHandle)
77+
print(io, "$(format_string(typeof(oh))) Fat Handle")
78+
end

src/MachO/MachOHandle.jl

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
export MachOHandle, FatMachOHandle
1+
export MachOHandle
22

3-
struct MachOHandle{T <: IO} <: ObjectHandle
3+
abstract type AbstractMachOHandle{T <: IO} <: ObjectHandle end
4+
5+
struct MachOHandle{T <: IO} <: AbstractMachOHandle{T}
46
# Backing IO and start point within the IOStream of this MachO object
57
io::T
68
start::Int64
79

810
# The parsed-out header of the MachO object
911
header::MachOHeader
10-
12+
1113
# The path of the file this was created with, if it exists
1214
path::String
1315
end
1416

15-
function readmeta(io::IO,::Type{MachOHandle})
17+
function readmeta(io::IO, ::Type{AbstractMachOHandle})
1618
start = position(io)
17-
19+
1820
# Peek at the magic
1921
magic = read(io,UInt32)
2022
seek(io, start)
@@ -23,32 +25,35 @@ function readmeta(io::IO,::Type{MachOHandle})
2325
header_type = macho_header_type(magic)
2426
endianness = macho_endianness(magic)
2527

26-
# If it's fat, just throw MagicMismatch
27-
if header_type <: MachOFatHeader
28-
throw(MagicMismatch("FAT header"))
29-
end
28+
header_type, endianness
29+
end
30+
31+
function readmeta(io::IO,::Type{MachOHandle})
32+
start = position(io)
33+
header_type, endianness = readmeta(io, AbstractMachOHandle)
34+
!(header_type <: MachOFatHeader) || throw(MagicMismatch("Binary is fat"))
3035

3136
# Unpack the header
3237
header = unpack(io, header_type, endianness)
3338
return MachOHandle(io, Int64(start), header, path(io))
3439
end
3540

3641
## IOStream-like operations:
37-
startaddr(oh::MachOHandle) = oh.start
38-
iostream(oh::MachOHandle) = oh.io
42+
startaddr(oh::AbstractMachOHandle) = oh.start
43+
iostream(oh::AbstractMachOHandle) = oh.io
3944

4045

4146
## Format-specific properties:
42-
header(oh::MachOHandle) = oh.header
43-
endianness(oh::MachOHandle) = macho_endianness(header(oh).magic)
47+
header(oh::AbstractMachOHandle) = oh.header
48+
endianness(oh::AbstractMachOHandle) = macho_endianness(header(oh).magic)
4449
is64bit(oh::MachOHandle) = macho_is64bit(header(oh).magic)
4550
isrelocatable(oh::MachOHandle) = header(oh).filetype == MH_OBJECT
4651
isexecutable(oh::MachOHandle) = header(oh).filetype == MH_EXECUTE
4752
islibrary(oh::MachOHandle) = header(oh).filetype == MH_DYLIB
4853
isdynamic(oh::MachOHandle) = !isempty(findall(MachOLoadCmds(oh), [MachOLoadDylibCmd]))
4954
mangle_section_names(oh::MachOHandle, name) = string("__", name)
5055
mangle_symbol_name(oh::MachOHandle, name::AbstractString) = string("_", name)
51-
format_string(::Type{H}) where {H <: MachOHandle} = "MachO"
56+
format_string(::Type{H}) where {H <: AbstractMachOHandle} = "MachO"
5257

5358
# Section information
5459
section_header_size(oh::MachOHandle) = sizeof(section_header_type(oh))

src/MachO/MachOHeader.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export MachHeader, MachHeader32, MachHeader64, MachFatArch, MachFatHeader
1+
export MachHeader, MachHeader32, MachHeader64
22

33
import Base: show
44

@@ -42,7 +42,7 @@ function macho_header_type(magic::UInt32)
4242
return MachOHeader32{MachOHandle}
4343
elseif magic in (MH_MAGIC_64, MH_CIGAM_64)
4444
return MachOHeader64{MachOHandle}
45-
elseif magic in (FAT_MAGIC, FAT_CIGAM)
45+
elseif magic in (FAT_MAGIC, FAT_CIGAM, FAT_MAGIC_64, FAT_CIGAM_64)
4646
return MachOFatHeader{MachOHandle}
4747
else
4848
throw(MagicMismatch("Invalid Magic ($(string(magic, base=16)))!"))
@@ -56,7 +56,7 @@ Given the `magic` field from a Mach-O file header, return the bitwidth of the
5656
Mach-O header.
5757
"""
5858
function macho_is64bit(magic::UInt32)
59-
if magic in (MH_MAGIC_64, MH_CIGAM_64)
59+
if magic in (MH_MAGIC_64, MH_CIGAM_64, FAT_MAGIC_64, FAT_CIGAM_64)
6060
return true
6161
elseif magic in (MH_MAGIC, MH_CIGAM, FAT_MAGIC, FAT_CIGAM)
6262
return false

src/MachO/constants.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
const FAT_MAGIC = 0xCAFEBABE
1010
const FAT_CIGAM = bswap(FAT_MAGIC)
11+
12+
const FAT_MAGIC_64 = 0xCAFEBABF
13+
const FAT_CIGAM_64 = bswap(FAT_MAGIC_64)
1114
end
1215

1316
const CPU_ARCH_MASK = 0xff000000

src/ObjectFile.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function __init__()
2525

2626
push!(ObjTypes, ELFHandle)
2727
push!(ObjTypes, MachOHandle)
28+
push!(ObjTypes, FatMachOHandle)
2829
push!(ObjTypes, COFFHandle)
2930
end
3031

test/mac64/libfoo_fat.dylib

16.4 KB
Binary file not shown.

test/runtests.jl

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function test_libfoo_and_fooifier(fooifier_path, libfoo_path)
1616
# Actually read it in
1717
oh_exe = readmeta(open(fooifier_path, "r"))
1818
oh_lib = readmeta(open(libfoo_path, "r"))
19-
19+
2020
# Tease out some information from the containing folder name
2121
dir_path = basename(dirname(libfoo_path))
2222
types = Dict(
@@ -52,7 +52,7 @@ function test_libfoo_and_fooifier(fooifier_path, libfoo_path)
5252
@test isdynamic(oh_exe) && isdynamic(oh_lib)
5353
end
5454

55-
55+
5656
@testset "Dynamic Linking" begin
5757
# Ensure that `dir_path` is one of the RPath entries
5858
rpath = RPath(oh_exe)
@@ -98,7 +98,7 @@ function test_libfoo_and_fooifier(fooifier_path, libfoo_path)
9898
@test !isundef(syms_exe[main_idx_exe])
9999
@test !isundef(syms_lib[foo_idx_lib])
100100
end
101-
101+
102102
@test !islocal(syms_exe[foo_idx_exe])
103103
@test !islocal(syms_exe[main_idx_exe])
104104
@test !islocal(syms_lib[foo_idx_lib])
@@ -134,7 +134,7 @@ function test_libfoo_and_fooifier(fooifier_path, libfoo_path)
134134
tshow(sects)
135135
tshow(sects[1])
136136

137-
# Test showing of Segments on non-COFF
137+
# Test showing of Segments on non-COFF
138138
if !isa(oh_exe, COFFHandle)
139139
segs = Segments(oh_lib)
140140
tshow(segs)
@@ -149,26 +149,33 @@ function test_libfoo_and_fooifier(fooifier_path, libfoo_path)
149149
# Test showing of RPath and DynamicLinks
150150
rpath = RPath(oh_exe)
151151
tshow(rpath)
152-
152+
153153
dls = DynamicLinks(oh_exe)
154154
tshow(dls)
155155
tshow(dls[1])
156156
end
157157
end
158158
end
159159

160+
function test_fat_libfoo(file)
161+
oh = readmeta(open(file, "r"))
162+
@test isa(oh, FatMachOHandle)
163+
local (ntotal, n64) = (0, 0)
164+
for coh in oh
165+
ntotal += 1
166+
n64 += is64bit(coh)
167+
end
168+
@test ntotal == 2
169+
@test n64 == 1
170+
end
171+
160172
# Run ELF tests
161173
test_libfoo_and_fooifier("./linux32/fooifier", "./linux32/libfoo.so")
162174
test_libfoo_and_fooifier("./linux64/fooifier", "./linux64/libfoo.so")
163175

164176
# Run MachO tests
165177
test_libfoo_and_fooifier("./mac64/fooifier", "./mac64/libfoo.dylib")
166-
167-
# Ensure that fat Mach-O files don't look like anything to us
168-
@testset "macfat" begin
169-
@test_throws ObjectFile.MagicMismatch readmeta(open("./macfat/fooifier","r"))
170-
@test_throws ObjectFile.MagicMismatch readmeta(open("./macfat/libfoo.dylib","r"))
171-
end
178+
test_fat_libfoo("./mac64/libfoo_fat.dylib")
172179

173180
# Run COFF tests
174181
test_libfoo_and_fooifier("./win32/fooifier.exe", "./win32/libfoo.dll")

0 commit comments

Comments
 (0)