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 "#{File.dirname(__FILE__)}/macho/view"
require "#{File.dirname(__FILE__)}/macho/headers"
require "#{File.dirname(__FILE__)}/macho/load_commands"
require "#{File.dirname(__FILE__)}/macho/sections"
require "#{File.dirname(__FILE__)}/macho/macho_file"
require "#{File.dirname(__FILE__)}/macho/fat_file"
require "#{File.dirname(__FILE__)}/macho/exceptions"
require "#{File.dirname(__FILE__)}/macho/utils"
require "#{File.dirname(__FILE__)}/macho/tools"
require_relative "macho/structure"
require_relative "macho/view"
require_relative "macho/headers"
require_relative "macho/load_commands"
require_relative "macho/sections"
require_relative "macho/macho_file"
require_relative "macho/fat_file"
require_relative "macho/exceptions"
require_relative "macho/utils"
require_relative "macho/tools"
# The primary namespace for ruby-macho.
module MachO
# 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.
# @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
# @param machos [Array<MachOFile>] the machos to combine
# @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)
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)
fat_archs = []
macho_pads = {}
macho_bins = {}
machos.each do |macho|
fat_archs << Headers::FatArch.new(macho.header.cputype,
macho.header.cpusubtype,
offset, macho.serialize.bytesize,
macho.alignment)
offset += macho.serialize.bytesize
macho_offset = Utils.round(offset, 2**macho.segment_alignment)
macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
macho_bins[macho] = macho.serialize
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
bin = header.serialize
bin << fat_archs.map(&:serialize).join
bin << machos.map(&:serialize).join
machos.each do |macho|
bin << Utils.nullpad(macho_pads[macho])
bin << macho_bins[macho]
end
new_from_bin(bin)
end
@ -265,6 +281,15 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) }
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
# Obtain the fat header from raw file data.

View File

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

View File

