vendor: ruby-macho 2.0

This commit is contained in:
William Woodruff 2018-07-03 12:11:19 -04:00
parent 343a5b45a9
commit bace3ac526
No known key found for this signature in database
GPG Key ID: 600D68320BE45ACC
9 changed files with 588 additions and 88 deletions

View File

@ -1,18 +1,18 @@
require "#{File.dirname(__FILE__)}/macho/structure" require_relative "macho/structure"
require "#{File.dirname(__FILE__)}/macho/view" require_relative "macho/view"
require "#{File.dirname(__FILE__)}/macho/headers" require_relative "macho/headers"
require "#{File.dirname(__FILE__)}/macho/load_commands" require_relative "macho/load_commands"
require "#{File.dirname(__FILE__)}/macho/sections" require_relative "macho/sections"
require "#{File.dirname(__FILE__)}/macho/macho_file" require_relative "macho/macho_file"
require "#{File.dirname(__FILE__)}/macho/fat_file" require_relative "macho/fat_file"
require "#{File.dirname(__FILE__)}/macho/exceptions" require_relative "macho/exceptions"
require "#{File.dirname(__FILE__)}/macho/utils" require_relative "macho/utils"
require "#{File.dirname(__FILE__)}/macho/tools" require_relative "macho/tools"
# The primary namespace for ruby-macho. # The primary namespace for ruby-macho.
module MachO module MachO
# release version # release version
VERSION = "1.2.0".freeze VERSION = "2.0.0".freeze
# Opens the given filename as a MachOFile or FatFile, depending on its magic. # Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened # @param filename [String] the file being opened

View File

@ -23,21 +23,37 @@ module MachO
# Creates a new FatFile from the given (single-arch) Mach-Os # Creates a new FatFile from the given (single-arch) Mach-Os
# @param machos [Array<MachOFile>] the machos to combine # @param machos [Array<MachOFile>] the machos to combine
# @return [FatFile] a new FatFile containing the give machos # @return [FatFile] a new FatFile containing the give machos
# @raise [ArgumentError] if less than one Mach-O is given
def self.new_from_machos(*machos) def self.new_from_machos(*machos)
header = Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size) raise ArgumentError, "expected at least one Mach-O" if machos.empty?
# put the smaller alignments further forwards in fat macho, so that we do less padding
machos = machos.sort_by(&:segment_alignment)
bin = +""
bin << Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size).serialize
offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize) offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
fat_archs = []
macho_pads = {}
macho_bins = {}
machos.each do |macho| machos.each do |macho|
fat_archs << Headers::FatArch.new(macho.header.cputype, macho_offset = Utils.round(offset, 2**macho.segment_alignment)
macho.header.cpusubtype, macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
offset, macho.serialize.bytesize, macho_bins[macho] = macho.serialize
macho.alignment)
offset += macho.serialize.bytesize bin << Headers::FatArch.new(macho.header.cputype, macho.header.cpusubtype,
macho_offset, macho_bins[macho].bytesize,
macho.segment_alignment).serialize
offset += (macho_bins[macho].bytesize + macho_pads[macho])
end end
bin = header.serialize machos.each do |macho|
bin << fat_archs.map(&:serialize).join bin << Utils.nullpad(macho_pads[macho])
bin << machos.map(&:serialize).join bin << macho_bins[macho]
end
new_from_bin(bin) new_from_bin(bin)
end end
@ -265,6 +281,15 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
# @return [Hash] a hash representation of this {FatFile}
def to_h
{
"header" => header.to_h,
"fat_archs" => fat_archs.map(&:to_h),
"machos" => machos.map(&:to_h),
}
end
private private
# Obtain the fat header from raw file data. # Obtain the fat header from raw file data.

View File

