[转帖][译] ltrace 是如何工作的(2016)

ltrace,如何,工作 · 浏览次数 : 0

小编点评

**流程简介:** 1. 在函数的 PLT 表项中设置一个软件断点。 2. 遇到断点时,内核触发中断处理函数。 3. 跟踪器或调试器通过 `ptrace` 系统调用 attach 到程序并设置断点。 4. 程序在断点处暂停执行,并发送 `SIGTRAP` 信号给跟踪器或调试器。 5. 跟踪器或调试器收到 `SIGTRAP` 信号后,分析断点并处理程序调用。 **实现方式:** 1. 通过 `ptrace` 系统调用将 `int $3` 指令插入 PLT 的代码。 2. 在 `PTRACE_POKETEXT` 模式下,设置断点。 3. 跟踪器或调试器通过 `ptrace` 设置 `PTRACE_POKETEXT`选项,覆盖库函数的 PLT 中的汇编 trampoline。 4. 当程序调用库函数时,`ptrace` 将覆盖 PLT 中的 trampoline,执行 `int $3` 指令。 5. 跟踪器或调试器收到 `SIGTRAP` 信号后,分析断点并处理程序调用。

正文

http://arthurchiao.art/blog/how-does-ltrace-work-zh/

 

译者序

本文翻译自 2016 年的一篇英文博客 How Does ltrace Work 。如果能看懂英文,我建议你阅读原文,或者和本文对照看。

阅读本文之前,强烈建议先阅读下面几篇之前的文章:

  1. (译) Linux 系统调用权威指南
  2. (译) strace 是如何工作的

其中包含了本文所需的部分预备知识。

以下是译文。


太长不读(TL;DR)

本文介绍 ltrace 内部是如何工作的,和我们的前一篇文章 strace 是如何工作的 是兄弟篇。

文章首先会对比 ltrace 和 strace 的异同;然后介绍 ltrace 是如何基于 ptrace 系统调用获取被跟踪进程的库函数调用信息的。

ltrace 和 strace

strace 是一个系统调用,也是一个信号跟踪器(signal tracer),主要用于跟踪系统 调用,打印系统调用的参数、返回值、时间戳等很多信息。它也可以跟踪和打印进程收到的 信号。

我们在前一篇文章strace 是如何工作的 中介绍过, strace 内部是基于 ptrace 系统调用的。

ltrace 是一个(函数)库调用跟踪器(libraray call tracer),顾名思义,主要用 于跟踪程序的函数库调用信息。另外,它也可以像 strace 一样跟踪系统调用和信号。它 的命令行参数和 strace 很相似。

ltrace 也是基于 ptrace,但跟踪库函数和跟踪系统调用还是有很大差别的,这就是为 什么会有 ltrace 的原因。

在介绍细节之前,我们需要先了解几个概念。

2 重要概念

2.1 程序调用函数库的流程

共享库可以被加载到任意地址。这意味着,共享库内的函数地址只有在运行时加载以后才能确定。 即使重复执行同一程序,加载同一动态库,库内的函数地址也是不同的。

那么,程序是如何调用地址未知的函数的呢?

简短版的回答是:二进制格式、操作系统,以及加载器。在 Linux 上,这是一 支程序和动态加载器之间的曼妙舞蹈。

下面是详细版的回答。

Linux 程序使用 ELF binary format,它提供了 许多特性。出于本文目的,我们这里只介绍两个:

  • 过程链接表(Procedire Linkage Table,PLT)
  • 全局偏移表(Global Offset Table,GOT)

库函数在 PLT 里都有一组对应的汇编指令,通常称作 trampoline,在函数被调用的时候执行。

PLT trampoline 都遵循类似的格式,下面是一个例子:

PLT1: jmp *name1@GOTPCREL(%rip)
      pushq $index1
      jmp .PLT0

第一行代码跳转到一个地址,这个地址的值存储在 GOT 中。

GOT 存储了绝对地址。这些地址在程序启动时初始化,指向 PLT pushq 指令所在的地址 (第二行代码)。

第三行 pushq $index1 为动态连接器准备一些数据,然后通过 jmp .PLT0 跳转到另一 段代码,后者会进而调用动态链接器。

动态链接器通过 $index1 和其他一些数据来判断程序想调用的是哪个库函数,然后定位 到函数地址,并将其写入 GOT,覆盖之前初始化时的默认值。

