Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Metal-produced universal binaries #38

Merged
merged 13 commits into from
Apr 7, 2023
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ObjectFile"
uuid = "d8793406-e978-5875-9003-1fc021f44a92"
authors = ["Elliot Saba <[email protected]>"]
version = "0.3.7"
version = "0.4.0"

[deps]
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Expand All @@ -11,7 +11,7 @@ StructIO = "53d494c1-5632-5724-8f4c-31dff12d585f"
[compat]
Reexport = "0.2, 1.0"
StructIO = "0.3"
julia = "1.0"
julia = "1.6"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
2 changes: 1 addition & 1 deletion src/Abstract/ObjectHandle.jl
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ Try to guess the path of an `IO` object. If it cannot be guessed, returns the
empty string.
"""
function path(io::IO)
if startswith(io.name, "<file ") && endswith(io.name, ">")
if hasfield(typeof(io), :name) && startswith(io.name, "<file ") && endswith(io.name, ">")
return abspath(io.name[7:end-1])
end
return ""
Expand Down
4 changes: 1 addition & 3 deletions src/Abstract/Section.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ Return the first section that matches on of the given `names`.
"""
function findfirst(sections::Sections, names::Vector{String})
results = findall(sections, names)
if isempty(results)
error("Could not find any sections that match $(names)")
end
isempty(results) && return nothing
return first(results)
end

Expand Down
49 changes: 46 additions & 3 deletions src/Abstract/Symbol.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
# Export Symbols API
export Symbols,
getindex, length, iterate, lastindex, eltype,
findall, findfirst,
handle, header

# Export SymtabEntry API
export SymtabEntry,
deref, symbol_name, symbol_value, isundef, isglobal, islocal, isweak
Expand All @@ -15,8 +16,8 @@ export SymtabEntry,
export SymbolRef,
symbol_number

# Import iteration protocol
import Base: length, iterate, lastindex
# Import Base methods for extension
import Base: length, iterate, lastindex, findall, findfirst

"""
Symbols
Expand All @@ -38,6 +39,10 @@ in emphasis:
- iterate()
- eltype()

### Search
- findall()
- findfirst()

### Misc.
- *handle()*
"""
Expand All @@ -62,6 +67,44 @@ function getindex(syms::Symbols{H}, idx) where {H <: ObjectHandle}
)
end

"""
findall(symbols::Symbols, name::String)

Return a list of symbols that match the given `name`.
"""
function findall(symbols::Symbols, name::AbstractString)
return findall(symbols, [name])
end

"""
findall(symbols::Symbols, name::String)

Return a list of symbols that match one of the given `names`.
"""
function findall(symbols::Symbols, names::Vector{S}) where {S <: AbstractString}
return [s for s in symbols if symbol_name(s) in names]
end

"""
findfirst(symbols::Symbols, name::String)

Return the first section that matches the given `name`.
"""
function findfirst(symbols::Symbols, name::AbstractString)
return findfirst(symbols, [name])
end

"""
findfirst(symbols::Symbols, names::Vector{String})

Return the first section that matches on of the given `names`.
"""
function findfirst(symbols::Symbols, names::Vector{String})
results = findall(symbols, names)
isempty(results) && return nothing
return first(results)
end




Expand Down
4 changes: 2 additions & 2 deletions src/COFF/COFFHandle.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct COFFHandle{T<:IO} <: ObjectHandle

# The parsed-out header of the COFF object
header::COFFHeader

# The location of the header (because of MZ confusion, we must store this)
header_offset::UInt32

Expand Down Expand Up @@ -63,7 +63,7 @@ function readmeta(io::IO, ::Type{H}) where {H <: COFFHandle}
opt_header = read(io, COFFOptionalHeader)

# Construct our COFFHandle, pilfering the filename from the IOStream
return COFFHandle(io, Int64(start), header, header_offset, opt_header, path(io))
return [COFFHandle(io, Int64(start), header, header_offset, opt_header, path(io))]
end


Expand Down
4 changes: 2 additions & 2 deletions src/ELF/ELFHandle.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function readmeta(io::IO, ::Type{H}) where {H <: ELFHandle}
start = position(io)