@ -475,6 +475,15 @@ module MachO
def serialize def serialize
[magic, nfat_arch].pack(FORMAT) [magic, nfat_arch].pack(FORMAT)
end end
# @return [Hash] a hash representation of this {FatHeader}
def to_h
{
"magic" => magic,
"magic_sym" => MH_MAGICS[magic],
"nfat_arch" => nfat_arch,
}.merge super
end
end end
# Fat binary header architecture structure. A Fat binary has one or more of # Fat binary header architecture structure. A Fat binary has one or more of
@ -508,7 +517,7 @@ module MachO
# @api private # @api private
def initialize(cputype, cpusubtype, offset, size, align) def initialize(cputype, cpusubtype, offset, size, align)
@cputype = cputype @cputype = cputype
@cpusubtype = cpusubtype @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
@offset = offset @offset = offset
@size = size @size = size
@align = align @align = align
@ -518,6 +527,19 @@ module MachO
def serialize def serialize
[cputype, cpusubtype, offset, size, align].pack(FORMAT) [cputype, cpusubtype, offset, size, align].pack(FORMAT)
end end
# @return [Hash] a hash representation of this {FatArch}
def to_h
{
"cputype" => cputype,
"cputype_sym" => CPU_TYPES[cputype],
"cpusubtype" => cpusubtype,
"cpusubtype_sym" => CPU_SUBTYPES[cputype][cpusubtype],
"offset" => offset,
"size" => size,
"align" => align,
}.merge super
end
end end
# 32-bit Mach-O file header structure # 32-bit Mach-O file header structure
@ -639,6 +661,24 @@ module MachO
def alignment def alignment
magic32? ? 4 : 8 magic32? ? 4 : 8
end end
# @return [Hash] a hash representation of this {MachHeader}
def to_h
{
"magic" => magic,
"magic_sym" => MH_MAGICS[magic],
"cputype" => cputype,
"cputype_sym" => CPU_TYPES[cputype],
"cpusubtype" => cpusubtype,
"cpusubtype_sym" => CPU_SUBTYPES[cputype][cpusubtype],
"filetype" => filetype,
"filetype_sym" => MH_FILETYPES[filetype],
"ncmds" => ncmds,
"sizeofcmds" => sizeofcmds,
"flags" => flags,
"alignment" => alignment,
}.merge super
end
end end
# 64-bit Mach-O file header structure # 64-bit Mach-O file header structure
@ -660,6 +700,13 @@ module MachO
super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags) super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
@reserved = reserved @reserved = reserved
end end
# @return [Hash] a hash representation of this {MachHeader64}
def to_h
{
"reserved" => reserved,
}.merge super
end
end end
end end
end end

View File

