22

As the title says, ltrace does not work properly on my system. It shows no output in most cases, like

$ltrace ls
[usual ls output]
+++ exited (status 0) +++

$gcc hello.c
$ltrace ./a.out
Hello world!
+++ exited (status 0) +++

I'm using the latest ltrace version (from package 0.7.3-5.1ubuntu4), I even tried recompiling from source with no difference. I'm using Ubuntu 16.10, kernel 4.8.0-42-generic. gcc version is 6.2.0.

Weird thing is, binaries downloaded from the Internet seem to work, correctly displaying the library calls.

What am I missing? Is anyone able to reproduce the issue?

0x5C91
  • 3,360
  • 3
  • 31
  • 46
  • I can reproduce on 16.10, where gcc has been configured to produce PIE executables by default. According to its changelog, ltrace has supported PIE executables since 0.7.0, so I'm not sure why version 0.7.3 doesn't work. Workaround is to compile your programs with `--no-pie`, although that reduces security a bit (ASLR will still be enabled for shared libraries, but not for the executable). – Mark Plotnick Apr 04 '17 at 22:06
  • 1
    @MarkPlotnick I tried to compile with `--no-pie`, unfortunately... `--no-luck` :) still the same problem. Even if it worked, I still wouldn't understand why /bin/ls & Co. aren't traced. I posted to the ltrace-devel mailing list since you were able to reproduce, let's see if someone can figure out this weird problem. – 0x5C91 Apr 05 '17 at 10:35
  • @MarkPlotnick I was mistaken. `--no-pie` alone was giving me error `relocation R_X86_64_32 against .rodata can not be used when making a shared object; recompile with -fPIC`, so I thought I would just recompile with `-fPIC` as suggested... too bad that still produces a PIE. So, I guess your explanation is right, I checked with the command `hardening-check` and all the untraceable binaries are PIE; it seems this is not related only to gcc 6, as I tried to compile with gcc 5 and no options (PIE produced). Also I'm not sure of how my system binaries were compiled. – 0x5C91 Apr 06 '17 at 13:51

3 Answers3

25

This may have to do with binaries being compiled with -z now. I created a quick test program (I'm using Ubuntu 16.04):

int main() {
  write(0, "hello\n", 6);
  return 0;
}

If I compile it with gcc -O2 test.c -o test then ltrace works:

$ ltrace ./test 
__libc_start_main(0x400430, 1, 0x7ffc12326528, 0x400550 <unfinished ...>
write(0, "hello\n", 6hello
)                                                              = 6
+++ exited (status 0) +++

However when I compile with gcc -O2 test.c -Wl,-z,relro -Wl,-z,now -o test2 then it doesn't:

$ ltrace ./test2 
hello
+++ exited (status 0) +++

You can check if a binary was compiled like so using scanelf from the pax-utils package on Ubuntu:

$ scanelf -a test*
 TYPE    PAX   PERM ENDIAN STK/REL/PTL TEXTREL RPATH BIND FILE 
ET_EXEC PeMRxS 0775 LE RW- R-- RW-    -      -   LAZY test 
ET_EXEC PeMRxS 0775 LE RW- R-- RW-    -      -   NOW test2

Note the LAZY (ltrace works) versus NOW (ltrace doesn't).

There is a little bit more discussion (but no resolution) here:

https://bugzilla.redhat.com/show_bug.cgi?id=1333481

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Doug Hoyte
  • 406
  • 4
  • 3
  • 3
    Looks to me like this is unrelated to `relro`, and actually just `now` vs. `lazy` that makes the difference. – Joseph Sible-Reinstate Monica Apr 22 '20 at 15:53
  • 2
    Can confirm what @JosephSible-ReinstateMonica said, this is related to just `now` vs `lazy`. – Sumit Ghosh Apr 23 '20 at 02:09
  • 1
    Thanks for looking into this @JosephSible-ReinstateMonica and SkullTech. I'm just used to using both "now" and "relro" together all the time since the main reason you use "now" is so that you can use "relro". I didn't think to try them separately. – Doug Hoyte Apr 30 '20 at 16:27
  • I just notice this may also be something else too: some executable seems to finally be a shared library. `ltrace` does not work on `evince` while it works on `eog`. `scanelf` says `envince` is `ET_DYN` while `eof` is `ET_EXEC`. By the way, `file` too, says `evince` is an ELF shared object, not an ELF executable. That said, `evince` is also linked `NOW`. – Hibou57 Jun 03 '20 at 14:23
  • @Hibou57 ltrace on an `ET_DYN` executable with `lazy` works fine. – Gene Vincent Nov 10 '20 at 19:29
  • In my case, ltrace works for `/bin/ls` on one system and doesn't work on another. Both of those executables use `NOW` and are `ET_DYN` executables. – user3207874 Oct 15 '21 at 08:37
8

Short answer is ltrace doesn't behave as expected due to the Object File Type and Position Independent Executable of the binary being traced. If the binary being traced has an ET_EXEC Object File Type and no Position Independent Executable, then ltrace will be able to intercept and record the dynamic library calls.

For the Object File Type (ET_EXEC, ET_DYN, etc.) of a binary please refer to the ELF header (0x10 offset).

To verify this I'll be using the same test.c program from the previous answer:

#include <unistd.h>
int main() {
  write(0, "hello\n", 6);
  return 0;
}

My system according to /etc/os-release:

NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"

Default gcc -v (version and config):

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-17ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-HskZEa/gcc-9-9.3.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)

Now compiling with gcc test.c -o test, we have:

$ ltrace ./test
hello
+++ exited (status 0) +++

Checking Object File Type of the binary with readelf -h ./test | grep Type:

  Type:                              DYN (Shared object file)

Checking Position Independent Executable with hardening-check ./test | grep Position:

 Position Independent Executable: yes

However compiling with gcc -no-pie test.c -o test, we have:

$ ltrace ./test
write(0, "hello\n", 6hello
)                                                            = 6
+++ exited (status 0) +++

Checking Object File Type of the binary with readelf -h ./test | grep Type:

  Type:                              EXEC (Executable file)

Checking Position Independent Executable with hardening-check ./test | grep Position:

 Position Independent Executable: no, normal executable!

Hope this helps clarifying the behavior of ltrace and its relation with the Object File Type and Position Independent Executable of the binary been traced.

tink
  • 14,342
  • 4
  • 46
  • 50
plche
  • 83
  • 1
  • 5
4

Latest version 0.7.91 built from source https://gitlab.com/cespedes/ltrace seems to work again

Remove installed 0.7.3 version

$ sudo apt remove ltrace

Make & install

$ git clone https://gitlab.com/cespedes/ltrace.git
$ cd ltrace
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

Test

$ ltrace --version
ltrace 0.7.91
...
$ ltrace echo
getenv("POSIXLY_CORRECT")                                  = nil
strrchr("echo", '/')                                       = nil
setlocale(LC_ALL, "")                                      = "en_US.UTF-8"
...
fflush(0x7f6afa7426a0)                                     = 0
fclose(0x7f6afa7426a0)                                     = 0
+++ exited (status 0) +++




ten0s
  • 839
  • 8
  • 11
  • On my system, `autogen.sh` required `sudo apt install libelf-dev`; `make check` required `sudo apt install dejagnu`; `make check` ran but some tests failed; installed `ltrace` 0.7.91 to `/usr/local/bin` anyway: `./configure --prefix=/usr/local/`, without `sudo apt remove ltrace` – knb Jan 10 '23 at 12:21
  • `sudo apt install autoconf libtool` before – Tinmarino Apr 17 '23 at 00:56