# Check for magic bytes
magic = [read(io, UInt8) for idx in 1:4]
magic = [read(io, UInt8) for idx in 1:4]
if any(magic .!= elven_magic)
msg = """
Magic Number 0x$(join(string.(magic, base=16),"")) does not match expected ELF
Expand All @@ -56,7 +56,7 @@ function readmeta(io::IO, ::Type{H}) where {H <: ELFHandle}
seek(io, start)

# Construct our ELFHandle, pilfering the filename from the IOStream
return ELFHandle(io, Int64(start), ei, header, path(io))
return [ELFHandle(io, Int64(start), ei, header, path(io))]
end


Expand Down
5 changes: 3 additions & 2 deletions src/MachO/MachO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ include("MachODynamicLink.jl")
include("MachOStrTab.jl")
include("MachOSymbol.jl")

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


end # module MachO

74 changes: 70 additions & 4 deletions src/MachO/MachOFat.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,78 @@
# Eventually, we will hopefully support multiarch MachO files
@io struct MachOFatArch
export MachOFatArch, MachOFatHeader, FatMachOHandle

abstract type MachOFatArchitecture end

@io struct MachOFatArch32 <: MachOFatArchitecture
cputype::UInt32
cpusubtype::UInt32
offset::UInt32
size::UInt32
align::UInt32
end

@io struct MachOFatArch64 <: MachOFatArchitecture
cputype::UInt64
cpusubtype::UInt64
offset::UInt64
size::UInt64
align::UInt32
reserved::UInt32
end

struct MachOFatHeader{H <: ObjectHandle} <: MachOHeader{H}
archs::Vector{MachOFatArch}
end
magic::UInt32
archs::Vector{MachOFatArchitecture}
end

function StructIO.unpack(io::IO, T::Type{<:MachOFatHeader}, endian::Symbol)
magic = read(io, UInt32)
nfats = unpack(io, UInt32, endian)
archtyp = macho_is64bit(magic) ? MachOFatArch64 : MachOFatArch32
archs = Vector{archtyp}(undef, nfats)
for i = 1:nfats
archs[i] = unpack(io, archtyp, endian)
end
T(magic, archs)
end

function show(io::IO, header::MachOFatHeader)
println(io, "MachOFatHeader Header")
println(io, " architectures: $(length(header.archs))")
end

struct FatMachOHandle{T <: IO} <: AbstractMachOHandle{T}
# Backing IO and start point within the IOStream of this MachO object
io::T
start::Int64

# The parsed-out header of the MachO object
header::MachOFatHeader

# The path of the file this was created with, if it exists
path::String
end

function readmeta(io::IO,::Type{FatMachOHandle})
start = position(io)
header_type, endianness = readmeta(io, AbstractMachOHandle)
(header_type <: MachOFatHeader) || throw(MagicMismatch("Binary is not fat"))

# Unpack the header
header = unpack(io, header_type, endianness)
return FatMachOHandle(io, start, header, path(io))
end

# Iteration
keys(h::FatMachOHandle) = 1:length(h)
iterate(h::FatMachOHandle, idx=1) = idx > length(h) ? nothing : (h[idx], idx+1)
lastindex(h::FatMachOHandle) = lastindex(h.header.archs)
length(h::FatMachOHandle) = length(h.header.archs)
eltype(::Type{S}) where {S <: FatMachOHandle} = MachOLoadCmdRef
function getindex(h::FatMachOHandle, idx)
seek(h.io, h.start + h.header.archs[idx].offset)
only(readmeta(h.io, MachOHandle))
end

function show(io::IO, oh::FatMachOHandle)
print(io, "$(format_string(typeof(oh))) Fat Handle")
end
35 changes: 20 additions & 15 deletions src/MachO/MachOHandle.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
export MachOHandle, FatMachOHandle
export MachOHandle

struct MachOHandle{T <: IO} <: ObjectHandle
abstract type AbstractMachOHandle{T <: IO} <: ObjectHandle end

struct MachOHandle{T <: IO} <: AbstractMachOHandle{T}
# Backing IO and start point within the IOStream of this MachO object
io::T
start::Int64

# The parsed-out header of the MachO object
header::MachOHeader

# The path of the file this was created with, if it exists
path::String
end

function readmeta(io::IO,::Type{MachOHandle})
function readmeta(io::IO, ::Type{AbstractMachOHandle})
start = position(io)

# Peek at the magic
magic = read(io,UInt32)
seek(io, start)
Expand All @@ -23,32 +25,35 @@ function readmeta(io::IO,::Type{MachOHandle})
header_type = macho_header_type(magic)
endianness = macho_endianness(magic)

# If it's fat, just throw MagicMismatch
if header_type <: MachOFatHeader
throw(MagicMismatch("FAT header"))
end
header_type, endianness
end

function readmeta(io::IO,::Type{MachOHandle})
start = position(io)
header_type, endianness = readmeta(io, AbstractMachOHandle)
!(header_type <: MachOFatHeader) || throw(MagicMismatch("Binary is fat"))

# Unpack the header
header = unpack(io, header_type, endianness)
return MachOHandle(io, Int64(start), header, path(io))
return [MachOHandle(io, Int64(start), header, path(io))]
end

## IOStream-like operations:
startaddr(oh::MachOHandle) = oh.start
iostream(oh::MachOHandle) = oh.io
startaddr(oh::AbstractMachOHandle) = oh.start
iostream(oh::AbstractMachOHandle) = oh.io


## Format-specific properties:
header(oh::MachOHandle) = oh.header
endianness(oh::MachOHandle) = macho_endianness(header(oh).magic)
header(oh::AbstractMachOHandle) = oh.header
endianness(oh::AbstractMachOHandle) = macho_endianness(header(oh).magic)
is64bit(oh::MachOHandle) = macho_is64bit(header(oh).magic)
isrelocatable(oh::MachOHandle) = header(oh).filetype == MH_OBJECT
isexecutable(oh::MachOHandle) = header(oh).filetype == MH_EXECUTE
islibrary(oh::MachOHandle) = header(oh).filetype == MH_DYLIB
isdynamic(oh::MachOHandle) = !isempty(findall(MachOLoadCmds(oh), [MachOLoadDylibCmd]))
mangle_section_names(oh::MachOHandle, name) = string("__", name)
mangle_symbol_name(oh::MachOHandle, name::AbstractString) = string("_", name)
format_string(::Type{H}) where {H <: MachOHandle} = "MachO"
format_string(::Type{H}) where {H <: AbstractMachOHandle} = "MachO"

# Section information
section_header_size(oh::MachOHandle) = sizeof(section_header_type(oh))
Expand Down
14 changes: 8 additions & 6 deletions src/MachO/MachOHeader.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export MachHeader, MachHeader32, MachHeader64, MachFatArch, MachFatHeader
export MachHeader, MachHeader32, MachHeader64

import Base: show

Expand Down Expand Up @@ -42,8 +42,10 @@ function macho_header_type(magic::UInt32)
return MachOHeader32{MachOHandle}
elseif magic in (MH_MAGIC_64, MH_CIGAM_64)
return MachOHeader64{MachOHandle}
elseif magic in (FAT_MAGIC, FAT_CIGAM)
elseif magic in (FAT_MAGIC, FAT_CIGAM, FAT_MAGIC_64, FAT_CIGAM_64, FAT_MAGIC_METAL, FAT_CIGAM_METAL)
return MachOFatHeader{MachOHandle}
elseif magic in (METALLIB_MAGIC,)
return MetallibHeader{MachOHandle}
else
throw(MagicMismatch("Invalid Magic ($(string(magic, base=16)))!"))
end
Expand All @@ -56,9 +58,9 @@ Given the `magic` field from a Mach-O file header, return the bitwidth of the
Mach-O header.
"""
function macho_is64bit(magic::UInt32)
if magic in (MH_MAGIC_64, MH_CIGAM_64)
if magic in (MH_MAGIC_64, MH_CIGAM_64, FAT_MAGIC_64, FAT_CIGAM_64)
return true
elseif magic in (MH_MAGIC, MH_CIGAM, FAT_MAGIC, FAT_CIGAM)
elseif magic in (MH_MAGIC, MH_CIGAM, FAT_MAGIC, FAT_CIGAM, FAT_MAGIC_METAL, FAT_CIGAM_METAL, METALLIB_MAGIC)
return false
else
throw(MagicMismatch("Invalid Magic ($(string(magic, base=16)))!"))
Expand All @@ -72,9 +74,9 @@ Given the `magic` field from a Mach-O file header, return the endianness of the
Mach-O header.
"""
function macho_endianness(magic::UInt32)
if magic in (MH_CIGAM, MH_CIGAM_64, FAT_CIGAM)
if magic in (MH_CIGAM, MH_CIGAM_64, FAT_CIGAM, FAT_CIGAM_METAL)
return :BigEndian
elseif magic in (MH_MAGIC, MH_MAGIC_64, FAT_MAGIC)
elseif magic in (MH_MAGIC, MH_MAGIC_64, FAT_MAGIC, FAT_MAGIC_METAL, METALLIB_MAGIC)
return :LittleEndian
else
throw(MagicMismatch("Invalid Magic ($(string(magic, base=16)))!"))
Expand Down
Loading