Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 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 <staticfloat@gmail.com>"]
version = "0.6.0"
version = "0.6.1"

[deps]
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Expand Down
156 changes: 156 additions & 0 deletions src/MachO/LoadCmds/VersionCmds.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
export MachOVersionMinCmd, MachOBuildVersionCmd

export min_os_version, sdk_version, build_platform

"""
macho_version(v::UInt32)

Decode a Mach-O packed version number, stored as `xxxx.yy.zz` in nibbles
(16 bits of major version, 8 bits each of minor and patch version), into a
`VersionNumber`.
"""
macho_version(v::UInt32) = VersionNumber(v >> 16, (v >> 8) & 0xff, v & 0xff)

@io struct MachOVersionMinStub
version::UInt32
sdk::UInt32
end

"""
MachOVersionMinCmd

The Load Command that records the minimum OS version and the SDK version an
object was built for, used by the `LC_VERSION_MIN_MACOSX`,
`LC_VERSION_MIN_IPHONEOS`, `LC_VERSION_MIN_TVOS` and `LC_VERSION_MIN_WATCHOS`
load commands. Newer toolchains emit `MachOBuildVersionCmd` instead.

### Accessors:
- min_os_version()
- sdk_version()
- build_platform()
"""
struct MachOVersionMinCmd{H <: MachOHandle} <: MachOLoadCmd{H}
cmd::UInt32
version::UInt32
sdk::UInt32
end

function MachOLoadCmd(oh::H, CT::Type{CTT}, header) where {CTT <: MachOVersionMinCmd} where {H <: MachOHandle}
stub = unpack(oh, MachOVersionMinStub)
return CT(header.cmd & ~LC_REQ_DYLD, stub.version, stub.sdk)
end

show(io::IO, lc::MachOVersionMinCmd) = write(io, "VersionMinCmd: $(PLATFORMS[build_platform(lc)]) $(min_os_version(lc)) (SDK $(sdk_version(lc)))")

"""
MachOBuildVersionCmd

The Load Command (`LC_BUILD_VERSION`) that records the platform, minimum OS
version and SDK version an object was built for. The list of build tool
versions that follows the fixed-size part of the command is not read.

### Accessors:
- min_os_version()
- sdk_version()
- build_platform()
"""
@io struct MachOBuildVersionCmd{H <: MachOHandle} <: MachOLoadCmd{H}
platform::UInt32
minos::UInt32
sdk::UInt32
ntools::UInt32
end

function show(io::IO, lc::MachOBuildVersionCmd)
platform = get(PLATFORMS, build_platform(lc), "Unknown Platform ($(build_platform(lc)))")
write(io, "BuildVersionCmd: $(platform) $(min_os_version(lc)) (SDK $(sdk_version(lc)))")
end

const VersionCmd = Union{MachOVersionMinCmd, MachOBuildVersionCmd}

"""
min_os_version(cmd::Union{MachOVersionMinCmd, MachOBuildVersionCmd})

Return the minimum OS version recorded in the given Load Command, as a
`VersionNumber`.
"""
min_os_version(cmd::MachOVersionMinCmd) = macho_version(cmd.version)
min_os_version(cmd::MachOBuildVersionCmd) = macho_version(cmd.minos)

"""
sdk_version(cmd::Union{MachOVersionMinCmd, MachOBuildVersionCmd})

Return the SDK version recorded in the given Load Command, as a
`VersionNumber`.
"""
sdk_version(cmd::VersionCmd) = macho_version(cmd.sdk)

