# 在 FreeBSD 上构建 Loom 框架

* 原文链接：[Building the Loom Framework on FreeBSD](https://freebsdfoundation.org/wp-content/uploads/2022/08/kidney_loom.pdf)
* 作者：**BRIAN KIDNEY**

在试图理解生产环境中的代码时，开发者会记录信息以便离线分析。这通常需要事先决定记录什么以及何时记录。对此问题的回答往往基于对系统中可能出现错误的情况或以往问题的判断。而在开发过程中，确切地知道需要哪些信息并不总是可能的，特别是在试图追踪与安全有关的数据时。

这正是因果自适应分布式高效跟踪系统（CADETS）研究项目所面临的问题之一。该项目的目标是利用现有机制并开发新技术，对 FreeBSD 进行改装，从而最大限度地提高对实时服务器的透明度。这不仅能让用户更好地了解其系统中的安全问题，还能为性能调优和调试提供更多信息。

Loom 是在这一工作中开发的众多工具之一。本文将探讨构建 Loom 的最初灵感，并讨论其如何超越最初的目的得到扩展。最后，我们还将介绍为 Loom 未来发展所做的工作。

## 为安全而进行的插桩

当开发者对他们的软件进行插桩时，通常是为了跟踪性能/追踪系统中可能出现故障的关键点。添加安全监控时常常采用类似的方法，不过通常只捕获少量事件（例如登录尝试）且记录的信息十分有限。这些信息大多被记录在计算机的不同位置，且往往没有与其他系统进行关联。CADETS 团队发现，通过使用 DTrace —— 这一内置在 FreeBSD 基本系统中的跟踪框架 —— 能轻松地获取内核中的这些信息。FreeBSD 提供了许多 DTrace 提供者，以便访问内核组件的信息，如系统调用、函数调用和网络缓冲区等。

然而，从用户态进程中提取额外信息时，使用 DTrace 需要更多工作。对运行在用户态的程序进行跟踪时，捕获到的信息往往缺乏上下文和灵活性。例如，编写 DTrace 脚本来记录库中某个特定函数的所有调用非常简单，但该脚本必须对每个使用该库的进程单独应用。我们需要一种方法，能够对库本身进行插桩，使得任何使用该库的程序在调用时都能自动记录日志，而无需在脚本中显式针对每个程序。所需的机制还必须为每次调用库函数提供上下文信息，包括函数名、参数以及调用该函数的可执行文件。

DTrace 通过用户态静态定义跟踪（USDT）提供了一种将跟踪探针直接插入程序和库中的机制。创建这些探针的过程要求开发者为每个探针编写并编译自定义代码，将其包含在原始源码中，然后使用 DTrace 专用工具修改程序的二进制目标文件，以插入对探针的调用。不幸的是，这一过程要求对构建流程进行修改，增加一个直接修改目标文件的额外工具。每次探针代码发生更改时，都需要将其转换为一个包含在原始代码中的头文件，从而要求程序必须从头开始完整编译。我们希望有一个更简单的系统，让用户在 LLVM 中间表示（IR）阶段插入插桩代码，而无需进行完整重编译。由于 FreeBSD 使用 LLVM 作为系统编译器，这将使得在构建系统中包含我们的方法变得更加容易。

我们对这一问题的解决方案是 Loom——自定义的 LLVM 优化（opt）pass，它能让用户在编译时向程序中插入任意代码，而无需修改原始源码，也无需每次都对整个程序进行重编译。如图 1 所示，Loom 以额外的 opt pass 形式集成到 FreeBSD 的构建过程中。原始源码首先被编译为 LLVM IR，然后利用 Loom pass 和 YAML 策略文件，在最终链接阶段之前将额外的代码插入到程序中。

![](https://github.com/user-attachments/assets/ce67c902-a7fd-4f23-9857-99e40db6b6e2)

**图 1：Loom 构建流程**

与原始的 USDT 方法不同，Loom 无需对源代码进行更改。事实上，甚至不需要应用程序的源代码。只要有代码的 LLVM IR 表示，就可以向程序中添加插桩。

CADETS 的一个使用案例是捕获所有使用可插拔认证模块（PAM）系统进行的身份验证尝试。这些尝试可能来自多个来源，例如 SSH 和 sudo，因此，为了全面捕获它们，我们直接针对 PAM 库进行插桩。示例 1 提供了一个 Loom 策略 YAML 文件，展示了我们如何对 PAM 进行插桩，以记录来自用户态的身份验证尝试。该示例特别针对 `pam_authenticate` 函数进行插桩，并捕获所有调用的参数。同时，我们使用元数据为数据添加上下文信息，以便 DTrace 在接收到数据时，能够区分来自不同用户态探针的数据。通过这种方式，我们能够捕获登录尝试的用户名（以及其他详细信息）及其来源可执行文件。

```yml
strategy: callout
dtrace: userspace
functions:
- callee: [entry]
metadata:
name: auth
id: 1
name: pam_authenticate
```

**示例 1：用于记录 PAM 认证尝试的 Loom 配置**

在大多数情况下，与 USDT 不同，该解决方案不需要用户编写自定义代码。为了将数据传输到 DTrace 系统，我们实现了一个实验性系统调用，该调用使用 DTrace 的 SDT 提供者（provider）将跟踪数据传递到内核。策略文件提供了所有必要的信息，以便在跟踪时为用户数据添加上下文。唯一需要额外开发的情况是，用户希望在数据发送到 DTrace 之前对其进行某种转换。

使用 Loom，我们能够在不将 USDT 工具链集成到 FreeBSD 构建流程的情况下，复现 DTrace USDT 的功能。尽管该系统需要一个自定义的系统调用，但这只是一个简单的修改，却提供了极大的灵活性，使得可以在无需修改源代码的情况下，向应用程序添加或移除插桩。由于操作系统已经使用 LLVM，我们可以通过为程序和库添加额外的构建目标，以生成 LLVM IR 输出的方式，将 Loom 集成到构建流程中。然后，在需要时，可以在构建的额外步骤中调用 Loom。

FreeBSD 基本系统为这一开发过程提供了极大的便利。FreeBSD 的基本系统不仅包括运行完整操作系统所需的内核和用户态源码，还提供了统一的系统来构建 FreeBSD。为了在我们的研究项目中使用 Loom，我们需要能够将程序和库构建为位代码对象（BCO）。BCO 是 LLVM IR 的二进制形式，Loom 可以修改它们以进行插桩和转换。由于 FreeBSD 中的所有二进制文件都使用一套统一的构建脚本，我们只需要修改这些脚本，就可以为基本系统的任何部分生成 BCO。待完成这些修改，我们就能够对任意程序或库应用 Loom。

## 扩展 Loom

在 CADETS 项目的一些使用场景中，需要在将数据传递给 DTrace 之前对其进行转换。例如，为了避免在插桩的分布式系统中出现服务器间的名称冲突，该项目使用全局唯一标识符（GUID）来表示用户名，以便在输出结果中对其进行单独跟踪。为实现这一目标，我们为 Loom 添加了一种机制，允许外部代码插入到程序中。只要符号在构建流程的链接阶段可用，用户就可以添加对自定义函数或库的调用。尽管这一功能最初是为了在日志记录前修改数据而设计的，但这一概念打开了新的可能性，例如代码转换，而不仅仅是插桩。

自 CADETS 项目结束以来，我们一直在探索这些可能性，比如在程序内部替换代码以使用新的 API。例如，以往测试一个新的网络 API 需要修改源代码，将对旧 API 的调用替换为新 API 的调用。这一过程既繁琐又容易出错。我们正在扩展 Loom，使其能够在无需修改源代码的情况下完成这些任务。通过匹配一组函数调用，Loom 可以移除原始代码，并用新的 API 调用进行替换。

目前，这项工作的一个限制是策略文件，该文件用于指定 Loom 需要进行的更改。尽管 Loom 具备代码转换的能力，但当前基于 YAML 的格式表达能力不足，无法完全描述复杂的转换。因此，我们正在开发一种新的语言，以克服这一限制。该语言的目标是让用户以更精确的方式指定 Loom 需要执行的转换操作。

## Loom 在 FreeBSD 上的未来

Loom 是一个功能完善的插桩与转换框架。它能够对函数和函数调用进行插桩，同时还支持访问结构体字段、全局变量和指针。目前已实现了一对一的函数调用替换，并且具备替换一系列函数调用的能力，尽管要使其普遍适用仍需进一步的配置工作。此外，许多这些配置都可以使用通配符匹配，或限制在源代码的特定文件范围内。

自 CADETS 项目以来，Loom 受到了其他操作系统（如 Linux）开发者的关注。尽管我们已努力支持这些用户，但 Loom 的主要开发仍然集中在 FreeBSD 上。随着 FreeBSD 构建系统即将引入链接时优化（LTO），我们将探索在未修改的 FreeBSD 构建系统中使用 Loom 的可能性。此外，我们还将利用 FreeBSD 测试新的 Loom 配置语言，并研究转换系统在软件移植中的应用，帮助将其他操作系统的软件移植到 FreeBSD 上。

## 了解更多关于 Loom 的信息

如需了解 Loom 的更多信息并关注其未来发展，你可以访问项目页面：[github.com/cadets/loom](https://github.com/cadets/loom)。

## 致谢

作者要感谢在本项目中提供帮助和指导的人，包括 George Neville-Neil 和 Domagoj Stolfa 在 DTrace 方面的帮助。感谢 Ed Maste，他不仅直接回答了我关于 FreeBSD 的问题，还帮助我联系到了相关的专家。此外，我还要感谢 CADETS 项目的所有成员，包括 Robert Watson、Arun Thomas、Silviu Chiricescu、Jon Anderson 和 Amanda Strand。

FreeBSD 社区为我们的工作提供了极大的支持。无论是在 IRC、邮件列表，还是 BSD 会议上，我们都能很容易地与社区成员建立联系，并在遇到问题时得到解答。感谢那些乐于提供帮助的社区成员。

最后，作者特别感谢 Jon Anderson 对本文稿提出的反馈和改进建议。

本研究得到了美国国防高级研究计划局（DARPA）和美国空军研究实验室（AFRL）的资助，合同编号 FA8650-15-C-7558。本文所包含的观点、意见和/或研究成果仅代表作者个人观点，不应被视为美国国防部或美国政府的官方立场或政策，无论是明示还是暗示。

## 参考文献

1. Anderson, J.; Neville-Neil, G. V.; Thomas, A.; Watson, R. N. M. “Cadets: Blending Tracing and Security on FreeBSD,” *FreeBSD Journal*, May/June 2017, 12 - 17.

***

**Brian Kidney** 是加拿大纽芬兰圣约翰斯北大西洋学院（College of the North Atlantic）网络安全课程的讲师，同时正在纽芬兰纪念大学（Memorial University）攻读计算机工程博士学位。他的研究兴趣包括隐私与安全，特别是操作系统和编程语言相关领域。Brian 拥有 20 年的软件工程经验，曾为多个行业开发软件。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/qi-kan/20220708-ke-yan-xi-tong-yu-freebsd/loom.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
