C-Externs-监控LD_PRELOAD库中的值的安全方法 [英] C - Externs - Safe way to monitor value in LD_PRELOAD'ed library
问题描述
背景
我帮助维护了一个简单的命令行工具diskmanager
,用于监视糟糕的磁盘性能,这主要是由于同时使用同一磁盘的操作/用户太多所致。我的工作涉及维护一个库,libdisksupervisor.so
,它偶尔用于通过以下方式启动磁盘管理器程序来监督它:
LD_PRELOAD=/public/libdisksupervisor.so /sbin/diskmanager
我们这样做的原因是,库和应用程序的发布计划非常不同,由于存在跨NDA而无法共享源代码等。为了方便我们的工作,diskmanager
的维护人员在应用程序中创建了几个extern
变量,并添加了对它们与diskmanager
捆绑在一起的库(libdonothing.so
)中的";哑元&函数的一些调用。
当调用int dummy(void)
(通常可以在libdonothing.so
中找到,但我们通过LD_PRELOAD
inglibdisksupervisor.so
拦截它,它也包含相同的函数原型)时,我们知道diskmanager
处于可以从我们自己的库中安全地读取extern int internalStatus
(位于diskimager
)的状态。dummy()
的代码非常简单:
# In source for diskmanager
int internalStatus = (-1);
# In libdummy.so
int dummy(void) { return 0; }
# In libdisksupervisor.so
extern int internalStatus;
int dummy(void) { syslog(LOG_ERR, "State:%d", internalStatus);
问题
到目前为止,一切顺利。几个月前,diskmanager
的一个维护者做了一些愚蠢的事情,并从diskmanager
中删除了int internalStatus
,导致我们的库在执行LD_PRELOAD=/public/libdisksupervisor.so diskmanager
时导致分段错误。当一名初级工程师笨手笨脚地处理GCC的隐藏属性并将某些值更改为static
时,也出现了类似的问题,并再次导致段故障。
问题
在libdisksupervisor.so
中的代码中,有没有什么方法可以在继续之前测试extern
(从我们的库的角度来看)这些变量的存在,可能是通过一些神秘的链接器或GCC魔术?我知道我可以将nm
或objdump
作为预验证脚本的一部分抛给它,但我们需要仅在c库中完成此操作。
谢谢。
推荐答案
在libdiskSupervisor中的代码中,有没有什么方法可以在继续之前测试这些外部变量(从我们的库的角度)的存在,可能是通过一些神秘的链接器或GCC魔术?
您在这里遇到了时间问题。事实上,您不需要做任何特殊的事情来测试在编译时,在您所链接的diskmanager
版本中的存在和可见性。当您尝试将libdisksupervisor.so
与运行时不兼容的diskmanager
版本一起使用时,会出现此问题。
我知道我可以将nm或objump作为预验证脚本的一部分抛给它,但我们需要仅在我们的c库中完成这项工作。
我不知道有任何方法适用于您运行程序的方式,并且不容易被diskmanager
维护意外挫败。
但也许有一种方法涉及到更改您运行程序的方式。如果您目前所说的libdisksupervisor.so
提供了一个程序入口点(即main()
),并且您直接运行它,则它可以dlopen()
diskmanager
并通过dlsym()
检查是否存在所需的符号。然后,它可以将控制转移到diskmanager
的main()
(也可以通过dlsym()
访问)。您可以将此视为在系统的动态链接器和diskmanager
之间插入填充程序。
更新:
好消息是,我有一个概念验证证明它是可以做到的(见下文)。坏消息是,将主可执行文件加载为共享库需要特殊的构建选项,而且让另一方使用这些选项进行构建听起来可能很麻烦。另一方面,这种方法允许他们精确地控制和记录哪些符号向您公开,也许这将作为适当的胡萝卜。
总之,POC由三个C源文件、两个辅助文件和一个Makefile组成:
ummy.c
int dummy(void) {
return 0;
}
main.c
#include <stdio.h>
int dummy(void);
#ifndef BREAKME
int internalStatus = 42;
#endif
int main(int argc, char *argv[]) {
printf("dummy() returns %d
", dummy());
return 0;
}
shim.c
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <assert.h>
#define TARGET_PATH "./mainprog"
#define NOT_FOUND_STATUS 127
#define MISSING_SYM_STATUS 126
typedef int (*main_type)(int, char **);
static int *internalStatus_p;
#define internalStatus (*internalStatus_p);
int dummy(void) {
return internalStatus;
}
#define LOAD_SYM(dso, name, var) do {
char *e_;
var = dlsym(dso, name);
e_ = dlerror();
if (e_) {
fprintf(stderr, "%s
", e_);
return MISSING_SYM_STATUS;
}
} while (0)
int main(int argc, char *argv[]) {
void *diskmanager_bin = dlopen(TARGET_PATH, RTLD_LAZY | RTLD_GLOBAL);
char *error;
main_type main_p;
if (!diskmanager_bin) {
fprintf(stderr, "Could not load " TARGET_PATH ": %s
aborting
", dlerror());
return NOT_FOUND_STATUS;
} else {
error = dlerror();
assert(!error);
}
LOAD_SYM(diskmanager_bin, "internalStatus", internalStatus_p);
LOAD_SYM(diskmanager_bin, "main", main_p);
return main_p(argc, argv);
}
#undef LOAD_SYM
mainprog_Dynamic
{
main; internalStatus;
};
shim_Dynamic
{
dummy;
};
生成文件
# sources contributing to a shared library must be built with -fpic or -fPIC
CFLAGS = -fPIC -std=c99
LDFLAGS =
SHLIB_LDFLAGS = -shared
SHLIB_EXTRALIBS = -lc
# Sources contributing to the main program should be built with -fpie or -fPIE
SHMAIN_CFLAGS = -fpie
# The main program must be linked with -pie
SHMAIN_LDFLAGS = -pie
DL_EXTRALIBS = -ldl
LIBDUMMY_SO_VER = 0
LIBDUMMY = libdummy.so.$(LIBDUMMY_SO_VER)
all: mainprog shim
mainprog: main.o $(LIBDUMMY) mainprog_dynamic
$(CC) $(CFLAGS) $(SHMAIN_CFLAGS) $(LDFLAGS) $(SHMAIN_LDFLAGS) -Wl,--dynamic-list=mainprog_dynamic -o $@ $< $(LIBDUMMY) $(SHLIB_EXTRALIBS)
main.o: main.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(SHMAIN_CFLAGS) -c -o $@ $<
libdummy.so.$(LIBDUMMY_SO_VER): libdummy.so
ln -sf $< $@
libdummy.so: dummy.o
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SHLIB_LDFLAGS) -Wl,-soname,libdummy.so.$(LIBDUMMY_SO_VER) $^ $(SHLIB_EXTRALIBS)
shim: shim.o shim_dynamic
$(CC) $(CFLAGS) $(LDFLAGS) -Wl,--dynamic-list=shim_dynamic -o $@ $< $(DL_EXTRALIBS)
test: all
@echo "LD_LIBRARY_PATH=`pwd` ./mainprog :"
@LD_LIBRARY_PATH=`pwd` ./mainprog
@echo "LD_LIBRARY_PATH=`pwd` ./shim :"
@LD_LIBRARY_PATH=`pwd` ./shim
clean:
rm -f *.o *.so *.so.* mainprog shim
这将模拟您所描述的情况,其中您想要覆盖的函数驻留在单独的共享库中。它采用GNU工具链。成功构建示例(make all
)后,您可以make test
观看演示:
$ make test LD_LIBRARY_PATH=/tmp/dl ./mainprog : dummy() returns 0 LD_LIBRARY_PATH=/tmp/dl ./shim : dummy() returns 42
*_dynamic
文件告诉链接器有关两个可执行文件中应包括在导出的(动态)符号中的符号,即使链接中没有引用它们。
internalStatus
变量,因为这样填充程序将需要将主程序链接为库,并且在填充程序运行时动态链接器将自动加载它。对变量的引用始终立即绑定,因此,如果填充程序控制之外的internalStatus
消失,则会导致动态链接器出现错误。
这篇关于C-Externs-监控LD_PRELOAD库中的值的安全方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!