@ -143,7 +143,7 @@ module MachO
:LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand", :LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand",
:LC_VERSION_MIN_TVOS => "VersionMinCommand", :LC_VERSION_MIN_TVOS => "VersionMinCommand",
:LC_VERSION_MIN_WATCHOS => "VersionMinCommand", :LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
:LC_NOTE => "LoadCommand", :LC_NOTE => "NoteCommand",
:LC_BUILD_VERSION => "BuildVersionCommand", :LC_BUILD_VERSION => "BuildVersionCommand",
}.freeze }.freeze
@ -169,15 +169,16 @@ module MachO
:SG_PROTECTED_VERSION_1 => 0x8, :SG_PROTECTED_VERSION_1 => 0x8,
}.freeze }.freeze
# Mach-O load command structure # The top-level Mach-O load command structure.
# This is the most generic load command - only cmd ID and size are #
# represented, and no actual data. Used when a more specific class # This is the most generic load command -- only the type ID and size are
# isn't available/implemented. # represented. Used when a more specific class isn't available or isn't implemented.
class LoadCommand < MachOStructure class LoadCommand < MachOStructure
# @return [MachO::MachOView] the raw view associated with the load command # @return [MachO::MachOView, nil] the raw view associated with the load command,
# or nil if the load command was created via {create}.
attr_reader :view attr_reader :view
# @return [Integer] the load command's identifying number # @return [Integer] the load command's type ID
attr_reader :cmd attr_reader :cmd
# @return [Integer] the size of the load command, in bytes # @return [Integer] the size of the load command, in bytes
@ -251,8 +252,8 @@ module MachO
view.offset view.offset
end end
# @return [Symbol] a symbol representation of the load command's # @return [Symbol, nil] a symbol representation of the load command's
# identifying number # type ID, or nil if the ID doesn't correspond to a known load command class
def type def type
LOAD_COMMANDS[cmd] LOAD_COMMANDS[cmd]
end end
@ -265,6 +266,17 @@ module MachO
type.to_s type.to_s
end end
# @return [Hash] a hash representation of this load command
# @note Children should override this to include additional information.
def to_h
{
"view" => view.to_h,
"cmd" => cmd,
"cmdsize" => cmdsize,
"type" => type,
}.merge super
end
# Represents a Load Command string. A rough analogue to the lc_str # Represents a Load Command string. A rough analogue to the lc_str
# struct used internally by OS X. This class allows ruby-macho to # struct used internally by OS X. This class allows ruby-macho to
# pretend that strings stored in LCs are immediately available without # pretend that strings stored in LCs are immediately available without
@ -304,6 +316,14 @@ module MachO
def to_i def to_i
@string_offset @string_offset
end end
# @return [Hash] a hash representation of this {LCStr}.
def to_h
{
"string" => to_s,
"offset" => to_i,
}
end
end end
# Represents the contextual information needed by a load command to # Represents the contextual information needed by a load command to
@ -364,6 +384,14 @@ module MachO
segs.join("-") segs.join("-")
end end
# @return [Hash] returns a hash representation of this {UUIDCommand}
def to_h
{
"uuid" => uuid,
"uuid_string" => uuid_string,
}.merge super
end
end end
# A load command indicating that part of this file is to be mapped into # A load command indicating that part of this file is to be mapped into
@ -398,7 +426,7 @@ module MachO
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
FORMAT = "L=2a16L=4l=2L=2".freeze FORMAT = "L=2Z16L=4l=2L=2".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
# @api private # @api private
@ -408,7 +436,7 @@ module MachO
def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff, def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
filesize, maxprot, initprot, nsects, flags) filesize, maxprot, initprot, nsects, flags)
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@segname = segname.delete("\x00") @segname = segname
@vmaddr = vmaddr @vmaddr = vmaddr
@vmsize = vmsize @vmsize = vmsize
@fileoff = fileoff @fileoff = fileoff
@ -448,6 +476,42 @@ module MachO
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end
# Guesses the alignment of the segment.
# @return [Integer] the guessed alignment, as a power of 2
# @note See `guess_align` in `cctools/misc/lipo.c`
def guess_align
return Sections::MAX_SECT_ALIGN if vmaddr.zero?
align = 0
segalign = 1
while (segalign & vmaddr).zero?
segalign <<= 1
align += 1
end
return 2 if align < 2
return Sections::MAX_SECT_ALIGN if align > Sections::MAX_SECT_ALIGN
align
end
# @return [Hash] a hash representation of this {SegmentCommand}
def to_h
{
"segname" => segname,
"vmaddr" => vmaddr,
"vmsize" => vmsize,
"fileoff" => fileoff,
"filesize" => filesize,
"maxprot" => maxprot,
"initprot" => initprot,
"nsects" => nsects,
"flags" => flags,
"sections" => sections.map(&:to_h),
}.merge super
end
end end
# A load command indicating that part of this file is to be mapped into # A load command indicating that part of this file is to be mapped into
@ -455,7 +519,7 @@ module MachO
class SegmentCommand64 < SegmentCommand class SegmentCommand64 < SegmentCommand
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
# @api private # @api private
FORMAT = "L=2a16Q=4l=2L=2".freeze FORMAT = "L=2Z16Q=4l=2L=2".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
# @api private # @api private
@ -510,6 +574,16 @@ module MachO
[cmd, cmdsize, string_offsets[:name], timestamp, current_version, [cmd, cmdsize, string_offsets[:name], timestamp, current_version,
compatibility_version].pack(format) + string_payload compatibility_version].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {DylibCommand}
def to_h
{
"name" => name.to_h,
"timestamp" => timestamp,
"current_version" => current_version,
"compatibility_version" => compatibility_version,
}.merge super
end
end end
# A load command representing some aspect of the dynamic linker, depending # A load command representing some aspect of the dynamic linker, depending
@ -546,6 +620,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {DylinkerCommand}
def to_h
{
"name" => name.to_h,
}.merge super
end
end end
# A load command used to indicate dynamic libraries used in prebinding. # A load command used to indicate dynamic libraries used in prebinding.
@ -576,6 +657,15 @@ module MachO
@nmodules = nmodules @nmodules = nmodules
@linked_modules = linked_modules @linked_modules = linked_modules
end end
# @return [Hash] a hash representation of this {PreboundDylibCommand}
def to_h
{
"name" => name.to_h,
"nmodules" => nmodules,
"linked_modules" => linked_modules,
}.merge super
end
end end
# A load command used to represent threads. # A load command used to represent threads.
@ -641,6 +731,20 @@ module MachO
@reserved5 = reserved5 @reserved5 = reserved5
@reserved6 = reserved6 @reserved6 = reserved6
end end
# @return [Hash] a hash representation of this {RoutinesCommand}
def to_h
{
"init_address" => init_address,
"init_module" => init_module,
"reserved1" => reserved1,
"reserved2" => reserved2,
"reserved3" => reserved3,
"reserved4" => reserved4,
"reserved5" => reserved5,
"reserved6" => reserved6,
}.merge super
end
end end
# A load command containing the address of the dynamic shared library # A load command containing the address of the dynamic shared library
@ -675,6 +779,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@umbrella = LCStr.new(self, umbrella) @umbrella = LCStr.new(self, umbrella)
end end
# @return [Hash] a hash representation of this {SubFrameworkCommand}
def to_h
{
"umbrella" => umbrella.to_h,
}.merge super
end
end end
# A load command signifying membership of a subumbrella containing the name # A load command signifying membership of a subumbrella containing the name
@ -696,6 +807,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_umbrella = LCStr.new(self, sub_umbrella) @sub_umbrella = LCStr.new(self, sub_umbrella)
end end
# @return [Hash] a hash representation of this {SubUmbrellaCommand}
def to_h
{
"sub_umbrella" => sub_umbrella.to_h,
}.merge super
end
end end
# A load command signifying a sublibrary of a shared library. Corresponds # A load command signifying a sublibrary of a shared library. Corresponds
@ -717,6 +835,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_library = LCStr.new(self, sub_library) @sub_library = LCStr.new(self, sub_library)
end end
# @return [Hash] a hash representation of this {SubLibraryCommand}
def to_h
{
"sub_library" => sub_library.to_h,
}.merge super
end
end end
# A load command signifying a shared library that is a subframework of # A load command signifying a shared library that is a subframework of
@ -738,6 +863,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@sub_client = LCStr.new(self, sub_client) @sub_client = LCStr.new(self, sub_client)
end end
# @return [Hash] a hash representation of this {SubClientCommand}
def to_h
{
"sub_client" => sub_client.to_h,
}.merge super
end
end end
# A load command containing the offsets and sizes of the link-edit 4.3BSD # A load command containing the offsets and sizes of the link-edit 4.3BSD
@ -749,10 +881,10 @@ module MachO
# @return [Integer] the number of symbol table entries # @return [Integer] the number of symbol table entries
attr_reader :nsyms attr_reader :nsyms
# @return the string table's offset # @return [Integer] the string table's offset
attr_reader :stroff attr_reader :stroff
# @return the string table size in bytes # @return [Integer] the string table size in bytes
attr_reader :strsize attr_reader :strsize
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
@ -771,6 +903,16 @@ module MachO
@stroff = stroff @stroff = stroff
@strsize = strsize @strsize = strsize
end end
# @return [Hash] a hash representation of this {SymtabCommand}
def to_h
{
"symoff" => symoff,
"nsyms" => nsyms,
"stroff" => stroff,
"strsize" => strsize,
}.merge super
end
end end
# A load command containing symbolic information needed to support data # A load command containing symbolic information needed to support data
@ -864,6 +1006,30 @@ module MachO
@locreloff = locreloff @locreloff = locreloff
@nlocrel = nlocrel @nlocrel = nlocrel
end end
# @return [Hash] a hash representation of this {DysymtabCommand}
def to_h
{
"ilocalsym" => ilocalsym,
"nlocalsym" => nlocalsym,
"iextdefsym" => iextdefsym,
"nextdefsym" => nextdefsym,
"iundefsym" => iundefsym,
"nundefsym" => nundefsym,
"tocoff" => tocoff,
"ntoc" => ntoc,
"modtaboff" => modtaboff,
"nmodtab" => nmodtab,
"extrefsymoff" => extrefsymoff,
"nextrefsyms" => nextrefsyms,
"indirectsymoff" => indirectsymoff,
"nindirectsyms" => nindirectsyms,
"extreloff" => extreloff,
"nextrel" => nextrel,
"locreloff" => locreloff,
"nlocrel" => nlocrel,
}.merge super
end
end end
# A load command containing the offset and number of hints in the two-level # A load command containing the offset and number of hints in the two-level
@ -895,6 +1061,15 @@ module MachO
@table = TwolevelHintsTable.new(view, htoffset, nhints) @table = TwolevelHintsTable.new(view, htoffset, nhints)
end end
# @return [Hash] a hash representation of this {TwolevelHintsCommand}
def to_h
{
"htoffset" => htoffset,
"nhints" => nhints,
"table" => table.hints.map(&:to_h),
}.merge super
end
# A representation of the two-level namespace lookup hints table exposed # A representation of the two-level namespace lookup hints table exposed
# by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`). # by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`).
class TwolevelHintsTable class TwolevelHintsTable
@ -927,6 +1102,14 @@ module MachO
@isub_image = blob >> 24 @isub_image = blob >> 24
@itoc = blob & 0x00FFFFFF @itoc = blob & 0x00FFFFFF
end end
# @return [Hash] a hash representation of this {TwolevelHint}
def to_h
{
"isub_image" => isub_image,
"itoc" => itoc,
}
end
end end
end end
end end
@ -950,6 +1133,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@cksum = cksum @cksum = cksum
end end
# @return [Hash] a hash representation of this {PrebindCksumCommand}
def to_h
{
"cksum" => cksum,
}.merge super
end
end end
# A load command representing an rpath, which specifies a path that should # A load command representing an rpath, which specifies a path that should
@ -984,6 +1174,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload [cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload
end end
# @return [Hash] a hash representation of this {RpathCommand}
def to_h
{
"path" => path.to_h,
}.merge super
end
end end
# A load command representing the offsets and sizes of a blob of data in # A load command representing the offsets and sizes of a blob of data in
@ -1011,6 +1208,14 @@ module MachO
@dataoff = dataoff @dataoff = dataoff
@datasize = datasize @datasize = datasize
end end
# @return [Hash] a hash representation of this {LinkeditDataCommand}
def to_h
{
"dataoff" => dataoff,
"datasize" => datasize,
}.merge super
end
end end
# A load command representing the offset to and size of an encrypted # A load command representing the offset to and size of an encrypted
@ -1040,20 +1245,20 @@ module MachO
@cryptsize = cryptsize @cryptsize = cryptsize
@cryptid = cryptid @cryptid = cryptid
end end
# @return [Hash] a hash representation of this {EncryptionInfoCommand}
def to_h
{
"cryptoff" => cryptoff,
"cryptsize" => cryptsize,
"cryptid" => cryptid,
}.merge super
end
end end
# A load command representing the offset to and size of an encrypted # A load command representing the offset to and size of an encrypted
# segment. Corresponds to LC_ENCRYPTION_INFO_64. # segment. Corresponds to LC_ENCRYPTION_INFO_64.
class EncryptionInfoCommand64 < LoadCommand class EncryptionInfoCommand64 < EncryptionInfoCommand
# @return [Integer] the offset to the encrypted segment
attr_reader :cryptoff
# @return [Integer] the size of the encrypted segment
attr_reader :cryptsize
# @return [Integer] the encryption system, or 0 if not encrypted yet
attr_reader :cryptid
# @return [Integer] 64-bit padding value # @return [Integer] 64-bit padding value
attr_reader :pad attr_reader :pad
@ -1067,12 +1272,16 @@ module MachO
# @api private # @api private
def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad) def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
super(view, cmd, cmdsize) super(view, cmd, cmdsize, cryptoff, cryptsize, cryptid)
@cryptoff = cryptoff
@cryptsize = cryptsize
@cryptid = cryptid
@pad = pad @pad = pad
end end
# @return [Hash] a hash representation of this {EncryptionInfoCommand64}
def to_h
{
"pad" => pad,
}.merge super
end
end end
# A load command containing the minimum OS version on which the binary # A load command containing the minimum OS version on which the binary
@ -1121,6 +1330,16 @@ module MachO
segs.join(".") segs.join(".")
end end
# @return [Hash] a hash representation of this {VersionMinCommand}
def to_h
{
"version" => version,
"version_string" => version_string,
"sdk" => sdk,
"sdk_string" => sdk_string,
}.merge super
end
end end
# A load command containing the minimum OS version on which # A load command containing the minimum OS version on which
@ -1156,6 +1375,40 @@ module MachO
@tool_entries = ToolEntries.new(view, ntools) @tool_entries = ToolEntries.new(view, ntools)
end end
# A string representation of the binary's minimum OS version.
# @return [String] a string representing the minimum OS version.
def minos_string
binary = "%032b" % minos
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# A string representation of the binary's SDK version.
# @return [String] a string representing the SDK version.
def sdk_string
binary = "%032b" % sdk
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# @return [Hash] a hash representation of this {BuildVersionCommand}
def to_h
{
"platform" => platform,
"minos" => minos,
"minos_string" => minos_string,
"sdk" => sdk,
"sdk_string" => sdk_string,
"tool_entries" => tool_entries.tools.map(&:to_h),
}.merge super
end
# A representation of the tool versions exposed # A representation of the tool versions exposed
# by a {BuildVersionCommand} (`LC_BUILD_VERSION`). # by a {BuildVersionCommand} (`LC_BUILD_VERSION`).
class ToolEntries class ToolEntries
@ -1181,37 +1434,23 @@ module MachO
# @return [Integer] the tool's version number # @return [Integer] the tool's version number
attr_reader :version attr_reader :version
# @param tool 32-bit integer # @param tool [Integer] 32-bit integer
# # @param version 32-bit integer # @param version [Integer] 32-bit integer
# @api private # @api private
def initialize(tool, version) def initialize(tool, version)
@tool = tool @tool = tool
@version = version @version = version
end end
# @return [Hash] a hash representation of this {Tool}
def to_h
{
"tool" => tool,
"version" => version,
}
end
end end
end end
# A string representation of the binary's minimum OS version.
# @return [String] a string representing the minimum OS version.
def minos_string
binary = "%032b" % minos
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
# A string representation of the binary's SDK version.
# @return [String] a string representing the SDK version.
def sdk_string
binary = "%032b" % sdk
segs = [
binary[0..15], binary[16..23], binary[24..31]
].map { |s| s.to_i(2) }
segs.join(".")
end
end end
# A load command containing the file offsets and sizes of the new # A load command containing the file offsets and sizes of the new
@ -1272,6 +1511,22 @@ module MachO
@export_off = export_off @export_off = export_off
@export_size = export_size @export_size = export_size
end end
# @return [Hash] a hash representation of this {DyldInfoCommand}
def to_h
{
"rebase_off" => rebase_off,
"rebase_size" => rebase_size,
"bind_off" => bind_off,
"bind_size" => bind_size,
"weak_bind_off" => weak_bind_off,
"weak_bind_size" => weak_bind_size,
"lazy_bind_off" => lazy_bind_off,
"lazy_bind_size" => lazy_bind_size,
"export_off" => export_off,
"export_size" => export_size,
}.merge super
end
end end
# A load command containing linker options embedded in object files. # A load command containing linker options embedded in object files.
@ -1293,6 +1548,13 @@ module MachO
super(view, cmd, cmdsize) super(view, cmd, cmdsize)
@count = count @count = count
end end
# @return [Hash] a hash representation of this {LinkerOptionCommand}
def to_h
{
"count" => count,
}.merge super
end
end end
# A load command specifying the offset of main(). Corresponds to LC_MAIN. # A load command specifying the offset of main(). Corresponds to LC_MAIN.
@ -1317,6 +1579,14 @@ module MachO
@entryoff = entryoff @entryoff = entryoff
@stacksize = stacksize @stacksize = stacksize
end end
# @return [Hash] a hash representation of this {EntryPointCommand}
def to_h
{
"entryoff" => entryoff,
"stacksize" => stacksize,
}.merge super
end
end end
# A load command specifying the version of the sources used to build the # A load command specifying the version of the sources used to build the
@ -1350,6 +1620,14 @@ module MachO
segs.join(".") segs.join(".")
end end
# @return [Hash] a hash representation of this {SourceVersionCommand}
def to_h
{
"version" => version,
"version_string" => version_string,
}.merge super
end
end end
# An obsolete load command containing the offset and size of the (GNU style) # An obsolete load command containing the offset and size of the (GNU style)
@ -1375,6 +1653,14 @@ module MachO
@offset = offset @offset = offset
@size = size @size = size
end end
# @return [Hash] a hash representation of this {SymsegCommand}
def to_h
{
"offset" => offset,
"size" => size,
}.merge super
end
end end
# An obsolete load command containing a free format string table. Each # An obsolete load command containing a free format string table. Each
@ -1412,6 +1698,14 @@ module MachO
@name = LCStr.new(self, name) @name = LCStr.new(self, name)
@header_addr = header_addr @header_addr = header_addr
end end
# @return [Hash] a hash representation of this {FvmfileCommand}
def to_h
{
"name" => name.to_h,
"header_addr" => header_addr,
}.merge super
end
end end
# An obsolete load command containing the path to a library to be loaded # An obsolete load command containing the path to a library to be loaded
@ -1440,6 +1734,52 @@ module MachO
@minor_version = minor_version @minor_version = minor_version
@header_addr = header_addr @header_addr = header_addr
end end
# @return [Hash] a hash representation of this {FvmlibCommand}
def to_h
{
"name" => name.to_h,
"minor_version" => minor_version,
"header_addr" => header_addr,
}.merge super
end
end
# A load command containing an owner name and offset/size for an arbitrary data region.
# Corresponds to LC_NOTE.
class NoteCommand < LoadCommand
# @return [String] the name of the owner for this note
attr_reader :data_owner
# @return [Integer] the offset, within the file, of the note
attr_reader :offset
# @return [Integer] the size, in bytes, of the note
attr_reader :size
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2Z16Q=2".freeze
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 48
def initialize(view, cmd, cmdsize, data_owner, offset, size)
super(view, cmd, cmdsize)
@data_owner = data_owner
@offset = offset
@size = size
end
# @return [Hash] a hash representation of this {NoteCommand}
def to_h
{
"data_owner" => data_owner,
"offset" => offset,
"size" => size,
}.merge super
end
end end
end end
end end

