Ubuntu 22.04 ltrace 安装与命令参数详解

详解 ltrace 库函数调用跟踪工具,覆盖全部参数在 Ubuntu 22.04 上的真实输出与 PLT、stderr 等实测要点

Linux 系统监控与诊断ltraceUbuntu 22.042026/10/06

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 的 -r CentOS 版实测或复用 -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 字节截断处显示 ...
-CC++ 符号反修饰C 程序无效果
-e help过滤表达式用法需配合表达式,单独用报错

八、卸载方式

apt-get remove -y ltrace

如需连残留配置一起清除:

apt-get purge -y ltrace