不同发行版本下面的ltrace还是存在一些差异,同样使用的是0.7.3版本的ltrace,在ubuntu18及以下,执行ltrace ls 会显示一大堆内容,而在ubuntu20与ubuntu22则基本无内容显示.如下:
ltrace ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
+++ exited (status 0) +++
在ubuntu18.04上运行,则会出现这样的结果.
ltrace ls
__libc_start_main(0x5661e5c0, 1, 0xfff143c4, 0x566326d0 <unfinished ...>
strrchr("ls", '/') = nil
setlocale(LC_ALL, "") = "C"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils")
...
fflush(0xf7ec7ce0) = 0
fclose(0xf7ec7ce0) = 0
+++ exited (status 0) +++
而ltrace的版本信息都是V0.7.3
ltrace -V
ltrace version 0.7.3.
Copyright (C) 1997-2009 Juan Cespedes <cespedes@debian.org>.
This is free software; see the GNU General Public Licence
version 2 or later for copying conditions. There is NO warranty.
这些都不是重点,重点是知道是怎么样用的就好.要让显示一致,就在ubuntu18及以下的命令运行时,使用ltrace -L 有时可以达到相同的显示效果,可以试试看 ltrace -L -x "getenv" ls -lh
ltrace -x "getenv" ls
getenv@libc.so.6("LOCPATH") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_IDENTIFICATION") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_MEASUREMENT") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_TELEPHONE") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
...
注意事项:
ltrace 不能用于静态编译链接的程序,只能用于动态库链接的程序.他的作用是Trace library calls of a given program.如果都没有library,你可以需要的是strace.
典型的就是你想去ltrace一个go编译的程序的时候,出现下面这个东西.
Couldn't find .dynsym or .dynstr in "/proc/3596/exe"
哪很有可能就是,你搞错了要trace的对象了.
常用的使用方式:
- strace/ltrace 直接加上指令,如: ltrace ls -l。
- strace/ltrace 加上-o 選項的參數而導到某個檔案,如: ltrace -o strace.out ls -l。
- strace/ltrace 加上-p 選項的參數(Process ID)來觀測某個process的system call/library API,如: ltrace -p 12345。
ltrace常用的参数:
-l 只打印某个库中的调用。
-L 不打印库调用。
-o, --output=file 把输出定向到文件。
-p PID 附着在值为PID的进程号上进行ltrace。
-r 打印相对时间戳。
-s STRLEN 设置打印的字符串最大长度。
-S 显示系统调用。
-t, -tt, -ttt 打印绝对时间戳。
-T 输出每个调用过程的时间开销。
-x 显示指定的函数名,可以用透配符,个人感觉这个好使.
ltrace 把输出保存到文件
root@bf9c8c24edc5:/# ltrace -o /tmp/debug.log -x "gete*" ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@bf9c8c24edc5:/# cat /tmp/debug.log
getenv@libc.so.6("LOCPATH") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_IDENTIFICATION") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_MEASUREMENT") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_TELEPHONE") = nil
getenv@libc.so.6("LANG") = nil
getenv@libc.so.6("LC_ALL") = nil
getenv@libc.so.6("LC_ADDRESS") = nil
ltrace 设置打印的长度,默认好像是32字节
ltrace -o /tmp/debug.log -x 'write' ping -c 4 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=9.12 ms
64 bytes from 223.5.5.5: icmp_seq=2 ttl=114 time=6.52 ms
64 bytes from 223.5.5.5: icmp_seq=3 ttl=114 time=6.57 ms
64 bytes from 223.5.5.5: icmp_seq=4 ttl=114 time=6.26 ms
--- 223.5.5.5 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 6.262/7.118/9.117/1.160 ms
root@bf9c8c24edc5:/# cat /tmp/debug.log
write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84"..., 49) = 49
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_se"..., 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_se"..., 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_se"..., 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_se"..., 57) = 57
write@libc.so.6(1, "\n", 1) = 1
write@libc.so.6(1, "--- 223.5.5.5 ping statistics --"..., 34) = 34
write@libc.so.6(1, "4 packets transmitted, 4 receive"..., 63) = 63
write@libc.so.6(1, "rtt min/avg/max/mdev = 6.262/7.1"..., 50) = 50
+++ exited (status 0) +++
看上面的/tmp/debug.log中,write的数据就没有显示完成,加上-S参数来显示完成试试看.
ltrace -o /tmp/debug.log -x 'write' -s 64 ping -c 4 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.27 ms
64 bytes from 223.5.5.5: icmp_seq=2 ttl=114 time=6.09 ms
64 bytes from 223.5.5.5: icmp_seq=3 ttl=114 time=6.33 ms
64 bytes from 223.5.5.5: icmp_seq=4 ttl=114 time=6.52 ms
--- 223.5.5.5 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 6.091/6.300/6.519/0.152 ms
root@bf9c8c24edc5:/# cat /tmp/debug.log
write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.\n", 49) = 49
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.27 ms\n", 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=2 ttl=114 time=6.09 ms\n", 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=3 ttl=114 time=6.33 ms\n", 57) = 57
write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=4 ttl=114 time=6.52 ms\n", 57) = 57
write@libc.so.6(1, "\n", 1) = 1
write@libc.so.6(1, "--- 223.5.5.5 ping statistics ---\n", 34) = 34
write@libc.so.6(1, "4 packets transmitted, 4 received, 0% packet loss, time 3006ms\n", 63) = 63
write@libc.so.6(1, "rtt min/avg/max/mdev = 6.091/6.300/6.519/0.152 ms\n", 50) = 50
+++ exited (status 0) +++
ltrace 加上时间
时间参数就三个,-t, -tt , -ttt,-t就只有时分秒,-tt时分秒带微秒,-ttt就是unixtime带微秒.看结果.
root@bf9c8c24edc5:/# ltrace -o /tmp/debug1.log -x 'write' -s 64 -t ping -c 1 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=7.61 ms
--- 223.5.5.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 7.613/7.613/7.613/0.000 ms
root@bf9c8c24edc5:/# ltrace -o /tmp/debug2.log -x 'write' -s 64 -tt ping -c 1 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.22 ms
--- 223.5.5.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.220/6.220/6.220/0.000 ms
root@bf9c8c24edc5:/# ltrace -o /tmp/debug3.log -x 'write' -s 64 -ttt ping -c 1 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.25 ms
--- 223.5.5.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.253/6.253/6.253/0.000 ms
root@bf9c8c24edc5:/# cat /tmp/debug1.log
02:34:59 write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.\n", 49) = 49
02:34:59 write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=7.61 ms\n", 57) = 57
02:34:59 write@libc.so.6(1, "\n", 1) = 1
02:34:59 write@libc.so.6(1, "--- 223.5.5.5 ping statistics ---\n", 34) = 34
02:34:59 write@libc.so.6(1, "1 packets transmitted, 1 received, 0% packet loss, time 0ms\n", 60) = 60
02:34:59 write@libc.so.6(1, "rtt min/avg/max/mdev = 7.613/7.613/7.613/0.000 ms\n", 50) = 50
02:34:59 +++ exited (status 0) +++
root@bf9c8c24edc5:/# cat /tmp/debug2.log
02:35:04.164310 write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.\n", 49) = 49
02:35:04.170973 write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.22 ms\n", 57) = 57
02:35:04.171221 write@libc.so.6(1, "\n", 1) = 1
02:35:04.171343 write@libc.so.6(1, "--- 223.5.5.5 ping statistics ---\n", 34) = 34
02:35:04.171504 write@libc.so.6(1, "1 packets transmitted, 1 received, 0% packet loss, time 0ms\n", 60) = 60
02:35:04.171697 write@libc.so.6(1, "rtt min/avg/max/mdev = 6.220/6.220/6.220/0.000 ms\n", 50) = 50
02:35:04.172049 +++ exited (status 0) +++
root@bf9c8c24edc5:/# cat /tmp/debug3.log
1687142111.348984 write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.\n", 49) = 49
1687142111.355684 write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.25 ms\n", 57) = 57
1687142111.355885 write@libc.so.6(1, "\n", 1) = 1
1687142111.355985 write@libc.so.6(1, "--- 223.5.5.5 ping statistics ---\n", 34) = 34
1687142111.356126 write@libc.so.6(1, "1 packets transmitted, 1 received, 0% packet loss, time 0ms\n", 60) = 60
1687142111.356314 write@libc.so.6(1, "rtt min/avg/max/mdev = 6.253/6.253/6.253/0.000 ms\n", 50) = 50
1687142111.356666 +++ exited (status 0) +++
ltrace 显示每个调用所花的绝对时间就加上-T参数,结果在最右边尖括号内.
ltrace -o /tmp/debug.log -x 'write' -s 64 -ttt -T ping -c 1 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.34 ms
--- 223.5.5.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.343/6.343/6.343/0.000 ms
root@bf9c8c24edc5:/# cat /tmp/debug.log
1687142203.255801 write@libc.so.6(1, "PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.\n", 49) = 49 <0.000193>
1687142203.262657 write@libc.so.6(1, "64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=6.34 ms\n", 57) = 57 <0.000247>
1687142203.262946 write@libc.so.6(1, "\n", 1) = 1 <0.000103>
1687142203.263084 write@libc.so.6(1, "--- 223.5.5.5 ping statistics ---\n", 34) = 34 <0.000124>
1687142203.263241 write@libc.so.6(1, "1 packets transmitted, 1 received, 0% packet loss, time 0ms\n", 60) = 60 <0.000158>
1687142203.263425 write@libc.so.6(1, "rtt min/avg/max/mdev = 6.343/6.343/6.343/0.000 ms\n", 50) = 50 <0.000142>
1687142203.263779 +++ exited (status 0) +++
ltrace 显示系统调用就加-S参数
ltrace -o /tmp/debug.log -x 'write' -s 64 -ttt -T -S ping -c 1 223.5.5.5
PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data.
64 bytes from 223.5.5.5: icmp_seq=1 ttl=114 time=5.94 ms
--- 223.5.5.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 5.940/5.940/5.940/0.000 ms
root@bf9c8c24edc5:/# cat /tmp/debug.log
1687142356.444336 SYS_brk(0) = 0x56108d657000 <0.000140>
1687142356.444611 SYS_arch_prctl(0x3001, 0x7ffea0f00a60, 0x7ff8bd4862d0, 13) = -22 <0.000116>
1687142356.444924 SYS_access("/etc/ld.so.preload", 04) = -2 <0.000296>
1687142356.445362 SYS_openat(0xffffff9c, 0x7ff8bd48fb80, 0x80000, 0) = 3 <0.000114>
1687142356.445548 SYS_fstat(3, 0x7ffea0effc60) = 0 <0.000045>
1687142356.445637 SYS_mmap(0, 6320, 1, 2) = 0x7ff8bd468000 <0.000075>
1687142356.445778 SYS_close(3) = 0 <0.000065>
...
ltrace 跟踪原理
ltrace的功能是能够跟踪进程的库函数调用,它是如何实现的呢?
ltrace其实也是基于ptrace。我们知道,ptrace能够主要是用来跟踪系统调用,那么它是如何跟踪库函数呢?
首先ltrace打开elf文件,对其进行分析。在elf文件中,出于动态连接的需要,需要在elf文件中保存函数的符号,供连接器使用。具体格式,大家可以参考elf文件的格式。
这样ltrace就能够获得该文件中,所有系统调用的符号,以及对应的执行指令。然后,ltrace将该指令所对应的4个字节,替换成断点。其实现大家可以参考Playing with ptrace, Part II。
这样在进程执行到相应的库函数后,就可以通知到了ltrace,ltrace将对应的库函数打印出来之后,继续执行子进程。
实际上ltrace与strace使用的技术大体相同,但ltrace在对支持fork和clone方面,不如strace。strace在收到frok和clone等系统调用后,做了相应的处理,而ltrace没有。
ltrace 与 strace 的异同
ltrace能够跟踪进程的库函数调用,它会显现出哪个库函数被调用,而strace则是跟踪程序的每个系统调用.ltrace比strace更费时.
strace -o /tmp/debug.log -s 64 -ttt -T -e 'write' echo "hello world"
hello world
root@bf9c8c24edc5:/# cat /tmp/debug.log
1687142834.793675 write(1, "hello world\n", 12) = 12 <0.000010>
1687142834.793840 +++ exited with 0 +++
root@bf9c8c24edc5:/#
root@bf9c8c24edc5:/#
root@bf9c8c24edc5:/# ltrace -o /tmp/debug.log -s 64 -ttt -T -x 'write' echo "hello world"
hello world
root@bf9c8c24edc5:/# cat /tmp/debug.log
1687142847.965263 write@libc.so.6(1, "hello world\n", 12) = 12 <0.000124>
1687142847.965520 +++ exited (status 0) +++
最后:
使用ltrace最好不要在生产系统上面搞,会阻塞住当前线程,如果是多线程程序,可能出现各种奇怪的问题.
参考:
https://www.cnblogs.com/machangwei-8/p/10388938.html
https://zhuanlan.zhihu.com/p/107063011
https://blog.csdn.net/fantasy_ARM9/article/details/115892633
https://man7.org/linux/man-pages/man1/ltrace.1.html