View File

@ -25,7 +25,7 @@ module MachO
# @note load commands are provided in order of ascending offset. # @note load commands are provided in order of ascending offset.
attr_reader :load_commands attr_reader :load_commands
# Creates a new MachOFile instance from a binary string. # Creates a new instance from a binary string.
# @param bin [String] a binary string containing raw Mach-O data # @param bin [String] a binary string containing raw Mach-O data
# @return [MachOFile] a new MachOFile # @return [MachOFile] a new MachOFile
def self.new_from_bin(bin) def self.new_from_bin(bin)
@ -35,7 +35,7 @@ module MachO
instance instance
end end
# Creates a new FatFile from the given filename. # Creates a new instance from data read from the given filename.
# @param filename [String] the Mach-O file to load from # @param filename [String] the Mach-O file to load from
# @raise [ArgumentError] if the given file does not exist # @raise [ArgumentError] if the given file does not exist
def initialize(filename) def initialize(filename)
@ -219,8 +219,7 @@ module MachO
update_sizeofcmds(sizeofcmds - lc.cmdsize) update_sizeofcmds(sizeofcmds - lc.cmdsize)
# pad the space after the load commands to preserve offsets # pad the space after the load commands to preserve offsets
null_pad = "\x00" * lc.cmdsize @raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, Utils.nullpad(lc.cmdsize))
@raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad)
populate_fields if options.fetch(:repopulate, true) populate_fields if options.fetch(:repopulate, true)
end end
@ -252,6 +251,33 @@ module MachO
end end
end end
# The segment alignment for the Mach-O. Guesses conservatively.
# @return [Integer] the alignment, as a power of 2
# @note This is **not** the same as {#alignment}!
# @note See `get_align` and `get_align_64` in `cctools/misc/lipo.c`
def segment_alignment
# special cases: 12 for x86/64/PPC/PP64, 14 for ARM/ARM64
return 12 if %i[i386 x86_64 ppc ppc64].include?(cputype)
return 14 if %i[arm arm64].include?(cputype)
cur_align = Sections::MAX_SECT_ALIGN
segments.each do |segment|
if filetype == :object
# start with the smallest alignment, and work our way up
align = magic32? ? 2 : 3
segment.sections.each do |section|
align = section.align unless section.align <= align
end
else
align = segment.guess_align
end
cur_align = align if align < cur_align
end
cur_align
end
# The Mach-O's dylib ID, or `nil` if not a dylib. # The Mach-O's dylib ID, or `nil` if not a dylib.
# @example # @example
# file.dylib_id # => 'libBar.dylib' # file.dylib_id # => 'libBar.dylib'
@ -408,6 +434,14 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) } File.open(@filename, "wb") { |f| f.write(@raw_data) }
end end
# @return [Hash] a hash representation of this {MachOFile}
def to_h
{
"header" => header.to_h,
"load_commands" => load_commands.map(&:to_h),
}
end
private private
# The file's Mach-O header structure. # The file's Mach-O header structure.

