Aug 13, 2016 Tags: gsoc, programming, ruby, ruby-macho
For a quick background, see the first post in this series.
As of the last two weeks…
Modification of “inconsistent” fat Mach-Os now works!
First, some background: an “inconsistent” Mach-O is a universal (“fat”)
binary whose individual architecture slices do not reference the same
external components. For example, the imaginary inconsistent libfoo.dylib
might have an i386
that references /usr/lib/libimportant.dylib
, while
the x86_64
slice may not reference /usr/lib/libimportant.dylib
at all
(or at a different path). Such inconsistencies are not necessarily
program-breaking or incorrect (different architectures can require different
dependency linkages, after all), and usually arise from a manual lipo(1)
step in the build process instead of the (preferred) -arch
linker flags.
Prior to ruby-macho/55,
ruby-macho assumed that all fat binaries given to it were internally
consistent, meaning that each internal slice contained the same list of
linked dylibs, rpaths, and so forth. As a result, attempting to modify a
dylib
or rpath
that isn’t referenced in all slices of the fat Mach-O
would result in the modification’s corresponding exception.
This was amended via the addition of an :strict
flag (true
by default),
whose behavior is as follows:
true
, fail immediately upon the first failed modification.false
:
To avoid confusing users with this new behavior, the exception hierarchy
was also modified to accommodate the ModificationError
and
RecoverableModificationError
classes, which are subclassed as necessary
by errors like OffsetInsertionError
(unrecoverable) and
DylibUnknownError
(recoverable). This generalization also allows us to
provide additional error context to the user in the form of
RecoverableModificationError
’s macho_slice
attribute, which gives the
index of the first slice to fail during modification.
ruby-macho’s style is now RuboCop enforced!
As of ruby-macho/56, the
main ruby-macho development branch is now consistently formatted (and
enforced by RuboCop). A full list of
ruby-macho’s custom style rules can be found in the project’s
.rubocop.yml
.
Some relevant changes:
Ugly housekeeping methods like set_ncmds
and
get_load_commands
are now prefixed with update_
and populate_
instead.
Exceptions are raised without explicit instantiation, where possible:
1
2
raise LCStrMalformedError.new(lc) # old
raise LCStrMalformedError, lc # new
All freezable constants are now frozen, including dozens of
MachOStructure
-derived FORMAT
s.
Long method definitions and invocations are aligned to the parameter list.
MachOFile#dylib_id=
and FatFile#dylib_id=
are now defined as
change_dylib_id
for uniformity with other modification methods. The old
method names are retained as aliases for compatibility.
Section enumeration is cleaner!
Prior to ruby-macho/57,
the standard way to enumerate a MachOFile
’s sections was a nested loop
like this:
1
2
3
4
5
file.segments.each do |segment|
file.sections(segment).each do |section|
# do something with each section
end
end
Like the old (and thankfully removed) MachOFile#set_lc_str_in_cmd
, this
brings control flow back into the MachOFile
instance for no particularly
good reason. By subclassing MachO::SegmentCommand64
as a
MachO::SegmentCommand
, we were able to make segment instantiation
significantly DRYer and add a generic Segment#sections
to the following
effect:
1
2
3
4
5
file.segments.each do |segment|
segment.sections.each do |section|
# do something with each section
end
end
This preserves the object/flow hierarchy and is much more intuitive.
Consequently, MachOFile#sections
has been marked as deprecated and will
probably be removed shortly.
Miscellaneous improvements!
In line with the subclassing of SegmentCommand64
as a SegmentCommand
,
RoutinesCommand64
is now a RoutinesCommand
as of
ruby-macho/58.
As of ruby-macho/59,
unimplemented unit tests now use skip
instead of pass
. This was a
originally a personal mistake, as ruby-macho’s tests were my first
experience with Minitest::Test
and I wasn’t aware that a distinction was
drawn between a (no-op) pass
and a skip
.
And for the final week:
Merge ruby-macho/61, which adds unit tests for the new behavior in ruby-macho/55.
Cap the 1.0 release off. I may not have time to complete all of the items marked as “important”, but adding broader serialization support and beginning work on constant/namespace delineation are high-priority.
Get 1.0 merged into brew
and activated for all users!