In an earlier post I talked about teaching ltrace to read function prototypes
from DWARF data. I'm making more progress on that front, and the initial code
has been merged into the upstream ltrace repository. One point of confusion for
me was the difference between ltrace's various filtering commandline options
-x
, -e
, -l
and -L
. I added a more thorough description and an example to
the ltrace manpage, and I'm discussing this here.
The ltrace filters specify which functions should be instrumented. Since ltrace introduces non-negligible overhead to the running process, it's very desirable to instrument only the functions you care about. Otherwise the process can be slowed significantly.
Broadly speaking
-x
is show me what calls these symbols (including local calls)-e
is show me what calls these symbols (inter-library calls only)-l
is show me what calls into this library
Inter-library and intra-library calls are treated separately because they are implemented differently in the binary. Calls into a shared object use the PLT mechanism, while local calls do not.
If no -e
or -l
filtering options are given, ltrace assumes a default filter
of -e @MAIN
(trace all non-local calls into the main executable). If only a
-x
is given, this default filter is still present, and it can be turned off
by passing -L
. In my experience, if you're using -x
, you pretty much always
want -L
as well.
Example
Suppose I have a library defined with this header tstlib.h
:
#pragma once void func_f_lib(void); void func_g_lib(void);
and this implementation tstlib.c
:
#include "tstlib.h" void func_f_lib(void) { func_g_lib(); } void func_g_lib(void) { }
Note that func_f_lib()
and func_g_lib()
are both external symbols. I have an
executable that uses this library defined like this tst.c
:
#include "tstlib.h" void func_f_main(void) { } void main(void) { func_f_main(); func_f_lib(); }
Note that func_f_main()
is an external symbol as well.
If linking with -Bdynamic
(the default for pretty much everybody), the
internal func_g_lib()
and func_g_main()
calls use the PLT like external
calls, and thus ltrace says:
$ gcc -Wl,-Bdynamic -shared -fPIC -g -o tstlib.so tstlib.c $ gcc -Wl,-rpath=$PWD -g -o tst tst.c tstlib.so $ ltrace -x 'func*' -L ./tst func_f_main() = <void> func_f_lib@tstlib.so( <unfinished ...> func_g_lib@tstlib.so() = <void> <... func_f_lib resumed> ) = <void> +++ exited (status 163) +++ $ ltrace -e 'func*' ./tst tst->func_f_lib( <unfinished ...> tstlib.so->func_g_lib() = <void> <... func_f_lib resumed> ) = <void> +++ exited (status 163) +++ $ ltrace -l tstlib.so ./tst tst->func_f_lib( <unfinished ...> tstlib.so->func_g_lib() = <void> <... func_f_lib resumed> ) = <void> +++ exited (status 163) +++
By contrast, if linking the shared library with -Bsymbolic
, then the internal
func_g_lib()
call bypasses the PLT, and ltrace says:
$ gcc -Wl,-Bsymbolic -shared -fPIC -g -o tstlib.so tstlib.c $ gcc -Wl,-rpath=$PWD -g -o tst tst.c tstlib.so $ ltrace -x 'func*' -L ./tst func_f_main() = <void> func_f_lib@tstlib.so( <unfinished ...> func_g_lib@tstlib.so() = <void> <... func_f_lib resumed> ) = <void> +++ exited (status 163) +++ $ ltrace -e 'func*' ./tst tst->func_f_lib() = <void> +++ exited (status 163) +++ $ ltrace -l tstlib.so ./tst tst->func_f_lib() = <void> +++ exited (status 163) +++
Note that the -x
traces are the same in both cases, since -x
traces local
and external calls. However -e
and -l
trace only external calls, so with
-Bsymbolic
, local calls to func_g_lib()
do not appear there.