View File

@ -13,6 +13,10 @@ module MachO
# system settable attributes mask # system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00 SECTION_ATTRIBUTES_SYS = 0x00ffff00
# maximum specifiable section alignment, as a power of 2
# @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
MAX_SECT_ALIGN = 15
# association of section flag symbols to values # association of section flag symbols to values
# @api private # @api private
SECTION_FLAGS = { SECTION_FLAGS = {
@ -104,7 +108,7 @@ module MachO
attr_reader :reserved2 attr_reader :reserved2
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
FORMAT = "a16a16L=9".freeze FORMAT = "Z16Z16L=9".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
SIZEOF = 68 SIZEOF = 68
@ -125,16 +129,14 @@ module MachO
@reserved2 = reserved2 @reserved2 = reserved2
end end
# @return [String] the section's name, with any trailing NULL characters # @return [String] the section's name
# removed
def section_name def section_name
sectname.delete("\x00") sectname
end end
# @return [String] the parent segment's name, with any trailing NULL # @return [String] the parent segment's name
# characters removed
def segment_name def segment_name
segname.delete("\x00") segname
end end
# @return [Boolean] whether the section is empty (i.e, {size} is 0) # @return [Boolean] whether the section is empty (i.e, {size} is 0)
@ -151,6 +153,23 @@ module MachO
return false if flag.nil? return false if flag.nil?
flags & flag == flag flags & flag == flag
end end
# @return [Hash] a hash representation of this {Section}
def to_h
{
"sectname" => sectname,
"segname" => segname,
"addr" => addr,
"size" => size,
"offset" => offset,
"align" => align,
"reloff" => reloff,
"nreloc" => nreloc,
"flags" => flags,
"reserved1" => reserved1,
"reserved2" => reserved2,
}.merge super
end
end end
# Represents a section of a segment for 64-bit architectures. # Represents a section of a segment for 64-bit architectures.
@ -159,7 +178,7 @@ module MachO
attr_reader :reserved3 attr_reader :reserved3
# @see MachOStructure::FORMAT # @see MachOStructure::FORMAT
FORMAT = "a16a16Q=2L=8".freeze FORMAT = "Z16Z16Q=2L=8".freeze
# @see MachOStructure::SIZEOF # @see MachOStructure::SIZEOF
SIZEOF = 80 SIZEOF = 80
@ -171,6 +190,13 @@ module MachO
nreloc, flags, reserved1, reserved2) nreloc, flags, reserved1, reserved2)
@reserved3 = reserved3 @reserved3 = reserved3
end end
# @return [Hash] a hash representation of this {Section64}
def to_h
{
"reserved3" => reserved3,
}.merge super
end
end end
end end
end end