@ -143,7 +143,7 @@ module MachO
:LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand",
:LC_VERSION_MIN_TVOS => "VersionMinCommand",
:LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
:LC_NOTE => "LoadCommand",
:LC_NOTE => "NoteCommand",
:LC_BUILD_VERSION => "BuildVersionCommand",
}.freeze
@ -169,15 +169,16 @@ module MachO
:SG_PROTECTED_VERSION_1 => 0x8,
}.freeze
# 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
# isn't available/implemented.
# The top-level Mach-O load command structure.
#
# This is the most generic load command -- only the type ID and size are
# represented. Used when a more specific class isn't available or isn't implemented.
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
# @return [Integer] the load command's identifying number
# @return [Integer] the load command's type ID
attr_reader :cmd
# @return [Integer] the size of the load command, in bytes
@ -251,8 +252,8 @@ module MachO
view.offset
end
# @return [Symbol] a symbol representation of the load command's
# identifying number
# @return [Symbol, nil] a symbol representation of the load command's
# type ID, or nil if the ID doesn't correspond to a known load command class
def type
LOAD_COMMANDS[cmd]
end
@ -265,6 +266,17 @@ module MachO
type.to_s
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
# struct used internally by OS X. This class allows ruby-macho to
# pretend that strings stored in LCs are immediately available without
@ -304,6 +316,14 @@ module MachO
def to_i
@string_offset
end
# @return [Hash] a hash representation of this {LCStr}.
def to_h
{
"string" => to_s,
"offset" => to_i,
}
end
end
# Represents the contextual information needed by a load command to
@ -364,6 +384,14 @@ module MachO
segs.join("-")
end
# @return [Hash] returns a hash representation of this {UUIDCommand}
def to_h
{
"uuid" => uuid,
"uuid_string" => uuid_string,
}.merge super
end
end
# A load command indicating that part of this file is to be mapped into
@ -398,7 +426,7 @@ module MachO
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2a16L=4l=2L=2".freeze
FORMAT = "L=2Z16L=4l=2L=2".freeze
# @see MachOStructure::SIZEOF
# @api private
@ -408,7 +436,7 @@ module MachO
def initialize(view, cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
filesize, maxprot, initprot, nsects, flags)
super(view, cmd, cmdsize)
@segname = segname.delete("\x00")
@segname = segname
@vmaddr = vmaddr
@vmsize = vmsize
@fileoff = fileoff
@ -448,6 +476,42 @@ module MachO
return false if flag.nil?
flags & flag == flag
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
# A load command indicating that part of this file is to be mapped into
@ -455,7 +519,7 @@ module MachO
class SegmentCommand64 < SegmentCommand
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2a16Q=4l=2L=2".freeze
FORMAT = "L=2Z16Q=4l=2L=2".freeze
# @see MachOStructure::SIZEOF
# @api private
@ -510,6 +574,16 @@ module MachO
[cmd, cmdsize, string_offsets[:name], timestamp, current_version,
compatibility_version].pack(format) + string_payload
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
# A load command representing some aspect of the dynamic linker, depending
@ -546,6 +620,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:name]].pack(format) + string_payload
end
# @return [Hash] a hash representation of this {DylinkerCommand}
def to_h
{
"name" => name.to_h,
}.merge super
end
end
# A load command used to indicate dynamic libraries used in prebinding.
@ -576,6 +657,15 @@ module MachO
@nmodules = nmodules
@linked_modules = linked_modules
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
# A load command used to represent threads.
@ -641,6 +731,20 @@ module MachO
@reserved5 = reserved5
@reserved6 = reserved6
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
# A load command containing the address of the dynamic shared library
@ -675,6 +779,13 @@ module MachO
super(view, cmd, cmdsize)
@umbrella = LCStr.new(self, umbrella)
end
# @return [Hash] a hash representation of this {SubFrameworkCommand}
def to_h
{
"umbrella" => umbrella.to_h,
}.merge super
end
end
# A load command signifying membership of a subumbrella containing the name
@ -696,6 +807,13 @@ module MachO
super(view, cmd, cmdsize)
@sub_umbrella = LCStr.new(self, sub_umbrella)
end
# @return [Hash] a hash representation of this {SubUmbrellaCommand}
def to_h
{
"sub_umbrella" => sub_umbrella.to_h,
}.merge super
end
end
# A load command signifying a sublibrary of a shared library. Corresponds
@ -717,6 +835,13 @@ module MachO
super(view, cmd, cmdsize)
@sub_library = LCStr.new(self, sub_library)
end
# @return [Hash] a hash representation of this {SubLibraryCommand}
def to_h
{
"sub_library" => sub_library.to_h,
}.merge super
end
end
# A load command signifying a shared library that is a subframework of
@ -738,6 +863,13 @@ module MachO
super(view, cmd, cmdsize)
@sub_client = LCStr.new(self, sub_client)
end
# @return [Hash] a hash representation of this {SubClientCommand}
def to_h
{
"sub_client" => sub_client.to_h,
}.merge super
end
end
# 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
attr_reader :nsyms
# @return the string table's offset
# @return [Integer] the string table's offset
attr_reader :stroff
# @return the string table size in bytes
# @return [Integer] the string table size in bytes
attr_reader :strsize
# @see MachOStructure::FORMAT
@ -771,6 +903,16 @@ module MachO
@stroff = stroff
@strsize = strsize
end
# @return [Hash] a hash representation of this {SymtabCommand}
def to_h
{
"symoff" => symoff,
"nsyms" => nsyms,
"stroff" => stroff,
"strsize" => strsize,
}.merge super
end
end
# A load command containing symbolic information needed to support data
@ -864,6 +1006,30 @@ module MachO
@locreloff = locreloff
@nlocrel = nlocrel
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
# 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)
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
# by a {TwolevelHintsCommand} (`LC_TWOLEVEL_HINTS`).
class TwolevelHintsTable
@ -927,6 +1102,14 @@ module MachO
@isub_image = blob >> 24
@itoc = blob & 0x00FFFFFF
end
# @return [Hash] a hash representation of this {TwolevelHint}
def to_h
{
"isub_image" => isub_image,
"itoc" => itoc,
}
end
end
end
end
@ -950,6 +1133,13 @@ module MachO
super(view, cmd, cmdsize)
@cksum = cksum
end
# @return [Hash] a hash representation of this {PrebindCksumCommand}
def to_h
{
"cksum" => cksum,
}.merge super
end
end
# A load command representing an rpath, which specifies a path that should
@ -984,6 +1174,13 @@ module MachO
cmdsize = SIZEOF + string_payload.bytesize
[cmd, cmdsize, string_offsets[:path]].pack(format) + string_payload
end
# @return [Hash] a hash representation of this {RpathCommand}
def to_h
{
"path" => path.to_h,
}.merge super
end
end
# A load command representing the offsets and sizes of a blob of data in
@ -1011,6 +1208,14 @@ module MachO
@dataoff = dataoff
@datasize = datasize
end
# @return [Hash] a hash representation of this {LinkeditDataCommand}
def to_h
{
"dataoff" => dataoff,
"datasize" => datasize,
}.merge super
end
end
# A load command representing the offset to and size of an encrypted
@ -1040,20 +1245,20 @@ module MachO
@cryptsize = cryptsize
@cryptid = cryptid
end
# @return [Hash] a hash representation of this {EncryptionInfoCommand}
def to_h
{
"cryptoff" => cryptoff,
"cryptsize" => cryptsize,
"cryptid" => cryptid,
}.merge super
end
end
# A load command representing the offset to and size of an encrypted
# segment. Corresponds to LC_ENCRYPTION_INFO_64.
class EncryptionInfoCommand64 < LoadCommand
# @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
class EncryptionInfoCommand64 < EncryptionInfoCommand
# @return [Integer] 64-bit padding value
attr_reader :pad
@ -1067,12 +1272,16 @@ module MachO
# @api private
def initialize(view, cmd, cmdsize, cryptoff, cryptsize, cryptid, pad)
super(view, cmd, cmdsize)
@cryptoff = cryptoff
@cryptsize = cryptsize
@cryptid = cryptid
super(view, cmd, cmdsize, cryptoff, cryptsize, cryptid)
@pad = pad
end
# @return [Hash] a hash representation of this {EncryptionInfoCommand64}
def to_h
{
"pad" => pad,
}.merge super
end
end
# A load command containing the minimum OS version on which the binary
@ -1121,6 +1330,16 @@ module MachO
segs.join(".")
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
# A load command containing the minimum OS version on which
@ -1156,6 +1375,40 @@ module MachO
@tool_entries = ToolEntries.new(view, ntools)
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
# by a {BuildVersionCommand} (`LC_BUILD_VERSION`).
class ToolEntries
@ -1181,37 +1434,23 @@ module MachO
# @return [Integer] the tool's version number
attr_reader :version
# @param tool 32-bit integer
# # @param version 32-bit integer
# @param tool [Integer] 32-bit integer
# @param version [Integer] 32-bit integer
# @api private
def initialize(tool, version)
@tool = tool
@version = version
end
# @return [Hash] a hash representation of this {Tool}
def to_h
{
"tool" => tool,
"version" => version,
}
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
# A load command containing the file offsets and sizes of the new
@ -1272,6 +1511,22 @@ module MachO
@export_off = export_off
@export_size = export_size
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
# A load command containing linker options embedded in object files.
@ -1293,6 +1548,13 @@ module MachO
super(view, cmd, cmdsize)
@count = count
end
# @return [Hash] a hash representation of this {LinkerOptionCommand}
def to_h
{
"count" => count,
}.merge super
end
end
# A load command specifying the offset of main(). Corresponds to LC_MAIN.
@ -1317,6 +1579,14 @@ module MachO
@entryoff = entryoff
@stacksize = stacksize
end
# @return [Hash] a hash representation of this {EntryPointCommand}
def to_h
{
"entryoff" => entryoff,
"stacksize" => stacksize,
}.merge super
end
end
# A load command specifying the version of the sources used to build the
@ -1350,6 +1620,14 @@ module MachO
segs.join(".")
end
# @return [Hash] a hash representation of this {SourceVersionCommand}
def to_h
{
"version" => version,
"version_string" => version_string,
}.merge super
end
end
# An obsolete load command containing the offset and size of the (GNU style)
@ -1375,6 +1653,14 @@ module MachO
@offset = offset
@size = size
end
# @return [Hash] a hash representation of this {SymsegCommand}
def to_h
{
"offset" => offset,
"size" => size,
}.merge super
end
end
# An obsolete load command containing a free format string table. Each
@ -1412,6 +1698,14 @@ module MachO
@name = LCStr.new(self, name)
@header_addr = header_addr
end
# @return [Hash] a hash representation of this {FvmfileCommand}
def to_h
{
"name" => name.to_h,
"header_addr" => header_addr,
}.merge super
end
end
# An obsolete load command containing the path to a library to be loaded
@ -1440,6 +1734,52 @@ module MachO
@minor_version = minor_version
@header_addr = header_addr
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