当后面再次调用到这个函数时,就会直接找到函数地址,而不需再经过以上的动态链接器查 找过程。

想更详细地了解这个过程,可以查看 System V AMD64 ABI,从 75 页开始。

总结起来:

  1. 程序加载到内存时,程序和每个动态共享库(例如 DSO)通过 PLT 和 GOT 映射到内存
  2. 程序开始执行时,动态共享库里的函数的内存地址是未知的,因为动态库可以被加载到程序地址空间的任意地址
  3. 首次执行到一个函数的时候,执行过程转到函数的 PLT,里面是一些汇编代码(trampoline)
  4. trampoline 组织数据,然后调用动态链接器
  5. 动态链接器通过 PLT 准备的数据找到函数地址
  6. 将地址写入 GOT 表,然后执行转到该函数
  7. 后面再次调用到这个函数时,不再经过动态链接器,因为 GOT 里已经存储了函数地址,PLT 可以直接调用

为了能够 hook 库函数调用,ltrace 必须将它自己插入以上的流程。它的实现方式: 在函数的 PLT 表项里设置一个软件断点。

2.2 断点的工作原理

断点(breakpoint)是使函数在特定的地方停止执行,然后让另一个程序(例如调试器,跟踪器)介入的方式。

有两类断点:硬件断点和软件断点。

硬件断点是 CPU 特性,数量比较有限。在 amd64 CPU 上有 4 个特殊的寄存器,可以设置让程序停止执行的地址。

软件断点通过特殊的汇编指令触发,数量不受限制。在 amd64 CPU 上,通过如下汇编指令触发软件断点:

int $3

这条指令会使处理器触发编号为 3 的中断,这个中断是专门为调试器准备的, Linux 内核有对应的中断处理函数,在执行的时候会向被调试程序发送一个 SIGTRAP 信号。

回忆我们前一篇讲 strace 的文章, 里面提到跟踪器可以通过 ptrace 系统调用 attach 到程序。 所有发送给被跟踪程序的信号会使得程序暂停执行,然后通知跟踪程序。

因此:

  1. 程序执行到 int $3,执行过程被暂停
  2. 触发内核中 3 号中断对应的中断处理函数
  3. 中断处理函数经过一些调用,最终向程序发送一个 SIGTRAP 信号
  4. 如果程序已经被其他(跟踪)程序通过 ptrace attach 了,那后者会收到一个 SIGTRAP 信号

这和前一篇文章介绍的使用 PTRACE_SYSCALL 参数的过程类似。

那么,跟踪器或调试器是如何将这个 int $3 指令插入程序的呢?

2.3 在程序中插入断点的实现

ptrace + PTRACE_POKETEXT:修改运行程序的内存。

ptrace 系统调用接受一个 request 参数,当设置为 PTRACE_POKETEXT 时,允许 修改运行中程序的内存。

调试器和跟踪器可以使用 PTRACE_POKETEXT 将 int $3 指令在程序运行的时候写到程 序的特定内存。这就是断点如何设置的。

ltrace

将以上讲到的所有内容结合起来就得到了 ltrace: ltrace = ptrace + PTRACE_POKETEXT + int $3

ltrace 的工作原理:

  1. 通过 ptrace attach 到运行中的程序
  2. 定位程序的 PLT
  3. 通过 ptrace 设置 PTRACE_POKETEXT 选项,用 int $3 指令覆盖库函数的 PLT 中的汇编 trampoline
  4. 恢复程序执行

接下来当调用到库函数时,程序会执行 int $3 指令:

  1. 程序执行 int $3 指令
  2. 对应的内核中断处理函数开始执行
  3. 内核通知 ltrace 被跟踪进程有 SIGTRAP 信号待处理
  4. ltrace 查看程序在调用哪个库函数,打印函数名、参数、时间戳等参数

最后,ltrace 必须将插入到 PLT 的代码 int $3 替换为原来的代码,然后程序就可以 恢复正常执行了:

  1. ltrace 使用 PTRACE_POKETEXT 将 int $3 替换原来的指令
  2. 程序恢复执行
  3. 程序恢复正常执行,因为插入的断点被移除了

这就是 ltrace 如何跟踪库函数调用的。

4 结束语

ptrace 系统调用非常强大,可以跟踪系统调用、重写运行中程序的内存、读取运行中程 序的寄存器等等。