"""
build_platform(cmd::Union{MachOVersionMinCmd, MachOBuildVersionCmd})

Return the `PLATFORM_*` constant identifying the platform the given Load
Command targets. For `MachOVersionMinCmd`, the platform is implied by the
load command type.
"""
build_platform(cmd::MachOBuildVersionCmd) = cmd.platform
function build_platform(cmd::MachOVersionMinCmd)
cmd.cmd == LC_VERSION_MIN_MACOSX && return UInt32(PLATFORM_MACOS)
cmd.cmd == LC_VERSION_MIN_IPHONEOS && return UInt32(PLATFORM_IOS)
cmd.cmd == LC_VERSION_MIN_TVOS && return UInt32(PLATFORM_TVOS)
cmd.cmd == LC_VERSION_MIN_WATCHOS && return UInt32(PLATFORM_WATCHOS)
throw(ArgumentError("Unknown LC_VERSION_MIN_* load command 0x$(string(cmd.cmd, base=16))"))
end

@derefmethod min_os_version(cmd::MachOLoadCmdRef{H, T}) where {H <: MachOHandle} where {T <: VersionCmd}
@derefmethod sdk_version(cmd::MachOLoadCmdRef{H, T}) where {H <: MachOHandle} where {T <: VersionCmd}
@derefmethod build_platform(cmd::MachOLoadCmdRef{H, T}) where {H <: MachOHandle} where {T <: VersionCmd}

"""
version_cmd(oh::MachOHandle)

Return the first `MachOBuildVersionCmd` or `MachOVersionMinCmd` Load Command
within `oh`, or `nothing` if it has neither.
"""
function version_cmd(oh::MachOHandle)
for cmd in MachOLoadCmds(oh)
cmd isa VersionCmd && return cmd
end
return nothing
end

"""
min_os_version(oh::MachOHandle)

Return the minimum OS version that the Mach-O object `oh` was built for, as
recorded in its `LC_BUILD_VERSION` or `LC_VERSION_MIN_*` Load Command, as a
`VersionNumber`. Returns `nothing` if the object records no such version.
"""
function min_os_version(oh::MachOHandle)
cmd = version_cmd(oh)
return cmd === nothing ? nothing : min_os_version(cmd)
end

"""
sdk_version(oh::MachOHandle)

Return the SDK version that the Mach-O object `oh` was built against, as
recorded in its `LC_BUILD_VERSION` or `LC_VERSION_MIN_*` Load Command, as a
`VersionNumber`. Returns `nothing` if the object records no such version.
"""
function sdk_version(oh::MachOHandle)
cmd = version_cmd(oh)
return cmd === nothing ? nothing : sdk_version(cmd)
end

"""
build_platform(oh::MachOHandle)

Return the `PLATFORM_*` constant identifying the platform that the Mach-O
object `oh` was built for, as recorded in its `LC_BUILD_VERSION` or
`LC_VERSION_MIN_*` Load Command. Returns `nothing` if the object records no
such information.
"""
function build_platform(oh::MachOHandle)
cmd = version_cmd(oh)
return cmd === nothing ? nothing : build_platform(cmd)
end
6 changes: 6 additions & 0 deletions src/MachO/MachOLoadCmd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ include("LoadCmds/UnknownCmd.jl")
include("LoadCmds/DylibCmds.jl")
include("LoadCmds/SegmentCmds.jl")
include("LoadCmds/SymtabCmds.jl")
include("LoadCmds/VersionCmds.jl")

function show(io::IO, cmd::MachOLoadCmd)
print(io, "MachOLoadCmd")
Expand Down Expand Up @@ -187,6 +188,11 @@ function load_cmd_type(header::MachOLoadCmdHeader{H}) where {H <: MachOHandle}
LC_ID_DYLIB => MachOIdDylibCmd{H},
LC_RPATH => MachORPathCmd{H},
LC_SYMTAB => MachOSymtabCmd{H},
LC_VERSION_MIN_MACOSX => MachOVersionMinCmd{H},
LC_VERSION_MIN_IPHONEOS => MachOVersionMinCmd{H},
LC_VERSION_MIN_TVOS => MachOVersionMinCmd{H},
LC_VERSION_MIN_WATCHOS => MachOVersionMinCmd{H},
LC_BUILD_VERSION => MachOBuildVersionCmd{H},
)

