背景
我帮助维护一个简单的命令行工具diskmanager
,用于监视磁盘性能差,这主要是由于同时使用同一磁盘的操作/用户太多。我的工作包括维护一个库libdisksupervisor.so
,该库偶尔用于通过以下方式启动磁盘管理器程序来“监督”磁盘管理器程序:
LD_PRELOAD=/public/libdisksupervisor.so /sbin/diskmanager
我们这样做的原因是因为库和应用程序有非常不同的发布计划,源代码由于跨NDA而不能共享,等等。为了让我们的生活更轻松,
diskmanager
的维护人员在应用程序中创建了几个extern
变量,并添加了一些对库中的“dummy”函数的调用(libdonothing.so
),这些函数与diskmanager
捆绑在一起。当调用
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
的一个维护人员做了一些愚蠢的事情,并从int internalStatus
中删除了diskmanager
,这导致我们的库在执行LD_PRELOAD=/public/libdisksupervisor.so diskmanager
时出现分段错误。当一名初级工程师在摸索GCC隐藏属性并将某些值更改为static
时,也出现了类似的问题,并再次导致segfault。问题
在
libdisksupervisor.so
中的代码中,我们是否可以在继续之前测试这些extern
(从库的角度)变量的存在,可能是通过某种神秘的链接器或GCC魔术?我知道作为预验证脚本的一部分,我可以向它抛出nm
或objdump
,但我们需要在c库中单独完成这项工作。谢谢您。
最佳答案
在libdisksupervisor.so中的代码中,是否有任何方法可以在继续之前测试这些外部(从库的角度)变量的存在,可能是通过某种神秘的链接器或GCC魔术?
你有时间问题。事实上,在链接所针对的diskmanager
版本中,您不需要做任何特殊的事情来测试这些符号在编译时的存在性和可见性。当您试图将libdisksupervisor.so
与运行时发现不兼容的diskmanager
版本一起使用时,就会出现问题。
我知道我可以作为预验证脚本的一部分向它抛出nm或objdump,但是我们需要在我们的c库中单独完成这项工作。
我不知道有什么方法可以与您运行程序的方式配合使用,也不容易被diskmanager
维护轻易和意外地挫败。
但是也许有一种方法涉及到改变你运行程序的方式。如果您现在所称的libdisksupervisor.so
提供了一个程序入口点(即main()
),并且您直接运行它,它可以dlopen()
diskmanager
并通过dlsym()
检查所需符号的存在。然后它可以将控制权转移到diskmanager
的main()
(也可以通过dlsym()
访问)。您可以将其视为在系统的动态链接器和diskmanager
之间插入一个填充。
更新:
好消息是,我有一个概念证明,证明这是可以做到的(见下文)。坏消息是,要将主可执行文件作为共享库加载,需要特殊的生成选项,让另一方使用这些选项生成文件听起来可能很麻烦。另一方面,这种方法允许他们精确地控制和记录哪些符号暴露在你的身边,也许这会是一个合适的选择。
总之,PoC由三个C源文件、两个辅助文件和一个MaMo文件组成:
假c
int dummy(void) {
return 0;
}
主c
#include <stdio.h>
int dummy(void);
#ifndef BREAKME
int internalStatus = 42;
#endif
int main(int argc, char *argv[]) {
printf("dummy() returns %d\n", dummy());
return 0;
}
垫片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\n", 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\naborting\n", 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
主程序动态
{
main; internalStatus;
};
动态垫片
{
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
文件告诉链接器应该包含在导出(动态)符号中的两个可执行文件中的符号,即使链接中没有引用它们。这种方法不允许shim直接引用主程序的
internalStatus
变量,因为shim需要将主程序链接为一个库,并且当shim运行时,它将由动态链接器自动加载。对变量的引用总是立即绑定的,因此如果internalStatus
消失在填充程序的控制之外,则会导致动态链接器出错。关于c - C-外部元素-监控LD_PRELOAD的库中值的安全方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39793363/