Sunday, June 18, 2017

Tweaking binaries with elfedit

On Solaris and illumos, you can inspect shared objects (binaries and libraries) with elfdump. In the most common case, you're simply looking for what shared libraries you're linked against, in which case it's elfdump -d (or, for those of us who were doing this years before elfdump came into existence, dump -Lv). For example:

% elfdump -d /bin/true

Dynamic Section:  .dynamic
     index  tag                value
       [0]  NEEDED            0x1d6               libc.so.1
       [1]  INIT              0x8050d20          

and it goes on a bit. But basically you're looking at the NEEDED lines to see which shared libraries you need. (The other field that's generally of interest for a shared library is the SONAME field.)

However, you can go beyond this, and use elfedit to manipulate what's present here. You can essentially replicate the above with:

elfedit -r -e dyn:dump /bin/true

Here the -r flag says read-only (we're just looking), and -e says execute the command that follows, which is dyn:dump - or just show the dynamic section.

If you look around, you'll see that the classic example is to set the runpath (which you might see as RPATH or RUNPATH in the dump output). This was used to fix up binaries that had been built incorrectly, or where you've moved the libraries somewhere other than where the binary normally looks for them. Which might look like:

elfedit -e 'dyn:runpath /my/local/lib' prog

This is the first example in the man page, and the standard example wherever you look. (Note the quotes - that's a single command input to elfedit.)

However, another common case I come across is where libtool has completely mangled the link so the full pathname of the library (at build time, no less) has been embedded in the binary (either in absolute or relative form). In other words, rather than the NEEDED section being

libfoo.so.1

it ends up being

/home/ptribble/build/bar/.libs/libfoo.so.1

With this sort of error, no amount of tinkering with RPATH is going to help the binary find the library. Fortunately, elfedit can help us here too.

First you need to work out which element you want to modify. Back to elfedit again to dump out the structure

% elfedit -r -e dyn:dump /bin/baz
     index  tag                value
       [0]  POSFLAG_1         0x1                 [ LAZY ]
       [1]  NEEDED            0x8e2               /home/.../libfoo.so.1

It might be further down, of course. But the entry we want to edit is index number 1. We can narrow down the output just to this element by using the -dynndx flag to the dyn:dump command, for example

elfedit -r -e 'dyn:dump -dynndx 1' /bin/baz

or, equivalently, using dyn:value

elfedit -r -e 'dyn:value -dynndx 1' /bin/baz

And we can actually set the value as well. This requires the -s flag to set a string, but you end up with:

elfedit -e 'dyn:value -dynndx -s 1 libfoo.so.1' /bin/baz

and then if you use elfdump or elfedit or ldd to look at the binary, it should pick up the library correctly.

This is really very simple (the hardest part is having to work out what the index of the right entry is). I didn't find anything when searching that actually describes how simple it is, so I thought it worth documenting for the next time I need it.


1 comment:

Anonymous said...

Hi Peter.

Thanks for this nice article about elfedit.

One nice thing to note about the libtool example that you used is that the desired string libfoo.so.1 is at the tail of the existing value. Therefore, this edit doesn't consume any of the reserved space that the Solaris ld creates for such edits. As such, you will see that the value of SUNWSTRPAD doesn't change. This edit comes at no cost.

If you execute the command 'set d 1' before that command, or run elfedit with the -d option, elfedit will show you how it satisfied the request. That's not really necessary, but interesting to the curious with some ELF background.

Identifying the specific NEEDED entry to modify has always been the weak part of this story. -dynndx does indeed work, but doing that in an automated way requires some sort of wrapper script to run elfdump or elfedit to get that index, and then create the necessary elfedit with the actual index value. With Solaris 11.3, I introduced a plethora of -with- selection options, intended to make it easier to do things like that in a single step. Your example would be:

% elfedit -e 'dyn:value -with-valstr /home/ptribble/build/bar/.libs/libfoo.so.1 -s libfoo.so.1'

The next release of Solaris will have a dyn:needed command intended to simplify NEEDED manipulations further and handle some of the tricker corner cases (not relevant to the simple example here).

Happy Editing!

- Ali Bahrami