A third-party has provided a C++ executable fooapp
that uses a shared object libfoo.so
. The library also comes with a header foo.hpp
so developers can build other applications:
/* foo.hpp */
namespace foo {
void bar(int a, int b);
// More code below here <--- NOTE!!!
}
This is a standard LD_PRELOAD
-based function interposition workflow.
First, I write my own version of the library, myfoo.cpp
that exactly mirrors part of foo.hpp
:
/* myfoo.hpp */
# include <ofstream>
namespace foo {
void bar(int a, int b) {
std::cout << a << "," << b << std::endl;
}
// NOTHING below here <-- NOTE!!!
}
Then I compile my library into libmyfoo.so
and see the following:
$ nm libfoo.so -C | fgrep bar
0000000000021fc0 T foo::bar(int, int)
$ nm libmyfoo.so -C | fgrep bar
0000000000010c30 T foo::bar(int, int)
$ LD_DEBUG=bindings LD_DEBUG_OUTPUT=ld_debug.log LD_PRELOAD=./libmyfoo.so fooapp
Success! ld_debug.log
shows binding as expected, and bar(...)
generates output to the console.
For the failure example, I'm going to (1) change one character in myfoo.hpp
and (2) then fix that character in the binary using objcopy
:
/* myfoo.hpp */
# include <ofstream>
namespace foq { // <-- NAME CHANGE!
void bar(int a, int b) {
std::cout << a << "," << b << std::endl;
}
// NOTHING below here <-- NOTE!!!
}
When I compile my library into libmyfoq.so
I see the following:
$ nm libfoo.so -C | fgrep bar
0000000000021fc0 T foo::bar(int, int) # <-- Sames as before
$ nm libmyfoq.so -C | fgrep bar
0000000000010c30 T foq::bar(int, int) # <-- New name as expected
$ objcopy --redefine-syms=sym.map libmyfoq.so libmyfoo.so # <-- NEW STEP!
$ nm libmyfoo.so -C | fgrep bar
0000000000010c30 T foo::bar(int, int) # <-- SUCCESSful name update
$ LD_DEBUG=bindings LD_DEBUG_OUTPUT=ld_debug.log LD_PRELOAD=./libmyfoo.so fooapp
Failure! ld_debug.log
shows NO binding of fooapp
symbols to libmyfoo.so
.
(PS -- If you're curious, sym.map
contains a mapping between the mangled name of foq::bar
and the mangled name of foo::bar
. You can see these if you drop the -C
from the nm
command. See man objcopy
for additional details.)
In summary:
objcopy
is renaming the symbol correctly.What's the story here?
WHY? What's the story here?
objcopy -redefine-syms
will only redefine the debugging symbols in the .symtab
and .strtab
symbol tables, not the symbols in the .dynsym
and .dynstr
symbol tables that is used for dynamic loading.
If you examine the library you created with objcopy
with nm -D
or readelf -s
you'll see that the name used by the dynamic loader is still _ZN3foq3barEii
, or with nm -DC
, foq::bar(int, int)
.
So, that's what's going on.
The most official references I could find indicating that objcopy
can't be used to update the dynamic symbols is this unassigned bugzilla entry from 2010:
Bug 11386 - objcopy should be able to update dynamic symbols visibility
And my own query on the official binutils
mailing list:
Redefining dynamic symbols
As a side note, I don't think there's a tool available to redefine the dynamic symbols (properly), but I could be wrong.
The cjacker/elfhash project from 2015 seems to make an attempt. I tried that and it seems to actually rename the dynamic symbol name - but loading the .so
afterwards fails and the original libs symbol is used instead.
% elfhash -f _ZN3foq3barEii -t _ZN3foo3barEii libmyfoq.so
% mv libmyfoq.so libmyfoo.so
% LD_DEBUG=bindings LD_DEBUG_OUTPUT=ld_debug.log LD_PRELOAD=./libmyfoo.so ./fooapp
ERROR: ld.so: object './libmyfoo.so' from LD_PRELOAD cannot be preloaded (cannot change memory protections): ignored.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With