通过 soname 插入共享对象的一部分
Interposing part of a shared object by soname
我已经编写了一个共享对象来修改 FreeType 的 FT_Load_Glyph
和 FT_Render_Glyph
函数的参数,目前通过将其插入 LD_PRELOAD
和 dlsym
。
这很好用,但我很想知道是否有办法进行这些更改:
- 给定主机上使用 FreeType 的所有程序(运行 例如 Debian);
- 不会破坏任何实际上未 link 为 FreeType 的程序;
- 无需简单地将
LD_PRELOAD
应用于主机上的所有程序;
- 无需任何维护,除非更改了 FreeType 的 soname;和
- 不修改 FreeType 的任何文件,也不修改主机上任何程序的文件。
我能想出的唯一两个“解决方案”是丑陋的黑客:
- 到
LD_PRELOAD
所有的程序,所有的时间,这看起来缓慢而脆弱;或者
- 复制例如
libfreetype.so.6.12.3
到 libxxxxtype.so.6.12.3
;然后
- 将
libxxxxtype.so.6.12.3
中的 soname 修补为 libxxxxtype.so.6
;
- link针对
libxxxxtype.so.6
设置共享对象;和
- 安装共享对象,例如
libfreetype.so.6.999
.
我基本上想透明地修补共享对象中的几个函数,同时让其余函数通过,而不必访问共享对象的源代码或使用它的程序,但是如果我用 soname libfreetype.so.6
创建一个假的共享对象,我看不到一个干净的方法来 link 它到(或 dlopen
)真正的 libfreetype.so.6
.
这是我第一次使用共享库进行真正的实验,所以如果这个问题做出了一些不正确的假设,或者根本没有意义,请多多包涵。
另一种解决方案是为 lib 创建系统范围 "overlay",使用自定义 libfreetype,然后将未修改的方法代理到真正的 lib。
您必须使自定义库与真实库兼容。您可以通过使用 dlopen
和 绝对路径 (例如 dlopen("/usr/lib64/libfreetype.so.6")
),
复制真实的导出函数的定义并使用 dlsym
代理它们。
它认为为了便于维护,您可以将代理参数类型替换为简单的 void*
。您只需要在 freetype 函数更改(参数计数、函数名称)时进行更改。
要创建库 "overlay",您可以将自定义库安装到例如。 “/opt/myapp/lib64/libfreetype.so.6”,然后将此路径添加到动态链接器 运行 时间路径。
如果原始实现发生变化,您可能必须为其他版本创建符号链接或编译新的自定义库。隐藏真实库并保持其他应用程序正常运行所需的一切:)
Google 表示要在 Debian 上更改 运行 时间加载路径,您只需编辑 /etc/ld.so.conf
。在开头添加 /opt/myapp/lib64
路径,以便首先检查它。
现在任何搜索 freetype 的应用程序都应该加载您的库,您可以使用 ldd <path to app>
.
检查它
我能想到只有一种情况下此解决方案不起作用:如果应用正在加载捆绑的 libfreetype 或通过完整路径而不是名称加载它。
to LD_PRELOAD all programs, all of the time, which seems slow and fragile
这是一个很好的解决方案(满足您的需求)。我没有看到更好的。
它并不脆弱。它以记录的方式向运行时链接程序提供信息。你不是在胡思乱想,假装事情不是它的本来面目。您只是在更改函数名称解析的首选项层次结构。
不慢。链接器有时必须做一些事情。它必须检查是否定义了 LD_PRELOAD
,这在任何情况下都是 user-space 操作。因此它将遵循该路径,并在执行大量其他工作之前加载您的库。如果在正常情况下甚至可以测量时间,我会感到惊讶。
我有两个顾虑,但它们与技术正交。该代码实际上必须在所有情况下都能正常工作,并且您必须稍微深入研究流程创建框架以确保 LD_PRELOAD
确实在 无处不在 中定义。除此之外,ld.so 为您的预期用途精确定义了它的环境变量。谁来争论?
您可以尝试使用 uprobes
从某些函数中动态窃取控制权吗?
勾选http://www.brendangregg.com/blog/2015-06-28/linux-ftrace-uprobe.html
uprobes: user-level dynamic tracing, which was added to Linux 3.5 and improved in Linux 3.14. It lets you trace user-level functions; for example, the return of the readline() function from all running bash shells, with the returned string:
# ./uprobe 'r:bash:readline +0($retval):string'
Tracing uprobe readline (r:readline /bin/bash:0x8db60 +0($retval):string). Ctrl-C to end.
bash-11886 [003] d... 19601837.001935: readline: (0x41e876 <- 0x48db60) arg1="ls -l"
bash-11886 [002] d... 19601851.008409: readline: (0x41e876 <- 0x48db60) arg1="echo "hello world""
bash-11886 [002] d... 19601854.099730: readline: (0x41e876 <- 0x48db60) arg1="df -h"
bash-11886 [002] d... 19601858.805740: readline: (0x41e876 <- 0x48db60) arg1="cd .."
bash-11886 [003] d... 19601898.378753: readline: (0x41e876 <- 0x48db60) arg1="foo bar"
^C
Ending tracing...
和http://www.brendangregg.com/blog/2015-07-03/hacking-linux-usdt-ftrace.html
还有其他跟踪用户功能的解决方案-space,如 ftrace、systemtap、dtrace、lttng。其中一些需要重新编译并在程序中静态定义跟踪点;和 uprobes 是 "user-level dynamic tracing".
关于 Uprobes 的一些 links:
- https://events.linuxfoundation.org/slides/lfcs2010_keniston.pdf Uprobes:用户-Space Probes (2010)
- https://www.kernel.org/doc/Documentation/trace/uprobetracer.txt
- https://lwn.net/Articles/499190/ "Uprobes in 3.5",乔纳森·科贝特 (2012)
有 handler
个 uprobe,其中 pt_regs
。正如最后 link 所说:“Uprobes 因此实现了一种机制,只要进程执行特定指令位置,就可以通过该机制调用内核函数。”,它表明 uprobes 可能替换一些基于 ptrace/gdb 的解决方案;所以有可能通过改变它的 eip/rip (PC) register.
来改变任何程序的执行。
您可以尝试其他一些动态检测工具,例如 pin
或 dyninst
;但它们是为每个进程使用而设计的。
我已经编写了一个共享对象来修改 FreeType 的 FT_Load_Glyph
和 FT_Render_Glyph
函数的参数,目前通过将其插入 LD_PRELOAD
和 dlsym
。
这很好用,但我很想知道是否有办法进行这些更改:
- 给定主机上使用 FreeType 的所有程序(运行 例如 Debian);
- 不会破坏任何实际上未 link 为 FreeType 的程序;
- 无需简单地将
LD_PRELOAD
应用于主机上的所有程序; - 无需任何维护,除非更改了 FreeType 的 soname;和
- 不修改 FreeType 的任何文件,也不修改主机上任何程序的文件。
我能想出的唯一两个“解决方案”是丑陋的黑客:
- 到
LD_PRELOAD
所有的程序,所有的时间,这看起来缓慢而脆弱;或者 - 复制例如
libfreetype.so.6.12.3
到libxxxxtype.so.6.12.3
;然后- 将
libxxxxtype.so.6.12.3
中的 soname 修补为libxxxxtype.so.6
; - link针对
libxxxxtype.so.6
设置共享对象;和 - 安装共享对象,例如
libfreetype.so.6.999
.
- 将
我基本上想透明地修补共享对象中的几个函数,同时让其余函数通过,而不必访问共享对象的源代码或使用它的程序,但是如果我用 soname libfreetype.so.6
创建一个假的共享对象,我看不到一个干净的方法来 link 它到(或 dlopen
)真正的 libfreetype.so.6
.
这是我第一次使用共享库进行真正的实验,所以如果这个问题做出了一些不正确的假设,或者根本没有意义,请多多包涵。
另一种解决方案是为 lib 创建系统范围 "overlay",使用自定义 libfreetype,然后将未修改的方法代理到真正的 lib。
您必须使自定义库与真实库兼容。您可以通过使用 dlopen
和 绝对路径 (例如 dlopen("/usr/lib64/libfreetype.so.6")
),
复制真实的导出函数的定义并使用 dlsym
代理它们。
它认为为了便于维护,您可以将代理参数类型替换为简单的 void*
。您只需要在 freetype 函数更改(参数计数、函数名称)时进行更改。
要创建库 "overlay",您可以将自定义库安装到例如。 “/opt/myapp/lib64/libfreetype.so.6”,然后将此路径添加到动态链接器 运行 时间路径。 如果原始实现发生变化,您可能必须为其他版本创建符号链接或编译新的自定义库。隐藏真实库并保持其他应用程序正常运行所需的一切:)
Google 表示要在 Debian 上更改 运行 时间加载路径,您只需编辑 /etc/ld.so.conf
。在开头添加 /opt/myapp/lib64
路径,以便首先检查它。
现在任何搜索 freetype 的应用程序都应该加载您的库,您可以使用 ldd <path to app>
.
我能想到只有一种情况下此解决方案不起作用:如果应用正在加载捆绑的 libfreetype 或通过完整路径而不是名称加载它。
to LD_PRELOAD all programs, all of the time, which seems slow and fragile
这是一个很好的解决方案(满足您的需求)。我没有看到更好的。
它并不脆弱。它以记录的方式向运行时链接程序提供信息。你不是在胡思乱想,假装事情不是它的本来面目。您只是在更改函数名称解析的首选项层次结构。
不慢。链接器有时必须做一些事情。它必须检查是否定义了
LD_PRELOAD
,这在任何情况下都是 user-space 操作。因此它将遵循该路径,并在执行大量其他工作之前加载您的库。如果在正常情况下甚至可以测量时间,我会感到惊讶。
我有两个顾虑,但它们与技术正交。该代码实际上必须在所有情况下都能正常工作,并且您必须稍微深入研究流程创建框架以确保 LD_PRELOAD
确实在 无处不在 中定义。除此之外,ld.so 为您的预期用途精确定义了它的环境变量。谁来争论?
您可以尝试使用 uprobes
从某些函数中动态窃取控制权吗?
勾选http://www.brendangregg.com/blog/2015-06-28/linux-ftrace-uprobe.html
uprobes: user-level dynamic tracing, which was added to Linux 3.5 and improved in Linux 3.14. It lets you trace user-level functions; for example, the return of the readline() function from all running bash shells, with the returned string:
# ./uprobe 'r:bash:readline +0($retval):string'
Tracing uprobe readline (r:readline /bin/bash:0x8db60 +0($retval):string). Ctrl-C to end.
bash-11886 [003] d... 19601837.001935: readline: (0x41e876 <- 0x48db60) arg1="ls -l"
bash-11886 [002] d... 19601851.008409: readline: (0x41e876 <- 0x48db60) arg1="echo "hello world""
bash-11886 [002] d... 19601854.099730: readline: (0x41e876 <- 0x48db60) arg1="df -h"
bash-11886 [002] d... 19601858.805740: readline: (0x41e876 <- 0x48db60) arg1="cd .."
bash-11886 [003] d... 19601898.378753: readline: (0x41e876 <- 0x48db60) arg1="foo bar"
^C
Ending tracing...
和http://www.brendangregg.com/blog/2015-07-03/hacking-linux-usdt-ftrace.html
还有其他跟踪用户功能的解决方案-space,如 ftrace、systemtap、dtrace、lttng。其中一些需要重新编译并在程序中静态定义跟踪点;和 uprobes 是 "user-level dynamic tracing".
关于 Uprobes 的一些 links:
- https://events.linuxfoundation.org/slides/lfcs2010_keniston.pdf Uprobes:用户-Space Probes (2010)
- https://www.kernel.org/doc/Documentation/trace/uprobetracer.txt
- https://lwn.net/Articles/499190/ "Uprobes in 3.5",乔纳森·科贝特 (2012)
有 handler
个 uprobe,其中 pt_regs
。正如最后 link 所说:“Uprobes 因此实现了一种机制,只要进程执行特定指令位置,就可以通过该机制调用内核函数。”,它表明 uprobes 可能替换一些基于 ptrace/gdb 的解决方案;所以有可能通过改变它的 eip/rip (PC) register.
您可以尝试其他一些动态检测工具,例如 pin
或 dyninst
;但它们是为每个进程使用而设计的。