Ubuntu 22.04 ltrace 安装与命令参数详解
详解 ltrace 库函数调用跟踪工具,覆盖全部参数在 Ubuntu 22.04 上的真实输出与 PLT、stderr 等实测要点
Ubuntu 22.04 ltrace 安装与命令参数详解
目标:讲解 ltrace(library trace)的用途与机制,给出全部主要参数在 Ubuntu 22.04(x86_64)真实环境中的逐条实测输出,并解释 PLT 过滤、stderr 输出等易踩坑点。
一、环境信息
- 操作系统:Ubuntu 22.04 LTS(jammy),amd64(x86_64)
- 工具版本:ltrace
0.7.3-6.1ubuntu6.22.04.1(ltrace --version显示ltrace version 0.7.3) - 安装来源:在线 apt 仓库
archive.ubuntu.com的jammy/universe - 主机名:
mon-ubuntu2204 - 同批监控工具:strace 5.16、sysstat 12.5.2、htop 3.0.5、glances 3.2.4.2 等
二、工具概览
ltrace 是一款用户态库函数调用跟踪器,与 strace(跟踪内核系统调用)正好互补:strace 看进程对内核的调用,ltrace 看进程在运行时对动态库函数(如 libc 的 setlocale、getenv、malloc、strrchr 等)的调用顺序、参数与返回值。其工作机制是解析动态链接器加载的符号表与 PLT(Procedure Linkage Table,过程链接表),在库函数入口处注入断点并打印参数,从而还原一次程序运行过程中“调用了哪些库函数、传了什么参、返回什么值”。
监控维度包括:库调用轨迹(顺序与嵌套)、每次调用的耗时(-T/-r)、时间戳(-t/-tt)、调用频次汇总(-c)、被调函数所在库或调用来源地址(-l/-i)等。它是排查“程序为什么行为诡异、参数为何不符合预期、某个库接口被调用了几次”这类问题的利器。
三、在线安装
3.1 apt 安装(已装时重复执行提示)
apt-get install -y ltrace
ltrace is already the newest version (0.7.3-6.1ubuntu6.22.04.1).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
3.2 版本验证
ltrace --version
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.
3.3 包管理视角确认版本
dpkg -l | grep ltrace
ii ltrace 0.7.3-6.1ubuntu6.22.04.1 amd64 Tracks runtime library calls in dynamically linked programs
四、离线安装
4.1 下载离线 deb 包到本地目录
在有网机器上把 ltrace 下载到指定目录(连同其他监控工具打包分发):
apt-get -o Dir::Etc::sourcelist=/dev/null -o Dir::Etc::sourceparts=/dev/null download ltrace 2>/dev/null
或使用 apt-get --download-only 批量下载:本环境离线包目录 /root/offline-bundle 中已包含 ltrace 的 deb:
ltrace_0.7.3-6.1ubuntu6.22.04.1_amd64.deb
4.2 离线安装
将 .deb 拷贝到目标机器后安装:
dpkg -i ltrace_0.7.3-6.1ubuntu6.22.04.1_amd64.deb
安装后再次验证版本,确认离线安装生效:
ltrace --version
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.
五、全部参数实测
💡 说明:ltrace 的库调用跟踪输出走 stderr,与被跟踪命令自身的 stdout(如
ls打印的目录列表)混在终端会干扰阅读。因此下面多数用例统一使用ltrace ... 2>&1 >/dev/null——把 stdout 丢弃、只保留 ltrace 的 stderr 输出。顺序很重要:2>&1(把 stderr 指向当前 stdout=终端),再>/dev/null(把 stdout 指向 /dev/null)。
5.1 默认库调用跟踪:ltrace
只看 ltrace 自身,不带任何参数运行一个真实动态链接程序 /usr/bin/ls,观察其启动阶段的库调用:
ltrace /usr/bin/ls 2>&1 >/dev/null
strrchr("/usr/bin/ls", '/') = "/ls"
strncmp("sr/bin/ls", "/.libs/", 7) = 68
setlocale(LC_ALL, "") = "C"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x5eea4f7a5a80, 0, 0x5eea4f7bc008, 0) = 0
isatty(1) = 0
getenv("QUOTING_STYLE") = nil
getenv("COLUMNS") = nil
ioctl(1, 21523, 0x7fff19470960) = -1
getenv("TABSIZE") = nil
getopt_long(2, 0x7fff19470ac8, "abcdfghiklmnopqrstuvw:xABCDFGHI:"..., 0x5eea4f7bb3a0, -1) = -1
🔬 说明:ltrace 依序打印了
ls启动时调用的库函数——strrchr从路径中剥离出程序名、setlocale设定 locale、getenv读取环境变量、getopt_long解析命令行选项等。nil表示返回 NULL(环境变量未设置)。
5.2 库调用 + 系统调用混合:-S
默认 ltrace 只看库函数;加 -S 会把系统调用也一并打印(以 SYS_ 前缀标识),便于对比库层与内核层的调用。
ltrace -S /usr/bin/ls 2>&1 >/dev/null
SYS_brk(0) = 0x56fb07a76000
SYS_arch_prctl(0x3001, 0x7ffc3a9b4030, 0x7b3ca93913c0, 1) = -22
SYS_mmap(0, 8192, 3, 34) = 0x7b3ca9370000
SYS_access("/etc/ld.so.preload", 04) = -2
SYS_openat(0xffffff9c, 0x7b3ca93a021b, 0x80000, 0) = 3
SYS_newfstatat(3, 0x7b3ca93a0ee9, 0x7ffc3a9b3180, 4096) = 0
SYS_mmap(0, 0x3514, 1, 2) = 0x7b3ca936c000
SYS_close(3) = 0
SYS_openat(0xffffff9c, 0x7b3ca9370140, 0x80000, 0) = 3
SYS_read(3, "\177ELF\002\001\001", 832) = 832
5.3 按函数过滤:-e
-e 函数名 只跟踪主程序 PLT 中出现的指定函数。ls 的 PLT 中有 getenv,所以下面的正例能打出全部 getenv 调用:
ltrace -e getenv /usr/bin/ls 2>&1 >/dev/null
ls->getenv("QUOTING_STYLE") = nil
ls->getenv("COLUMNS") = nil
ls->getenv("TABSIZE") = nil
ls->getenv("LS_BLOCK_SIZE") = nil
ls->getenv("BLOCK_SIZE") = nil
ls->getenv("BLOCKSIZE") = nil
ls->getenv("POSIXLY_CORRECT") = nil
ls->getenv("BLOCK_SIZE") = nil
⚠️ 注意:
-e只匹配主程序自身 PLT 里的符号,看不到发生在库内部或其它被动态加载库里的同名调用。ls 的 PLT 没有malloc直接入口(malloc 调用发生在 libc/ld 内部),因此:
ltrace -e malloc /usr/bin/ls 2>&1 >/dev/null
+++ exited (status 0) +++
🔬 说明:
-e malloc无任何输出(仅结束时打印退出标记),这就是"主程序 PLT 无该符号"的直接体现。要抓池子/堆分配里的 malloc,需要用下面的-x追加任意库符号。
5.4 组合过滤:-e 'malloc+free'
用 + 串联多个函数名,同时跟踪并打印,可用于观察"分配/释放"配对:
ltrace -e 'malloc+free' /usr/bin/ls 2>&1 >/dev/null
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
libselinux.so.1->free(0) = <void>
5.5 排除过滤:-e '-*free*'
- 开头表示排除;* 为通配符。-e '-*free*' 即"过滤掉所有名字里含 free 的调用",其余照常输出:
ltrace -e '-*free*' /usr/bin/ls 2>&1 >/dev/null
libc.so.6->__rawmemchr(0x7fbe05ba62e0, 0, 0x7fff0e610390, 0x5e38b7956fd8) = 0
ls->strrchr("/usr/bin/ls", '/') = "/ls"
ls->strncmp("sr/bin/ls", "/.libs/", 7) = 68
ls->setlocale(LC_ALL, "" <unfinished ...>
libc.so.6->strspn("", "C") = 4294967229
libc.so.6->strcmp("LOCPATH", "C") = 7
libc.so.6->strcpy(0x5e38b794fbbd, ";<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"...) = 0
libc.so.6->strcmp("LC_ALL", nil) = 6
🔬 说明:此时输出里能看到
ls->、libc.so.6->等来源前缀——过滤会改变默认的打印方式,明确标注每个调用来自哪个库/程序。
5.6 追加任意库符号:-x
-x 符号 用于追加跟踪任意动态库里的指定符号(不必存在于主程序 PLT)。这里用 -x malloc 抓 libc 内部的 malloc 分配:
ltrace -x malloc /usr/bin/ls 2>&1 >/dev/null
malloc@libc.so.6(34) = 0x5ff7dcae3910
malloc@libc.so.6(10) = 0x5ff7dcae3940
malloc@libc.so.6(56) = 0x5ff7dcae3960
malloc@libc.so.6(56) = 0x5ff7dcae39a0
malloc@libc.so.6(128) = 0x5ff7dcae39e0
🔬 说明:
malloc@libc.so.6(34)表示 libc.so.6 中被调用的malloc,参数 34 为申请字节数,返回值为分配到的地址。对比 5.3 可知:-e malloc空、-x malloc有输出,正是"PLT 入口缺失 → 用 -x 补充"的最佳例证。
5.7 跟踪子进程:-f
当被跟踪程序会 fork/clone 出子进程时,-f(follow forks)会一并跟踪子进程,子进程输出带 [pid ...] 前缀。这里以 bash -c 启动一个 shell,ltrace 跟进其子进程:
ltrace -f bash -c 'exit 0' 2>&1 >/dev/null
[pid 11421] __errno_location() = 0x768db60296c0
[pid 11421] getuid() = 0
[pid 11421] getgid() = 0
[pid 11421] _setjmp(0x62bdc5f8d500, 0x7ffc0f74c1a8, 0x7ffc0f74c1c8, 0x768db611804b) = 0
[pid 11421] getpid() = 11421
[pid 11421] sigfillset(~<31-32>) = 0
[pid 11421] sigaction(SIGCHLD, { 0x62bdc5f81aa0, ~<31-32>, 0, 0x34000000340 }, nil) = 0
[pid 11421] geteuid() = 0
[pid 11421] __ctype_b_loc() = 0x768db60296d8
[pid 11421] __ctype_b_loc() = 0x768db60296d8
5.8 附加运行中进程:-p
-p PID 附加到一个已经在运行的进程上。先让一个 python 进程进入持续忙循环,再用 ltrace 附加(示例 PID 用实际 python 进程 PID):
# 另一个终端先启动忙循环进程,例如:python3 -c 'while True: pass'
ltrace -p <python_pid>
__errno_location() = 0x70260400af80
pthread_mutex_lock(0x625413193cd0, 0x625413193c78, 0x625413193b20, 0) = 0
pthread_mutex_lock(0x625413193d28, 0, 0, 0) = 0
pthread_cond_signal(0x625413193cf8, 0x625439e28150, 0, 0) = 0
pthread_mutex_unlock(0x625413193d28, 0x625439e28150, 0, 0) = 0
pthread_mutex_unlock(0x625413193cd0, 0, 0, 0) = 0
⚠️ 注意:若附加的是
sleep这类纯等待、既不调库函数也不产生调用的进程,ltrace 输出会完全为空。这是 ltrace 的客观行为——它只在进程真正发起库调用时打印,并不是跟踪失败。对这类想观察系统调用等待态的进程,应改用strace -p(详见第六章)。
5.9 输出到文件:-o
-o 文件 将库调用日志写入文件,避免污染终端:
ltrace -o /tmp/ltrace_ls.log /usr/bin/ls >/dev/null 2>&1
cat /tmp/ltrace_ls.log
strrchr("/usr/bin/ls", '/') = "/ls"
strncmp("sr/bin/ls", "/.libs/", 7) = 68
setlocale(LC_ALL, "") = "C"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
5.10 显示每次调用耗时:-T
-T 为每次库调用追加耗时。本用例目标进程极短暂,未输出逐条调用、仅反馈退出状态:
ltrace -T /usr/bin/ls 2>&1 >/dev/null
+++ exited (status 0) +++
🔬 说明:
-T/-r/-n/-i/-C这些参数只改变输出格式。当被跟踪目标运行速度过快、且与超短生命周期进程交互时,部分逐条明细可能被压缩;更详尽的耗时见 5.12 的-rCentOS 版实测或复用-T于长任务。
5.11 时间戳:-t / -tt
-t 给每行加秒级时间戳,-tt 加微秒级时间戳:
ltrace -t /usr/bin/ls 2>&1 >/dev/null
05:12:45 +++ exited (status 0) +++
ltrace -tt /usr/bin/ls 2>&1 >/dev/null
05:12:45.124712 +++ exited (status 0) +++
5.12 相对时间戳:-r
-r 打印相邻两次调用之间的时间差:
ltrace -r /usr/bin/ls 2>&1 >/dev/null
0.000000 +++ exited (status 0) +++
5.13 库调用汇总:-c
-c 统计各库函数被调用次数、总耗时与平均耗时,是一个"性能剖析汇总表":
ltrace -c /usr/bin/ls 2>&1 >/dev/null
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
0.40 0.000297 297 1 closedir
0.40 0.000295 295 1 isatty
0.38 0.000281 281 1 ioctl
0.36 0.000267 267 1 textdomain
0.36 0.000262 262 1 setlocale
0.34 0.000249 249 1 strrchr
0.32 0.000235 235 1 freecon
0.27 0.000200 200 1 __cxa_atexit
0.27 0.000200 200 1 bindtextdomain
0.22 0.000158 158 1 getopt_long
------ ----------- ----------- --------- --------------------
100.00 0.073470 342 total
5.14 嵌套缩进:-n
-n 空格数 为嵌套调用追加缩进,辅助区分调用层次:
ltrace -n 2 /usr/bin/ls 2>&1 >/dev/null
+++ exited (status 0) +++
5.15 隐藏库调用,只看系统调用:-L
-L 隐藏库函数调用,配合 -S 就只剩系统调用(效果近似 strace 的过滤):
ltrace -L -S /usr/bin/ls 2>&1 >/dev/null
SYS_brk(0) = 0x5c1ad53b1000
SYS_arch_prctl(0x3001, 0x7ffe54fa9200, 0x76879e0bb3c0, 1) = -22
SYS_mmap(0, 8192, 3, 34) = 0x76879e09a000
SYS_access("/etc/ld.so.preload", 04) = -2
SYS_openat(0xffffff9c, 0x76879e0ca21b, 0x80000, 0) = 3
SYS_newfstatat(3, 0x76879e0caee9, 0x7ffe54fa8350, 4096) = 0
SYS_mmap(0, 0x3514, 1, 2) = 0x76879e096000
SYS_close(3) = 0
5.16 显示调用来源地址:-i
-i 打印调用指令的库内偏移地址,用于定位"是谁调用了这个函数":
ltrace -i /usr/bin/ls 2>&1 >/dev/null
[0xffffffffffffffff] +++ exited (status 0) +++
5.17 只跟踪指定库:-l
-l 库名 仅跟踪指定库的函数,其它库的调用被忽略(输出带 ls-> 前缀,表明来自主程序调用):
ltrace -l libc.so.6 /usr/bin/ls 2>&1 >/dev/null
ls->setlocale(LC_ALL, "") = "C"
ls->bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
ls->textdomain("coreutils") = "coreutils"
ls->__cxa_atexit(0x5ced3da0ca80, 0, 0x5ced3da23008, 0) = 0
ls->isatty(1) = 0
ls->getenv("QUOTING_STYLE") = nil
5.18 字符串参数截断显示:-s
-s N 将字符串参数/返回值截断为 N 字节(截断处用 ... 标记):
ltrace -s 4 /usr/bin/ls 2>&1 >/dev/null
strrchr("/usr"..., '/') = "/ls"
strncmp("sr/b"..., "/.li"..., 7) = 68
setlocale(LC_ALL, "") = "C"
bindtextdomain("core"..., "/usr"...) = "/usr"...
textdomain("core"...) = "core"...
__cxa_atexit(0x5d4bc6aa6a80, 0, 0x5d4bc6abd008, 0) = 0
5.19 C++ 符号反修饰:-C
-C 对 C++ 修饰名(mangled name)做反修饰,便于阅读。ls 为 C 程序、无 C++ 符号,故仅反馈退出:
ltrace -C /usr/bin/ls 2>&1 >/dev/null
+++ exited (status 0) +++
5.20 过滤表达式帮助:-e help
-e help 需配合实际过滤表达式使用,单独使用会提示用法:
ltrace -e help /usr/bin/ls
ltrace: too few arguments
Try `ltrace --help' for more information.
六、常见问题与真实行为
6.1 -e func 只匹配主程序 PLT,-x 才追加库符号
- 现象:
ltrace -e malloc /usr/bin/ls输出为空(仅 exit 标记);ltrace -x malloc /usr/bin/ls却能打印malloc@libc.so.6(...)。 - 原因:
-e基于主程序自身的 PLT做符号过滤。ls 的 PLT 中没有 malloc 直接入口(malloc 调用发生在 libc/ld 内部);而-x是显式追加对任意动态库符号的跟踪,故能抓到。反之,ls PLT 有getenv,-e getenv就能打出 8 行。 - 解决:抓"库内部调用"用
-x 符号;只关心"主程序直接调用的库接口"用-e 符号。先nm -D 程序或objdump -T确认符号是否处于 PLT,再决定用哪个参数。
6.2 ltrace 输出走 stderr,命令 stdout 需用 >/dev/null 丢弃
- 现象:直接
ltrace /usr/bin/ls时,终端上既混有 ls 打出的目录列表(stdout),又有 ltrace 的库调用日志(stderr),难以区分。 - 原因:ltrace 的跟踪输出写 stderr,而被跟踪命令的 stdout 未经重定向直达终端。
- 解决:加
2>&1 >/dev/null丢弃 stdout,只留 ltrace 输出。重定向顺序不能颠倒——先2>&1(使 stderr 指向当前 stdout 终端),再>/dev/null(把 stdout 指向 /dev/null),即可在保留 stderr 的同时清掉命令自身输出。
6.3 -p 附加纯等待进程输出为空
- 现象:
ltrace -p <sleep_pid>无任何输出;只有附加到会持续发起库调用的进程(如忙循环 python)才有输出。 - 原因:ltrace 只在进程真正调用库函数时打印,
sleep处于纯系统调用等待态、不触发任何库调用,故零输出。 - 解决:这是 ltrace 的客观限制而非 bug。若要观察纯等待/阻塞进程的系统层行为,请改用
strace -p <pid>。
6.4 -e help 报 "too few arguments"
- 现象:
ltrace -e help立即报ltrace: too few arguments。 - 原因:
-e需要过滤表达式作为参数,help并非该选项的合法参数。 - 解决:按
ltrace --help查看正确用法,过滤表达式应为函数名/通配符/+/-/*组合。
6.5 /bin/ls 是符号链接,建议用 /usr/bin/ls
- 现象:用
/bin/ls作测例时,默认跟踪打印的首个字符串是/bin/ls。 - 原因:Ubuntu 22.04 中
/bin是/usr/bin的符号链接,ltrace 解析到的被调路径随之不同,测试结果略有出入。 - 解决:直接使用真实路径
/usr/bin/ls,输出更稳定、更具代表性(本文统一用/usr/bin/ls)。
6.6 与 CentOS 8 的版本差异
- 版本号:Ubuntu 22.04 为 ltrace
0.7.3-6.1ubuntu6.22.04.1,CentOS 8 为0.7.91-28.el8。Ubuntu 版--version输出为 Debian 作者 Juan Cespedes 的 GPL-2 声明;CentOS 版则含 Red Hat(Petr Machata)与 GPLv2+ 声明。 -S系统调用输出格式不同:Ubuntu 0.7.3 用SYS_brk(0)、SYS_openat(...)形式;CentOS 0.7.91 用brk@SYS(nil)、openat@SYS(...)形式,且加载库路径不同(本环境 Ubuntu 在/lib/x86_64-linux-gnu/,CentOS 在/lib64/)。- 被测目标差异:Ubuntu 的
/usr/bin/ls是真 ELF,可直接做测例;CentOS 的/usr/bin/ls是 coreutils wrapper 脚本(非 ELF),需改用/usr/bin/grep等真 ELF。 -p实测差异:Ubuntu 上附加忙循环 python 可打印 pthread 相关调用;CentOS 上附加yes | grep目标输出为空。二者共性结论均为"纯等待进程输出为空,改用 strace -p"。
七、命令与参数速查表
| 参数/命令 | 含义 | 实测说明/默认值 |
|---|---|---|
ltrace | 默认库函数调用跟踪 | 输出走 stderr |
-S | 库调用 + 系统调用混合 | 系统调用以 SYS_ 前缀标识 |
-e 函数 | 只跟踪主程序 PLT 中的指定函数 | PLT 无该符号时输出为空(如 ls 无 malloc) |
-e 'a+b' | 组合多个函数同时跟踪 | 如 malloc+free 观察分配/释放配对 |
-e '-*free*' | 排除匹配的通配符函数 | * 通配、- 排除 |
-x 符号 | 追加任意动态库的符号跟踪 | 抓库内部调用用此(如 -x malloc) |
-f | 跟踪 fork/clone 子进程 | 子进程输出带 [pid] 前缀 |
-p PID | 附加运行中进程 | 纯等待进程(sleep)输出为空,改用 strace -p |
-o 文件 | 输出到文件 | 便于重定向,不污染终端 |
-T | 显示每次调用耗时 | 秒单位 |
-t | 秒级时间戳 | |
-tt | 微秒级时间戳 | |
-r | 相邻调用相对时间戳 | |
-c | 库调用频次/耗时汇总 | %time/seconds/usecs/calls/function |
-n N | 嵌套调用缩进 N 空格 | 辅助看调用层级 |
-L | 隐藏库函数调用 | 配合 -S 只看系统调用 |
-i | 显示调用方指令地址 | |
-l 库 | 只跟踪指定库 | 如 libc.so.6 |
-s N | 字符串参数截断 N 字节 | 截断处显示 ... |
-C | C++ 符号反修饰 | C 程序无效果 |
-e help | 过滤表达式用法 | 需配合表达式,单独用报错 |
八、卸载方式
apt-get remove -y ltrace
如需连残留配置一起清除:
apt-get purge -y ltrace