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/38 and
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...
There’s also 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 add_rpath
, delete_rpath
,
and change_install_name
should be favored as they do all of the busy work
internally.
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
ruby-macho/40,
ruby-macho/44,
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
across).
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 master
here.
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 LoadCommand.create
was hardcoded to use :LC_LOAD_DYLIB
in MachOFile#change_install_name
.
This was fixed in
ruby-macho/53 by changing
1
new_lc = LoadCommand.create(:LC_LOAD_DYLIB, new_name, ...)
to:
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 virtualenv
implements its own
mach_o_change
method
that leaves any unoverwritten bytes of an old install name in the padding.
This broke the assumption, and left ruby-macho
very confused.
Luckily, the fix was simple and merged as
ruby-macho/52.
Additionally, the virtualenv
team was notified of their tool’s (not
necessarily incorrect, but strange) behavior via
virtualenv/944.
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: machotool
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!
- William