strace 和 ltrace 都使用 PTRACE_SYSCALL 跟踪系统调用。两者的大致工作过程类 似:为被跟踪程序触发 SIGTRAP 信号,暂停执行,通知跟踪程序(strace 或 ltrace),然后跟踪程序被“唤醒”,分析被暂停的程序。

ltrace 还会通过 PTRACE_POKETEXT 重写程序内存,以便通过特殊指令中断程序的执行。

想了解更多 PTRACE_SYSCALL 的内部细节,可以阅读我们前一篇介绍 strace 的博客 

5 我们的相关文章

如果对本文感兴趣,那么你可能对我们的以下文章也感兴趣:

  1. (译) Linux 系统调用权威指南
  2. (译) strace 是如何工作的

与[转帖][译] ltrace 是如何工作的(2016)相似的内容:

[转帖][译] ltrace 是如何工作的(2016)

http://arthurchiao.art/blog/how-does-ltrace-work-zh/ 译者序 本文翻译自 2016 年的一篇英文博客 How Does ltrace Work 。如果能看懂英文,我建议你阅读原文,或者和本文对照看。 阅读本文之前,强烈建议先阅读下面几篇之前的文章:

[转帖][译] Cilium 未来数据平面:支撑 100Gbit/s k8s 集群(KubeCon, 2022)

http://arthurchiao.art/blog/cilium-tomorrow-networking-data-plane-zh/ 作者写的非常好呢 基础支持的确非常重要呢. Published at 2022-11-12 | Last Update 2022-11-12 译者序 本文翻译自

[转帖][译] Cilium:基于 BPF+EDT+FQ+BBR 实现更好的带宽管理(KubeCon, 2022)

http://arthurchiao.art/blog/better-bandwidth-management-with-ebpf-zh/ Published at 2022-10-30 | Last Update 2022-10-30 译者序 本文翻译自 KubeCon+CloudNativeCo

[转帖][译] Linux Socket Filtering (LSF, aka BPF)(KernelDoc,2021)

http://arthurchiao.art/blog/linux-socket-filtering-aka-bpf-zh/ 译者序 本文翻译自 2021 年 Linux 5.10 内核文档: Linux Socket Filtering aka Berkeley Packet Filter (BP

[转帖][译] 流量控制(TC)五十年:从基于缓冲队列(Queue)到基于时间(EDT)的演进(Google, 2018)

http://arthurchiao.art/blog/traffic-control-from-queue-to-edt-zh/ 译者序 本文组合翻译了 Google 2018 年两篇分享中的技术部分,二者讲的同一件事情,但层次侧重不同: Netdev 2018: Evolving from AF

[转帖][译] Linux 网络栈监控和调优:接收数据(2016)

http://arthurchiao.art/blog/tuning-stack-rx-zh/ 注意:本文内容已经太老,基于 kernel 3.13 和 1Gbps 网卡驱动 igb,建议移步 kernel 5.10 + 25Gbps 驱动版: Linux 网络栈原理、监控与调优:前言 Linux

[转帖][译] Linux 系统调用权威指南(2016)

http://arthurchiao.art/blog/system-call-definitive-guide-zh/ 译者序 本文翻译自 2016 年的一篇英文博客 The Definitive Guide to Linux System Calls 。如果能看懂英文,我建议你阅读原文,或者和本

[转帖][译] RFC 1180:朴素 TCP/IP 教程(1991)

http://arthurchiao.art/blog/rfc1180-a-tcp-ip-tutorial-zh/ 译者序 本文翻译自 1991 年的一份 RFC(1180): A TCP/IP Tutorial。 本文虽距今将近 20 年,但内容并未过时,这不禁让人惊叹于 TCP/IP 协议栈生命

[转帖][译] 大规模微服务利器:eBPF + Kubernetes(KubeCon, 2020)

http://arthurchiao.art/blog/ebpf-and-k8s-zh/ Published at 2020-09-06 | Last Update 2021-02-21 译者序 本文翻译自 2020 年 Daniel Borkmann 在 KubeCon 的一篇分享: eBPF a

[转帖][译] Linux 网络栈监控和调优:发送数据(2017)

http://arthurchiao.art/blog/tuning-stack-tx-zh/ 译者序 本文翻译自 2017 年的一篇英文博客 Monitoring and Tuning the Linux Networking Stack: Sending Data。如果能看懂英文,建议阅读原文,