Jul 31, 2016 Tags: gsoc, programming, ruby, ruby-macho
For a quick background, see the first post in this series.
As of the last three weeks (sorry, I got a bit caught up!):
Creation and serialization of load commands is here!
ruby-macho/40 have found
their way into
master, bringing a new set of methods for creating,
serializing, adding, and deleting abstract load commands:
1 2 3 4 5 6 7 macho = MachO::MachOFile.new("hello.o") rpath = MachO::LoadCommand.create(:LC_RPATH, "/var/lib") puts rpath.serialize # => "\x1c\x00\x00\x80..." macho.add_command(rpath) # prefer MachOFile#add_rpath("/var/lib") macho.replace_command(macho.load_commands.sample, rpath) # bad idea... macho.delete_command(macho.load_commands.sample) # also a bad idea...
MachOFile#insert_command, which takes an offset and a load
command to be inserted at that offset. These methods are exposed as part
of the public API, but specialized methods like
change_install_name should be favored as they do all of the busy work
Rpath addition, deletion, and modification are now more cautious!
After a discussion in ruby-macho/41, we realized that the current approach to handling critical load commands (like rpaths and dylibs) allowed for the creation of Mach-Os with inconsistent (or at least ambiguous) loading/linking behavior.
For example, prior to
and ruby-macho/49, it was
possible via various techniques to create multiple (duplicated) rpaths
within a single Mach-O. These same techniques actually exist in
install_name_tool as well, suggesting that Apple is either not aware of
this problem or does not consider it particularly serious.
Since we aren’t looking to
install_name_tool as a paragon of good design,
ruby-macho now throws an exception
RpathExistsError if the user tries
to create a duplicate rpath. Similarly, ruby-macho will delete all
duplicate rpaths when asked to change or delete an rpath (unlike
install_name_tool, which will only change or delete the first it comes
We’re at 100% documentation coverage!
I’ve been neglecting ruby-macho’s YARD docs for quite a while, so “completing” them (largely an affair of properly marking constants) was long overdue. Needless to say, 100% coverage doesn’t tell you anything about the actual documentation quality, although I’d like to think that ruby-macho’s are fairly detailed. That being said, I’m certain that there are plenty of typos and outdated method signatures floating around.
You can view the relevant work in
ruby-macho/46, and the
documentation current with
Bugs are being squashed and the code is getting cleaner!
Some routine cleanup was in order, and can be seen in ruby-macho/43.
More interestingly, the addition of relocatable Python virtualenvs in Homebrew brew/592 exposed some interesting (and previously hypothetical) bugs in ruby-macho. In particular:
Fat files whose Mach-O slices contained differing dylibs were observed for the first time, causing ruby-macho to error upon failing to change the given install name in all slices. ruby-macho/55 tackles this by adding an options hash to the relevant modification methods, allowing the user to specify how strict they want to be with the operation:
1 2 fat.change_install_name(old, new) # fails if a slice doesn't have `old` fat.change_install_name(old, new, strict: false) # ignores irrelevant slices
Changing the install name of a dylib load command whose type was not
LC_LOAD_DYLIB would silently corrupt the Mach-O, as
was hardcoded to use
This was fixed in
ruby-macho/53 by changing
1 new_lc = LoadCommand.create(:LC_LOAD_DYLIB, new_name, ...)
1 new_lc = LoadCommand.create(old_lc.type, new_name, ...)
LoadCommand::LCStr previously assumed that load command strings were
always padded with nulls, making it safe to retrieve a
lc_str simply by
deleting all zero bytes that followed it. While this is correct for all
Mach-Os produced by the standard OS X toolchain, Python’s
implements its own
that leaves any unoverwritten bytes of an old install name in the padding.
This broke the assumption, and left ruby-macho
Luckily, the fix was simple and merged as
virtualenv team was notified of their tool’s (not
necessarily incorrect, but strange) behavior via
And over the next two weeks:
Get ruby-macho/55 into
master and finally put out a new release.
Continue work on the improved command-line tools. I haven’t pushed my local
branch, mostly because I’m not satisfied with its current state. Ultimately,
however, I’d like to have two general purpose utilities completed:
for modifications, and
machoinfo for querying information within a Mach-O.
Continue cleaning up and expanding the test suite. This has been happening slowly in the form of additions and tweaks in the PRs mentioned above, but I’d also like to put out 1 or 2 PRs dedicated to general improvements.
Everything in the newly-minted 1.0 milestone!
Thanks for reading!