# We drop LC_REQ_DYLD from the flags because we don't care
Expand Down
20 changes: 20 additions & 0 deletions src/MachO/constants.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,27 @@ const LC_REQ_DYLD = 0x80000000
const LC_DYLIB_CODE_SIGN_DRS = 0x2B # Code signing DRs copied from linked dylibs
const LC_ENCRYPTION_INFO_64 = 0x2C # 64-bit encrypted segment information
const LC_LINKER_OPTION = 0x2D # linker options in MH_OBJECT files
const LC_LINKER_OPTIMIZATION_HINT = 0x2E # optimization hints in MH_OBJECT files
const LC_VERSION_MIN_TVOS = 0x2F # build for AppleTV min OS version
const LC_VERSION_MIN_WATCHOS = 0x30 # build for Watch min OS version
const LC_NOTE = 0x31 # arbitrary data included within a Mach-O file
const LC_BUILD_VERSION = 0x32 # build for platform min OS version
end

# Platform identifiers stored in `LC_BUILD_VERSION` load commands
@constants PLATFORMS "PLATFORM_" begin
const PLATFORM_MACOS = 1
const PLATFORM_IOS = 2
const PLATFORM_TVOS = 3
const PLATFORM_WATCHOS = 4
const PLATFORM_BRIDGEOS = 5
const PLATFORM_MACCATALYST = 6
const PLATFORM_IOSSIMULATOR = 7
const PLATFORM_TVOSSIMULATOR = 8
const PLATFORM_WATCHOSSIMULATOR = 9
const PLATFORM_DRIVERKIT = 10
const PLATFORM_VISIONOS = 11
const PLATFORM_VISIONOSSIMULATOR = 12
end


Expand Down
32 changes: 32 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,38 @@ test_fat_libfoo("./mac64/libfoo_fat64.dylib")
test_fat_libfoo(IOBuffer(read("./mac64/libfoo_fat64.dylib")))
test_metal("./macmetal/dummy")

@testset "MachO minimum OS version" begin
# LC_VERSION_MIN_MACOSX
let oh = only(readmeta(open("./mac64/libfoo.dylib", "r")))
@test min_os_version(oh) == v"10.8.0"
@test sdk_version(oh) == v"10.10.0"
@test build_platform(oh) == MachO.PLATFORM_MACOS
cmds = MachO.findall(MachOLoadCmds(oh), MachOVersionMinCmd)
@test length(cmds) == 1
@test min_os_version(only(cmds)) == v"10.8.0"
@test sdk_version(only(cmds)) == v"10.10.0"
@test build_platform(only(cmds)) == MachO.PLATFORM_MACOS
@test sprint(show, deref(only(cmds))) == "VersionMinCmd: MACOS 10.8.0 (SDK 10.10.0)"
end

# Each slice of a fat binary carries its own version command
for oh in readmeta(open("./macfat/libfoo.dylib", "r"))
@test min_os_version(oh) == v"10.12.0"
end

# LC_BUILD_VERSION
let oh = only(readmeta(open("./libjulias/libjulia.1.10.10.dylib", "r")))
@test min_os_version(oh) == v"11.0.0"
@test sdk_version(oh) == v"13.3.0"
@test build_platform(oh) == MachO.PLATFORM_MACOS
cmd = only(MachO.findall(MachOLoadCmds(oh), MachOBuildVersionCmd))
@test min_os_version(cmd) == v"11.0.0"
@test sdk_version(cmd) == v"13.3.0"
@test build_platform(cmd) == MachO.PLATFORM_MACOS
@test sprint(show, deref(cmd)) == "BuildVersionCmd: MACOS 11.0.0 (SDK 13.3.0)"
end
end

# Run COFF tests
test_libfoo_and_fooifier("./win32/fooifier.exe", "./win32/libfoo.dll")
test_libfoo_and_fooifier("./win64/fooifier.exe", "./win64/libfoo.dll")
Expand Down
Loading