Wrapped function is only called from linked librar

2019-07-22 23:55发布

问题:

Edit: Made title a bit clearer.

I am trying to wrap glibc's __assert_fail and __assert_perror_fail functions with my own that log the messages using syslog.

I have verified that if I fail an assert my functions get called. The problem lies in libzmq's assertions. libzmq's assertions only invoke my wrapper functions when I build with -static.

NOTES

  • I patched libzmq to call __assert_* instead of fprintf(stderr, ...), and I have verified that it correctly calls __assert_*.

  • I also patched libzmq to randomly have assertion failures from within the zmq_assert macros so that I can easily reproduce. If the patch is wanted, I will put it up.

Here is some test code

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <zmq.h>

extern "C" void
__wrap___assert_perror_fail(int __errnum, const char *__file,
                            unsigned int __line, const char *__function)
{
        fprintf(stderr, "TESTING123:: %s:%u %s: Unexpected error: %s.\n",
                __file, __line, __function, strerror(__errnum));
        abort();
}

extern "C" void
__wrap___assert_fail(const char *__assertion, const char *__file,
                     unsigned int __line, const char *__function)
{
        fprintf(stderr, "TESTING123:: %s:%u %s: Assertion '%s' failed.\n",
                __file, __line, __function, __assertion);
        abort();
}

int main()
{
#ifdef DO_ASSERT
        assert(1 == 0);
#endif
        void *ctx = zmq_init(0);
        void *req = zmq_socket(ctx, ZMQ_REQ);
        void *rep = zmq_socket(ctx, ZMQ_REQ);
        zmq_bind(rep, "inproc://inproc-1");
        zmq_connect(req, "inproc://inproc-1");
        unsigned long long c = 0;
        while (1) {
                zmq_msg_t msg;

                zmq_msg_init_size(&msg, 1024);
                zmq_send(req, &msg, 0);
                zmq_msg_close(&msg);

                zmq_msg_init(&msg);
                zmq_recv(rep, &msg, 0);
                zmq_send(rep, &msg, 0);
                zmq_msg_close(&msg);

                zmq_msg_init(&msg);
                zmq_recv(req, &msg, 0);
                zmq_msg_close(&msg);

                ++c;
                if (c % 1000000 == 0) {
                        fprintf(stderr, "processed %llu messages\n", c);
                }
        }
        return 0;
}

Which I build 4 ways with/without DO_ASSERT, dynamic/static

$ g++ -DDO_ASSERT -o t-me-dyn t.cc -Wl,-wrap,__assert_fail -Wl,-wrap,__asser_perror_fail -lzmq -lpthread -luuid -lrt 
$ g++ -static -DDO_ASSERT -o t-me-sta t.cc -Wl,-wrap,__assert_fail -Wl,-wrap,__asser_perror_fail -lzmq -lpthread -luuid -lrt 
$ g++ -o t-zmq-dyn t.cc -Wl,-wrap,__assert_fail -Wl,-wrap,__asser_perror_fail -lzmq -lpthread -luuid -lrt 
$ g++ -static -o t-zmq-sta t.cc -Wl,-wrap,__assert_fail -Wl,-wrap,__asser_perror_fail -lzmq -lpthread -luuid -lrt 
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.1/../../../../lib/libzmq.a(libzmq_la-ip.o): In function 'zmq::resolve_ip_interface(sockaddr_storage*, unsigned int*, char const*)':
(.text+0x49b): warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

And I get the following when running them

$ for bin in t-{me,zmq}-{dyn,sta}; do echo ==== $bin ====; ./$bin; done
==== t-me-dyn ====
TESTING123:: t.cc:29 int main(): Assertion '1 == 0' failed.
Aborted
==== t-me-sta ====
TESTING123:: t.cc:29 int main(): Assertion '1 == 0' failed.
Aborted
==== t-zmq-dyn ====
t-zmq-dyn: lb.cpp:142: int zmq::lb_t::send(zmq_msg_t*, int): Assertion 'rc == 0' failed.
Aborted
==== t-zmq-sta ====
TESTING123:: lb.cpp:142 int zmq::lb_t::send(zmq_msg_t*, int): Assertion 'rc == 0' failed.
Aborted

So what am I doing wrong? According to man ld

If you link other code with this file using --wrap malloc, then all calls to "malloc" will call the function "__wrap_malloc" instead.

which is not what I am seeing.

回答1:

Your mental model of how --wrap linker option works is likely all wrong.

It's quite simple really: when you are linking a particular ELF executable or shared library with --wrap foo, all the linker does is:

  • if it sees a reference to foo, it replaces it with a reference to __wrap_foo,
  • if it sees a reference to __real_foo, it replaces with a reference to foo.

I repeat, that is all it does. In particular, since you have not relinked libzmq.so with --wrap, libzmq.so continues to call __assert_fail (i.e. no renaming whatsoever is happening inside libzmq.so).

In order to interpose libc function, forget the --wrap.

Instead, simply define a new __assert_fail in your main executable. When you do that, your definition will get called regardless of whether the call comes from the main executable, or from libzmq.so (or from anywhere else).

If you don't want to call the version of __assert_fail from libc, you are done. If you do, you'll have to look it up dynamically (via dlsym).