libfaketime modifies the system time for a single application
libfaketime,版本 0.9.12(2025 年 6 月)
持续集成覆盖范围
必备的 CI 门禁覆盖 macOS arm64/arm64e,以及 Linux glibc、musl、
32-bit/time64、ARM64、Fedora、Debian、Rocky Linux 和 Arch Linux。
Sanitizer 和容器任务设有时间限制,并在某次运行失败时上传完整的测试日志。
针对特定运行时的检查被有意设为可选:某些语言运行时使用 vDSO 或直接系统调用,
能够绕过 LD_PRELOAD/dyld 拦截。这类绕过会被记录为兼容性限制,
而不会被视为 libfaketime 核心失败的证据。
要在本地执行有界的 Linux 运行,请使用兼容的镜像运行 test/docker_baseline.sh,
例如 gcc:13-bookworm。当需要显式选择镜像架构时,设置
DOCKER_PLATFORM=linux/amd64。仅当可选的 Rust 运行时探测及其工具链可用时,
才设置 LIBFAKETIME_RUN_OPTIONAL_RUNTIME_TESTS=1。
本文件内容:
-
简介
-
兼容性问题
-
安装
-
使用 a) 基础用法 b) 使用绝对日期 c) 使用“start at”日期 d) 使用偏移量表示相对日期 e) 高级功能与注意事项 f) 在全系统范围内伪造日期和时间 g) 使用 “faketime” 包装脚本 h) 基于经过的时间或调用次数“限制”libfaketime i) 按进程“限制”libfaketime j) 派生外部进程 k) 将时间戳保存到文件,并从文件加载 l) 使用确定性数列替换随机数
-
许可证
-
联系方式
-
简介
libfaketime 会拦截程序用于获取当前日期和时间的各种系统调用,然后向这些程序 报告经过修改(伪造)的日期和时间(由您作为用户指定)。这意味着,您可以修改 某个程序所看到的系统时间,而无需更改全系统的时间。
libfaketime 允许您指定绝对日期(例如 01/01/2004)和相对日期(例如 10 天前)。
libfaketime 可用于多种目的,例如
- 确定性构建流程
- 调试时间相关问题,例如 SSL 证书过期
- 测试软件的 year-2038 兼容性
libfaketime 附带一个名为 "faketime" 的命令行包装器,便于使用,但并未暴露 libfaketime 的全部功能。如果您的使用场景无法由 faketime 命令覆盖,请查阅 本文档,确认能否直接使用 libfaketime 实现。
- 兼容性问题
-
libfaketime 应在 Linux 和 macOS 上可用。 实际效果可能因环境而异;也有用户报告某些其他 *NIX 系统可以正常工作。
-
libfaketime 使用操作系统链接器(其参与启动程序)的库预加载机制,因此无法 用于静态链接的二进制文件,或已设置 setuid 标志的二进制文件(例如 ping 或 passwd 这类 suidroot 程序)。更多细节请参见系统链接器的手册页。
-
libfaketime 支持 pthreads 环境。会单独构建一个库(libfaketimeMT.so.1), 其中包含 pthread 同步调用。该库还会将经过 time() 拦截的调用单线程化, 因为库中静态缓存了若干变量,如果在不同步的情况下访问可能会引发问题。
不过,这种性能开销可能会影响某些应用。如果情况如此,您可以尝试使用未同步 的 time() 拦截:从 Makefile 中移除 -DPTHREAD_SINGLETHREADED_TIME, 然后重新构建 libfaketimeMT.so.1
-
基于 Java/JVM 的应用可以工作,但您需要额外传入一个参数 (FAKETIME_DONT_FAKE_MONOTONIC)。具体细节见下文的基础用法。 如果不传该参数,java 命令通常会挂起。
-
应用如果显式自行动态加载系统库(例如 librt),而不是依赖链接器在应用启动时 完成加载,最终可能会绕过 libfaketime。除非您能修改这些应用,否则 libfaketime 对它们不起作用。
这种情况在复杂的运行时环境中似乎十分常见,例如使用 golang 编写的程序、 某些 Java 虚拟机实现等。在这些情况下,libfaketime 实际上已被绕过, 因此我们无能为力。建议您向相应的开发者和供应商提出请求,让其以支持通过 LD_PRELOAD 拦截指定系统调用的方式实现运行时环境。
-
应用可以被明确设计成阻止 libfaketime 工作,例如检查特定环境变量是否已设置, 或检查 libfaketime 专用文件是否存在。
-
运行时兼容性:当语言运行时或框架报告真实时间时,请先确认它使用哪种时钟, 以及是否动态加载其自身的系统库。在将某个特定应用的报告判定为 libfaketime 缺陷之前,必须先提供一个使用相同时钟 API 的最小 C 复现用例。尤其需要留意: Go、Python、.NET、Boost.Asio、jemalloc 的使用者、Proton/Wine 以及 JVM 可能会缓存时钟、使用 vDSO/直接系统调用、在启动后加载库,或混合使用真实 时钟域与伪造时钟域。即使 libfaketime 基础测试套件通过,这些情况仍可能导致 挂起或忽略预加载。请显式保持预加载顺序,并在运行时要求真实单调时钟时使用 FAKETIME_DONT_FAKE_MONOTONIC。这类限制属于兼容性约束,而不是安全边界。
-
CLOCK_MONOTONIC 测试:运行 "make test" 会在 libfaketime 库成功编译后执行 一系列测试。在部分平台上,"CLOCK_MONOTONIC 测试" 似乎会永久挂起。 当且仅当这种情况出现在您的平台上时,才在 src/Makefile 中添加 CFLAG -DFORCE_MONOTONIC_FIX 并重新编译 libfaketime。 对于测试不会挂起的平台,请勿设置 FORCE_MONOTONIC_FIX。
如果在 CLOCK_REALTIME 测试中观察到挂起,请添加 CFLAG -DFORCE_PTHREAD_NONVER。如果仅设置 FORCE_MONOTONIC_FIX 无法解决 MONOTONIC_CLOCK 测试中的挂起问题,也要设置 FORCE_PTHREAD_NONVER 标志。
如果未将 FORCE_MONOTONIC_FIX 设置为编译期标志,您也可以通过设置环境变量 FAKETIME_FORCE_MONOTONIC_FIX=1 在运行时启用该修复,或设置为 0 以显式禁用它。如果 libfaketime 是在底层 libc 实现为 glibc 的系统上编译的, 并且运行时检测到假设需要该变通方案的 glibc 版本,则该修复会自动启用。 如果 CLOCK_MONOTONIC 测试期间观察到任何挂起,请通过 https://github.com/wolfcw/libfaketime/issues 的 Github issues 报告, 并附上 CPU 架构、libc 实现(例如 glibc 2.30)以及其他可能有用的细节 (例如 Linux 发行版、是否运行在 Docker 容器中等)。
请尽量避免在无需该标志的平台上使用 FORCE_MONOTONIC_FIX 编译。 虽然大多数情况下不会有影响,但根据所使用 FAKETIME 的具体设置, 它可能导致某些被拦截函数(例如 pthread_cond_timedwait())过早或过晚 返回超时结果,从而破坏某些应用。请先尝试不设置 FORCE_MONOTONIC_FIX 进行编译,并检查测试是否出现挂起。如果出现,您可以将 FAKETIME_FORCE_MONOTONIC_FIX 环境变量设为 1,或者设置 FORCE_MONOTONIC_FIX 重新编译。
- 安装
运行 "make" 会编译两个库版本和一个测试程序,随后也会执行该测试程序。
如果测试正常通过,您应把 libfaketime 库(libfaketime.so.1 和 libfaketimeMT.so.1)复制到期望的位置。运行 "make install" 会尝试将它们 放入 /usr/local/lib/faketime,并把包装 shell 脚本 "faketime" 安装到 /usr/local/bin,这两项操作很可能都需要 root 权限。不过,从技术角度看, 并不一定要进行全系统安装,因此即使在没有 root 权限的机器上也可以使用 libfaketime。您可能需要根据相应 Makefile 调整 PREFIX 变量。
默认情况下,Makefile 会针对您的默认系统架构编译/链接 libfaketime。 如果您需要在 64 位平台上构建 32 位文件,请参见 src/Makefile 中关于 CFLAGS 和 LDFLAGS 的说明。
自 0.6 版本起,针对文件时间戳的系统调用也会被拦截,这得益于 Philipp Hachtmann 的贡献。当程序读写文件且这些文件的时间戳也需要被伪造时, 结合下文 4d) 中说明的相对时间偏移,这一功能尤为有用。如果您不需要该功能, 或它会使您希望配合 FTPL 使用的应用感到困惑,请定义环境变量 NO_FAKE_STAT, 这样被拦截的 stat 调用将原样透传。
在 macOS 上,由于 dyld 的行为不同,必须以不同方式编译。请使用提供的 Makefile.OSX 文件编译 libfaketime.1.dylib。此外,不要使用 LD_PRELOAD, 而应将变量 DYLD_INSERT_LIBRARIES 设置为 libfaketime.1.dylib 的路径, 并设置变量 DYLD_FORCE_FLAT_NAMESPACE(可设为任意值)。macOS 用户应阅读 README.OSX 以获取更多细节。
在 macOS 和 Linux 上测试
标准测试命令会构建特定平台的库,并运行完整的功能测试套件:
make test
在 macOS 上,顶层 Makefile 会自动选择 Darwin 的 makefiles。 Linux 构建可以在一次性 Docker 容器中进行测试,而不会将构建产物写入代码检出目录。基线测试脚本接受一组本地可用的镜像:
test/docker_baseline.sh gcc:13-bookworm debian:13 alpine:3.20
在 ARM64 macOS 主机上,需要显式指定目标平台。专用封装脚本还会在启动容器前检查每个镜像的架构:
DOCKER_PLATFORM=linux/arm64 test/docker_arm64.sh
gcc:13-bookworm debian:13 alpine:3.20
标记为其他架构的镜像必须替换为 ARM64 镜像;Docker 模拟无法让一个不包含 ARM64 的镜像清单被该测试脚本使用。一旦有匹配的 ARM64 镜像可用,就可以添加 Arch Linux:
DOCKER_PLATFORM=linux/arm64 test/docker_arm64.sh archlinux:base-devel
在 Linux 上进行内存安全检查时,sanitizer 测试脚本会使用 AddressSanitizer 和 UndefinedBehaviorSanitizer 进行构建,并在 LD_PRELOAD 中将 sanitizer 运行时放在 libfaketime 之前:
DOCKER_PLATFORM=linux/arm64 test/docker_sanitizers.sh gcc:13-bookworm
sanitizer 运行只会跳过那些 preload 配置与 sanitizer 插桩不兼容的辅助进程检查。仅限 Linux 的功能在 macOS 上会被报告为跳过;这样可以保持两个平台上通用时钟、配置、进程和生命周期覆盖范围具有可比性。
- 使用
4a) 使用基础
在任意指定程序上使用 libfaketime 包括两个步骤:
- 确保 libfaketime 被系统链接器加载。
- 指定伪造的时间。
举个例子,我们希望 "date" 命令报告我们伪造的时间。为此,可以在 Linux 上使用以下命令行:
user@host> date Tue Nov 23 12:01:05 CEST 2016
user@host> LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" date Mon Nov 8 12:01:12 CEST 2016
user@host> LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" FAKETIME_DONT_FAKE_MONOTONIC=1 java -version java version "1.8.0_111" Java(TM) SE Runtime Environment (build 1.8.0_111-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
启用 libfaketime 来运行任意命令/程序的基本方法是确保环境变量 LD_PRELOAD 包含 libfaketime 库的路径和文件名。可以提前一次性设置:
export LD_PRELOAD=/path/to/libfaketime.so.1 (现在运行任意你想要的命令)
也可以在命令行中直接指定:
LD_PRELOAD=/path/to/libfaketime.so.1 your_command_here
(这些示例适用于 bash shell;在你的系统上,设置环境变量的方式可能有所不同。)
在 Linux 上,库搜索路径可以作为链接器配置的一部分进行设置。 这样,LD_PRELOAD 也可以使用相对路径。例如,如果 libfaketime.so.1 被安装为 /path/to/libfaketime.so.1,你可以将 /path/to 添加到相应的 链接器配置文件中,例如 /etc/ld.so.conf.d/local.conf,然后运行 "ldconfig" 命令。之后,使用 LD_PRELOAD=libfaketime.so.1 即可。
不过,还需要指定伪造的时间;否则,libfaketime 虽然会加载,但只会报告真实的系统时间。指定伪造时间有多种方式:
a) 通过设置环境变量 FAKETIME。 b) 通过使用环境变量 FAKETIME_TIMESTAMP_FILE 指定的文件 c) 通过使用你主目录下的 .faketimerc 文件。 d) 通过使用 /etc/faketimerc 文件作为系统级默认设置。 e) 通过使用 FAKETIME_UPDATE_TIMESTAMP_FILE 和 date -s "" 或类似命令。
如果你想使用 b)、c) 或 d),$HOME/.faketimerc 或 /etc/faketimerc 仅包含一行文本,其内容与下文所述的 FAKETIME 环境变量完全相同。请注意,只有当不存在 $HOME/.faketimerc,且不存在 FAKETIME_TIMESTAMP_FILE 文件时,才会使用 /etc/faketimerc。 此外,FAKETIME 环境变量的优先级 始终 高于这些文件。关于 FAKETIME_UPDATE_TIMESTAMP_FILE,请参见下文。
4b) 使用绝对日期
必须用于 绝对 日期的格式是 "YYYY-MM-DD hh:mm:ss"。 例如,2020 年 12 月 24 日 20:30 必须指定为 FAKETIME="2020-12-24 20:30:00"。
4c) 使用 'start at' 日期
(感谢 David North 在 0.7 版中对 TDI 的重要贡献)
必须用于 start_at 日期的格式是 "@YYYY-MM-DD hh:mm:ss"。 例如,2020 年 12 月 24 日 20:30 必须指定为 FAKETIME="@2020-12-24 20:30:00"。
4b) 中描述的绝对日期会模拟一个停留在指定绝对时间的系统时钟。'start at' 格式允许实现如下 4d) 节所述的“相对”时钟行为,但它使用的是 'start at' 时间,而不是偏移时间。
如果已启动的进程本身又启动其他(子)进程,它们默认会再次从指定的 start-at 日期开始。如果这不是你需要的行为,请设置环境变量 FAKETIME_DONT_RESET=1。可通过这些示例查看区别:
LD_PRELOAD=src/libfaketime.so.1 FAKETIME="@2000-01-01 11:12:13"
FAKETIME_DONT_RESET=1
bash -c 'while [ $SECONDS -lt 5 ]; do date; sleep 1; done'
LD_PRELOAD=src/libfaketime.so.1 FAKETIME="@2000-01-01 11:12:13"
bash -c 'while [ $SECONDS -lt 5 ]; do date; sleep 1; done'
在第二个示例中,"date" 命令始终会打印相同的时间;而在第一个示例中,由于设置了 FAKETIME_DONT_RESET,即使所有 "date" 命令都是新进程,时间仍会继续递增。
4d) 使用偏移量处理相对日期
相对日期偏移量可以为正或负,因此写入 FAKETIME 的内容 必须 以 + 或 - 开头,后跟一个数字,并可选地后跟一个乘数:
-
默认情况下,指定的偏移量以秒为单位。示例:
export FAKETIME="-120" 将伪造时间设置为比真实时间慢 2 分钟(120 秒)。
-
乘数 "m"、"h"、"d" 和 "y" 可分别用于将偏移量指定为分钟、小时、天和年(每年按 365 天计算)。示例:
export FAKETIME="-10m" 将伪造时间设置为比真实时间慢 10 分钟。 export FAKETIME="+14d" 将伪造时间设置为 14 天后的未来时间。
请注意,如果你需要其他乘数(周、月等)或更高精度(例如正确处理闰年),应使用 faketime wrapper 或 GNU date 命令,如下面三个示例中的第一个所示。
至此,你应该能够理解我们之前使用的完整示例:
LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" date
该命令行确保加载 libfaketime,并将伪造时间设置为 15 天前。
Moreno Baricevic 贡献了对 FAKETIME_FMT 环境变量的支持,使你能够可选地设置 strptime() 格式:
一些简单示例:
LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%s FAKETIME="date +%s -d'1 year ago'" date
LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%s FAKETIME="stat -c %Y somefile" date
LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%c FAKETIME="date" date
4e) 高级功能与注意事项
高级时间指定选项:
自 0.8 版本起,感谢 Karl Chen 的贡献,时间偏移量的指定可以使用小数。例如,
FAKETIME="+1,5h"
等价于 FAKETIME="+90m"。请注意,小数分隔符取决于你的区域设置,因此实际上你可能需要使用
FAKETIME="+1.5h"
你应该自行确认正确的分隔符,例如对 /bin/date 等命令使用 libfaketime,这样就能立刻验证其是否按预期生效。
Karl Chen 在 v0.8 中还贡献了一个选项,可用于加速或减慢使用 libfaketime 执行的程序的墙钟时间。例如,
FAKETIME="+1y x2"
会将伪造时间设置为未来一年,并让时钟以两倍速度运行。同样,
FAKETIME="+1y x0,5"
会让时钟仅以一半速度运行。如上所述,小数分隔符取决于你的区域设置。此外,
FAKETIME="+1y i2,0"
会让时钟在每次 time() 等调用时前进两秒,完全独立于系统时钟。这有助于以一定的确定性运行程序。仅此一种情况下,所有派生进程都将使用同一个全局时钟,而不会在每个进程启动时重新初始化它。
请注意,在 FAKETIME 中使用 "x" 或 "i" 仍然需要给出偏移量(参见 4d 节)。这意味着 "+1y x2" 可以生效,但仅写 "x2" 不行。如果你不想伪造时间,只想修改时钟速度,请使用类似 "+0 x2" 的写法,即在 FAKETIME 中显式使用零偏移量作为前缀。
为了测试,你应该运行类似以下命令:
LD_PRELOAD=./libfaketime.so.1 FAKETIME="+1,5y x10,0"
bash -c 'while true; do echo $SECONDS ; sleep 1 ; done'
每当无限循环休眠一秒时,正在运行的 bash 外壳会认为已经过去了 10 秒($SECONDS 是 bash 内置变量,用于测量自该外壳启动以来经过的时间)。
(请注意,例如将 "echo $SECONDS" 替换为对 "/bin/date" 的调用,不会得到预期结果,因为 /bin/date 始终会作为新进程启动,libfaketime 也会为其重新初始化。它会显示正确的偏移量(未来 1.5 年),但不会体现加速或减速。)
对于每次运行时都应使用不同日期和时间的应用程序,可以考虑使用附带的 timeprivacy 封装 Shell 脚本(由 adrelanos at riseup dot net 提供)。
注意事项:
尽可能使用相对偏移量或“start at”日期, 而不要使用绝对日期。
为什么?因为你设置的绝对日期/时间是固定的,也就是说,如果某个程序 获取当前时间,并在 5 分钟后再次获取当前时间, 它两次仍然会得到相同的结果。这很可能会破坏那些通过测量经过时间来工作的程序 (例如,每 X 分钟检查新邮件的邮件程序)。
使用相对偏移量或“start at”日期可以解决此问题。 libfaketime 随后会始终基于真实当前时间和你指定的偏移量 报告伪造时间。
也请注意,为了提升库的性能,伪造时间的默认配置会被缓存 10 秒。因此,如果一个程序正在运行时,你更改了 $HOME/.faketimerc 或 /etc/faketimerc 的内容, 新的伪造时间可能需要最多 10 秒才会生效。如果你的场景中这是一个问题, 你可以通过环境变量 FAKETIME_CACHE_DURATION 更改文件被重新读取前等待的秒数, 或者使用 FAKETIME_NO_CACHE=1 完全禁用缓存。请记住,禁用缓存可能 会对性能产生负面影响(尤其是未使用 FAKETIME 环境变量, 而使用配置文件,例如 FAKETIME_TIMESTAMP_FILE 时)。
通过文件时间戳设置 FAKETIME
基于 Hitoshi Harada(umitanuki)的提案,“start at”时间现在可以通过 文件系统中的任意文件进行设置:将 FAKETIME 环境变量 设置为“%”(百分号),并将 FAKETIME_FOLLOW_FILE 设置为 其修改时间戳将用作“start at”时间来源的文件名。
文件时间戳钩子契约
FAKE_FILE_TIMESTAMPS(也可由 FAKE_UTIME 启用)是一个选择性启用的特性。
其经过测试的契约目前涵盖诸如
utimensat() 等设置时间戳的调用,以及 stat() 和 lstat()
返回的时间戳结果。
fstatat() 仍然可以调用,并针对成功执行进行了测试,但
其结果并不保证在所有 libc 上都会带有相同的虚拟时间戳:
Linux libc 版本使用不同的 __fxstatat() 和时间宽度符号路径,
而 macOS 使用不同的 dyld 符号拦截模型。需要
此行为的应用程序应使用 stat()/lstat(),直到提供
特定 ABI 的契约。
使用示例:
创建任意文件,并设置其时间戳为 2009 年 12 月 24 日 12:34:56
touch -t 0912241234.56 /tmp/my-demo-file.tmp
运行一个 bash shell,通过无限循环打印当前时间
LD_PRELOAD=/path/to/libfaketime.so.1
FAKETIME='%' FAKETIME_FOLLOW_FILE=/tmp/my-demo-file.tmp
FAKETIME_DONT_RESET=1
bash -c 'while true ; do date ; sleep 1 ; done'
现在,在上述进程运行期间,修改该文件的时间戳
(在另一个终端窗口或其他任意位置)
touch -t 2002290123.45 /tmp/my-demo-file.tmp
设置环境变量 FAKETIME_FOLLOW_ABSOLUTE=1 可启用 FAKETIME_FOLLOW_FILE 行为的一种子模式:在这种模式下,模拟时间仅当被跟踪文件的时间戳前进时才前进。在此模式中,未受 libfaketime 的 LD_PRELOAD 拦截的应用可以完全控制被 libfaketime 挂钩的应用的时间。例如,宿主应用可以控制映射到容器中的被跟踪文件的时间戳,从而为容器内运行的模拟时间应用实现(相对)干净的暂停/恢复行为。
在运行时更改 'x' 修饰符
使用 FAKETIME_TIMESTAMP_FILE 可以方便地在程序运行期间更改 FAKETIME 设置:
echo "+0 x1" > /tmp/my-faketime.rc
LD_PRELOAD=libfaketime.so.1 FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc"
FAKETIME_NO_CACHE=1 ./some-program &
sleep 10 ; echo "+0 x10" > /tmp/my-faketime.rc
在运行时改变墙钟时间的速度,也就是使用不同的 'x' 修饰符,默认情况下可能会导致较大的时间跳变,使程序产生混乱。例如,如果程序已在 'x1' 下运行了 10 秒,随后设置改为 'x10',模拟时间在程序看来将像是已经运行了 100 多秒,而不仅仅是 10 多秒。
将环境变量 FAKETIME_XRESET 设置为任意值,可显著平滑不同 'x' 修饰符值之间的切换:
LD_PRELOAD=libfaketime.so.1 FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc"
FAKETIME_NO_CACHE=1 FAKETIME_XRESET=1 ./some-program &
设置 FAKETIME_XRESET 可确保墙钟时间仅在 'x' 修饰符被增大后才开始变快,并确保所报告的模拟时间不会跳回到过去的时间值(在减小时)。
请注意,FAKETIME_XRESET 在内部通过重置 libfaketime 的计时数据结构来实现,这可能会影响所报告的文件时间戳。目前,FAKETIME_XRESET 应被视为实验性功能。
清理共享内存
在支持信号量和共享内存的平台上,libfaketime 会使用信号量和共享内存, 以便在父进程与子进程之间同步 faketime 配置。
请注意,这并不会共享通过 settimeofday 设置的时间 (相关内容见下文中的 FAKETIME_UPDATE_TIMESTAMP_FILE)。
当 libfaketime 正常退出时,它会自行清理。 但是,当进程被终止(例如在命令行按 Ctrl-C)时, 共享内存无法被正确清理。在这种情况下, 应定期删除
/dev/shm/faketime_shm_* 以及 /dev/shm/sem.faketime_sem_*
(手动操作,或妥善自动化)。已终止进程遗留在此处的文件通常没有问题, 但如果下次启动进程时,其进程 ID 对应着已存在的陈旧信号量或共享内存, 就会引发 libfaketime 错误。因此,如果遇到以下错误信息
libfaketime: In ft_shm_create(), shm_open failed: File exists
请按上述说明清理 /dev/shm。这对长期运行的系统(长时间保持运行的服务器) 以及频繁启动大量进程的系统(例如承载许多容器 或类似虚拟化机制的服务器)尤其重要。
可以通过设置环境变量 FAKETIME_DISABLE_SHM 来禁用共享内存的使用, 或者等效地向 faketime 传递 --disable-shm 参数。
拦截时间设置调用
编译 libfaketime 时可以使用 CFLAG “-DFAKE_SETTIME”, 以便同时拦截时间设置函数,即 clock_settime()、 settimeofday() 和 adjtime()。每次调用时,环境变量 FAKETIME 都会被调整。
当设置了环境变量 FAKETIME_TIMESTAMP_FILE,并且它指向一个 可写(可创建)的自定义配置文件,同时环境变量 FAKETIME_UPDATE_TIMESTAMP_FILE 为 “1” 时,该文件也会随每次调用更新。 由此,多个进程可以共享同一套“虚拟时间”, 并且每个进程都可以为所有进程调整时间。
在独立进程间共享“虚拟可设定时间”
当 libfaketime 使用 FAKETIME_COMPILE_CFLAGS="-DFAKE_SETTIME" 编译后, 可以配置为支持多个进程共享同一个时间偏移量。 例如,这允许普通用户使用 “ntpdate” 而不影响系统时钟, 以不同日期交互式测试软件,或测试多个独立进程且这些进程内部使用 settime 的复杂软件。
示例:
$ export LD_PRELOAD=libfaketime.so.1 $ export FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc" $ export FAKETIME_UPDATE_TIMESTAMP_FILE=1 $ export FAKETIME_CACHE_DURATION=1 # 以秒为单位
或:export FAKETIME_NO_CACHE=1
$ date -s "1999-12-24 16:00:00" Fri Dec 24 16:00:00 CET 1999 $ LD_PRELOAD="" date Thu Apr 9 15:19:38 CEST 2020 $ date Fri Dec 24 16:00:02 CET 1999 $ /usr/sbin/ntpdate -u clock.isc.org 9 Apr 15:18:37 ntpdate[718]: step time server xx offset 640390517.057257 sec $ date Thu Apr 9 15:18:40 CEST 2020
在另一个终端、脚本或环境中,可以设置相同的变量, 并打印相同的时间。 这同样避免了为使用不同时间而直接更新 rc 配置文件的需要, 但当然只支持时间偏移量。
请注意,该功能与若干其他功能不兼容, 例如 FAKETIME_FOLLOW_FILE、FAKETIME_XRESET,以及其他可能的功能。 首次 settime 之后,FAKETIME_TIMESTAMP_FILE 中将使用偏移量,即使它 最初使用的是高级时间指定选项。
4f) 在系统范围内伪造日期和时间
SourceForge, Inc. 的 David Burley 报告了将 libfaketime 应用于系统范围的一个有趣用例:目前,所有运行在 OpenVZ 主机内的虚拟机都具有相同的系统日期和时间。为了使用多个 系统日期不同的沙箱,可以将 libfaketime 库放入 /etc/ld.so.preload;之后,它会自动应用到所有命令和程序。 这当然最好与系统范围的 /etc/faketimerc 文件一起使用。感谢 SourceForge, Inc. 提供的补丁!
注意:如果运行虚拟机,重启时其实时时钟可能会重置为现实世界 的日期和时间。根据你的 FAKETIME 设置, 这可能带来副作用,例如每次重启都强制进行文件系统检查。 系统范围的伪造时间也可能导致软件自动更新工具 出现意外的副作用,如果现实世界时间与伪造系统时间 之间的偏移量过大。如有疑问,请将系统日期设置为伪造时间, 先验证一切是否仍按预期工作,然后再在系统范围内应用 libfaketime。
4g) 使用 “faketime” 封装器
从 0.8 版本开始,libfaketime 提供了一个名为 “faketime” 的命令, 它会在 “make install” 时安装到 /usr/bin。它省去了手动设置 LD_PRELOAD 和 FAKETIME 环境变量的麻烦,但只开放了 libfaketime 功能的一部分。另一方面,它借助 /bin/date 的日期解析功能, 从而在指定伪造日期和时间方面提供更高的灵活性。例如,你可以使用
faketime 'last Friday 5 pm' /your/command/here
当然,也可以使用绝对日期,例如:
faketime '2018-12-24 08:15:42' /bin/date
感谢 Daniel Kahn Gillmor 提供这些建议!
Balint Reczey 在 0.9.5 中将该封装器从一个简单的 Shell 脚本 重写为一个高效的封装器程序。
4h) 基于已用时间或调用次数“限制” libfaketime
从 0.9 版本开始,libfaketime 可以配置为不持续启用, 而仅在某个特定时间区间内启用。
例如,你可能希望程序以当前真实时间启动,但在使用 5 分钟后, 让它看到一个伪造时间,例如一年之后的时间。
也可以通过以下方式动态更改伪造时间:
-
在运行时更改 FAKETIME 环境变量;如果你作为程序员使用 libfaketime 进行调试和测试,这是首选方式,因为它能让你最直接地控制 libfaketime, 且不会带来任何性能损失。
-
不使用 FAKETIME 环境变量,而是在文件(如 ~/.faketimerc)中指定伪造时间。 你可以在运行时更改该文件的内容。这种方式在禁用缓存时效果最好, 但会带来性能开销,因为每次都需要读取并解析该文件。
这里描述的功能基于两对环境变量,
FAKETIME_START_AFTER_SECONDS 和 FAKETIME_STOP_AFTER_SECONDS,以及 FAKETIME_START_AFTER_NUMCALLS 和 FAKETIME_STOP_AFTER_NUMCALLS
这些环境变量的默认值均为 -1,表示“忽略该值”。
如果你希望 libfaketime 仅在应用运行后的第 2 到第 5 分钟内启用, 请设置
FAKETIME_START_AFTER_SECONDS=60 FAKETIME_STOP_AFTER_SECONDS=300
这意味着你的应用将从启动(第 0 秒)开始到第 60 秒使用真实时间。 随后,从运行第 60 秒到第 300 秒(第 2、3、4 和 5 分钟), 它将看到伪造时间。在运行第 600 秒后,它又会看到 真实(未伪造)时间。
这种方式不如在运行时更改 FAKETIME 环境变量灵活,但可能更容易使用, 作用于单个程序范围(而不是按用户或系统范围),且只带来轻微的性能开销。
使用另一对环境变量,你可以基于被启动程序执行的时间相关函数调用次数, 而不是实际经过的秒数,来限制 libfaketime 的活动时间。这种替代方式 可能只适合那些了解程序代码以便确定有用的启动/停止值, 或想要执行模糊测试的程序员。
两对环境变量可以组合使用,以进一步限制 libfaketime 的活动,尽管这种用法只在极少数场景中有用。
通常不建议以这种方式限制 libfaketime 的活动。许多 程序在遭遇时间突变时会出错,尤其是当它们以当前(真实)时间启动, 随后在例如 5 分钟后被送回过去时。例如,它们可能看似冻结 或卡住,因为在等待某个时间点,然而由于 libfaketime 延迟启用, 该时间点永远不会到达。除非你确定确实需要该功能并清楚 自己在做什么,否则请避免使用此功能。
4i) 按进程“限制”libfaketime
faketime 可以被配置为仅对指定命令模拟时间相关调用,或者对除某些命令以外的每条命令模拟时间。
环境变量分别为 FAKETIME_ONLY_CMDS 和 FAKETIME_SKIP_CMDS。
示例: FAKETIME_ONLY_CMDS=javadoc faketime '2008-12-24 08:15:42' make 这将运行 “make” 命令,但时间模拟仅应用于 javadoc 进程。
多个命令之间使用逗号分隔。
示例: FAKETIME_SKIP_CMDS="javadoc,ctags" faketime '2008-12-24 08:15:42' make 这将运行 “make” 命令,并对 “make” 所执行的所有内容应用时间模拟,但 javadoc 和 ctags 进程除外。
FAKETIME_ONLY_CMDS 和 FAKETIME_SKIP_CMDS 互斥,即不能同时设置二者。如果两个环境变量都被设置,faketime 将打印错误信息并终止。
4j) 启动外部进程
从 0.9 版本开始,libfaketime 可以在 a) 经过任意秒数,或 b) 程序自启动以来已经发起一定数量的时间相关系统调用之后,执行一次 shell 命令。需要注意,这有两个限制:
-
启动外部进程发生在原始程序的一次时间相关系统调用期间。如果希望外部进程在程序启动 5 秒后启动,但该程序在运行到第 8 秒前都没有发起任何时间相关系统调用,外部进程的启动就会被延迟到运行时间的第 8 秒。
-
原始程序会被阻塞,直到外部进程结束,因为被拦截的时间相关系统调用不会提前返回。如果需要启动一个长时间运行的外部进程,请确保其通过 fork 转入后台。
启动外部进程由以下三个环境变量控制: FAKETIME_SPAWN_TARGET、FAKETIME_SPAWN_SECONDS、FAKETIME_SPAWN_NUMCALLS。
安全警告:FAKETIME_SPAWN_TARGET 会被传递给 shell。这是有意设计的命令执行功能,并不是安全的参数传递接口。当环境变量可能受到不可信用户影响时,请勿启用它;也不要将其用于 setuid/setgid 程序、特权守护进程或其他安全敏感进程。这些使用场景需要无 shell 的基于 argv 的接口。该接口通过 FAKETIME_SPAWN_EXEC 以及 FAKETIME_SPAWN_ARG_1 至 FAKETIME_SPAWN_ARG_8 提供。FAKETIME_SPAWN_EXEC 会在不经过 shell 的情况下直接执行指定文件;带编号的参数必须连续,并且基于 shell 的 FAKETIME_SPAWN_TARGET 与 FAKETIME_SPAWN_EXEC 设置不能组合使用。
示例(在 Linux 上使用 bash):
(... 此处为通常的 libfaketime 配置,设置 LD_PRELOAD 和 FAKETIME ...) export FAKETIME_SPAWN_TARGET="/bin/echo 'Hello world'" export FAKETIME_SPAWN_SECONDS=5 /opt/local/bin/myprogram
这将在 “myprogram” 运行满 5 秒后,于其第一次执行时间相关系统调用期间,使用给定参数运行 “echo” 命令。
4k) 将时间戳保存到文件,以及从文件加载时间戳
要通过 同一个 文件存储和加载时间戳 偏移量,以便在独立进程之间共享公共“虚拟时间”,请参阅上文中的 FAKETIME_UPDATE_TIMESTAMP_FILE。FAKETIME_SAVE_FILE 功能则有所不同。
faketime 可以将模拟时间戳保存到由 FAKETIME_SAVE_FILE 环境变量指定的文件。它也可以使用 FAKETIME_LOAD_FILE 指定的文件来回放其中的时间戳。在处理完整个文件后,libfaketime 将恢复使用 FAKETIME 变量中设置的规则,但时间戳进程 将从文件中的最后一个时间戳开始计数。
该文件将每个时间戳保存为 saved_timestamp 结构体流, 不包含任何元数据或填充:
/* 写入文件的时间戳存储格式。大端序。 */ struct saved_timestamp { int64_t sec; uint64_t nsec; };
若要使用这些文件,faketime 必须通过 faketime 封装器运行。此 功能由 Balint Reczey 在 v0.9.5 中添加。
4l) 用确定性数字序列替换随机数
编译 libfaketime 时可设置 CFLAG FAKE_RANDOM(参见 src/Makefile)。
此可选功能目前仅在 Linux 上受支持。 请勿在 macOS 构建中启用 FAKE_RANDOM:Darwin 的 getentropy() 拦截路径 无法安全调用原始系统实现,并可能在进程启动期间递归或 中止。 Darwin 支持仍是后续工作。
以这种方式编译后,libfaketime 还会拦截对 函数 getrandom() 的调用,该函数是 Linux 专有的。
此功能旨在向使用 getrandom() 的应用程序提供一系列确定性、可重复 的数字,而不是 /dev/[u]random 提供的随机数。
为了生成确定性数字序列,libfaketime 内部 使用 Bernard Widynski 的 Middle Square Weyl Sequence Random Number Generator, 参见 https://mswsrng.wixsite.com/rand。
它需要一个 64 位种子值,必须通过环境变量 FAKERANDOM_SEED 传递,例如
LD_PRELOAD=src/libfaketime.so.1
FAKERANDOM_SEED="0x12345678DEADBEEF"
test/getrandom_test
只要使用相同的种子值,就会生成相同的“看似随机” 数字序列。
请注意,这必然会破坏 getrandom() 所提供的随机数可能具有的任何安全属性,例如在 密码学操作场景中。仅将其用于确定性测试 目的。切勿在生产环境中使用。
可选 FAKE_PID 功能仅在 Linux 上受支持。在 macOS 构建中提供 FAKETIME_FAKEPID 可能使进程启动不稳定并导致 中止,因为 Darwin 运行时代码依赖真实进程身份。 请勿在 macOS 上启用它;Darwin 支持仍是后续工作。
关于为何将这一看似与日期/时间无关的函数 加入 libfaketime,以及它可能如何演变,请参阅 Github issue #275。
- 许可证
libfaketime 已按照 GNU General Public License, GPL 发布。 请参阅随附的 COPYING 文件。
- 联系方式
非常欢迎提交错误报告、功能建议、成功反馈以及补丁/合并请求:
https://github.com/wolfcw/libfaketime
项目介绍
time-warping功能使单个应用程序的时间扭曲成为可能libfaketime通过修改单个应用程序的系统时间,实现了时间扭曲的功能。该功能让单个应用程序的时间调整变得可行。【此简介由AI生成】
定制我的领域