View File

@ -26,5 +26,15 @@ module MachO
new(*bin.unpack(format)) new(*bin.unpack(format))
end end
# @return [Hash] a hash representation of this {MachOStructure}.
def to_h
{
"structure" => {
"format" => self.class::FORMAT,
"bytesize" => self.class.bytesize,
},
}
end
end end
end end

View File

@ -22,6 +22,16 @@ module MachO
round(size, alignment) - size round(size, alignment) - size
end end
# Returns a string of null bytes of the requested (non-negative) size
# @param size [Integer] the size of the nullpad
# @return [String] the null string (or empty string, for `size = 0`)
# @raise [ArgumentError] if a non-positive nullpad is requested
def self.nullpad(size)
raise ArgumentError, "size < 0: #{size}" if size.negative?
"\x00" * size
end
# Converts an abstract (native-endian) String#unpack format to big or # Converts an abstract (native-endian) String#unpack format to big or
# little. # little.
# @param format [String] the format string being converted # @param format [String] the format string being converted
@ -46,11 +56,11 @@ module MachO
strings.each do |key, string| strings.each do |key, string|
offsets[key] = next_offset offsets[key] = next_offset
payload << string payload << string
payload << "\x00" payload << Utils.nullpad(1)
next_offset += string.bytesize + 1 next_offset += string.bytesize + 1
end end
payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment) payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
[payload, offsets] [payload, offsets]
end end

View File

@ -19,5 +19,13 @@ module MachO
@endianness = endianness @endianness = endianness
@offset = offset @offset = offset
end end
# @return [Hash] a hash representation of this {MachOView}.
def to_h
{
"endianness" => endianness,
"offset" => offset,
}
end
end end
end end