View File

@ -25,7 +25,7 @@ module MachO
# @note load commands are provided in order of ascending offset.
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
# @return [MachOFile] a new MachOFile
def self.new_from_bin(bin)
@ -35,7 +35,7 @@ module MachO
instance
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
# @raise [ArgumentError] if the given file does not exist
def initialize(filename)
@ -219,8 +219,7 @@ module MachO
update_sizeofcmds(sizeofcmds - lc.cmdsize)
# pad the space after the load commands to preserve offsets
null_pad = "\x00" * lc.cmdsize
@raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad)
@raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, Utils.nullpad(lc.cmdsize))
populate_fields if options.fetch(:repopulate, true)
end
@ -252,6 +251,33 @@ module MachO
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.
# @example
# file.dylib_id # => 'libBar.dylib'
@ -408,6 +434,14 @@ module MachO
File.open(@filename, "wb") { |f| f.write(@raw_data) }
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
# The file's Mach-O header structure.

View File

@ -13,6 +13,10 @@ module MachO
# system settable attributes mask
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
# @api private
SECTION_FLAGS = {
@ -104,7 +108,7 @@ module MachO
attr_reader :reserved2
# @see MachOStructure::FORMAT
FORMAT = "a16a16L=9".freeze
FORMAT = "Z16Z16L=9".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 68
@ -125,16 +129,14 @@ module MachO
@reserved2 = reserved2
end
# @return [String] the section's name, with any trailing NULL characters
# removed
# @return [String] the section's name
def section_name
sectname.delete("\x00")
sectname
end
# @return [String] the parent segment's name, with any trailing NULL
# characters removed
# @return [String] the parent segment's name
def segment_name
segname.delete("\x00")
segname
end
# @return [Boolean] whether the section is empty (i.e, {size} is 0)
@ -151,6 +153,23 @@ module MachO
return false if flag.nil?
flags & flag == flag
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
# Represents a section of a segment for 64-bit architectures.
@ -159,7 +178,7 @@ module MachO
attr_reader :reserved3
# @see MachOStructure::FORMAT
FORMAT = "a16a16Q=2L=8".freeze
FORMAT = "Z16Z16Q=2L=8".freeze
# @see MachOStructure::SIZEOF
SIZEOF = 80
@ -171,6 +190,13 @@ module MachO
nreloc, flags, reserved1, reserved2)
@reserved3 = reserved3
end
# @return [Hash] a hash representation of this {Section64}
def to_h
{
"reserved3" => reserved3,
}.merge super
end
end
end
end

View File

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

View File

@ -22,6 +22,16 @@ module MachO
round(size, alignment) - size
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
# little.
# @param format [String] the format string being converted
@ -46,11 +56,11 @@ module MachO
strings.each do |key, string|
offsets[key] = next_offset
payload << string
payload << "\x00"
payload << Utils.nullpad(1)
next_offset += string.bytesize + 1
end
payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment)
payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
[payload, offsets]
end

View File

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