在 linux 中使用 LD_PRELOAD 挂钩 strcmp?

Hook strcmp using LD_PRELOAD in linux?

因为我要hook strcmp,代码如下:

#include <stdio.h>
#include <string.h>


int strcmp(const char *s1, const char *s2)
{
    printf("hooked strcmp\n");
    return 0;
}

// gcc test.c -shared -fPIC -o libtest.so

#include <stdio.h>
#include <string.h>
#include "test.h"

int main(int argc, char *argv[])
{
    strcmp(argv[1], "aba");         // didn't call strcmp in libtest
    int i = strcmp(argv[1], "aba"); // call strcmp in libtest
}

// gcc main.c
// LD_PRELOAD=./libtest.so ./a.out 12123

我的问题是:为什么 strcmp 在这两个条件下不同?

如果您不使用 strcmp 返回的值,则调用不执行任何操作。所以编译器可以随意删除调用。在某些情况下,编译器也可能内联对 strcmp 的调用,因为它确切地知道 strcmp 的作用。

这是标准所容忍的,它保留了标准库 header 中声明的所有标识符(无论是否实际包含 header):

§7.1.3/1:“在以下任何子条款中具有外部 linkage 的所有标识符 [即标准库 headers]... 始终保留用作外部标识符link年龄。

§7.1.3/2:“如果程序在 保留它的上下文(7.1.4 允许的除外)...行为未定义。

例外情况,根据 §7.1.4/2:"Provided that a library function can be declared without reference to any type defined in a header, it is also permissible to declare the function and use it without including its associated header."


由于定义您自己的 strcmp 显然是未定义的行为(除非您使用独立的编译器),所以应该简单地避免它。但是,在实践中,使用一些常见的编译器可能会以 non-portable 的方式进行。

首先,您必须避免包含 string.h 标准库 header。与 gcc 一起分发的标准 C 库 header 声明 strcmp__attribute__((pure)):

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. Such a function can be subject to common subexpression elimination and loop optimization just as an arithmetic operator would be. These functions should be declared with the attribute pure.

这个声明(它是 C 标准的扩展)让编译器消除对 strcmp 的第一次调用,因为它认为该函数没有 side-effects(这将包括写入 stdout ).即使没有优化,gcc 和 clang 也会消除第一次调用。

您可以说服 clang 编译对 strcmp 的两个调用,您可以简单地通过不包括 string.h header(并自己声明 strcmp 来避免消除pure 属性)。但这对 gcc 来说还不够,因为 gcc 默认情况下会自动包含许多标准库函数的内联版本的声明(可以找到当前列表 here in the GCC manual。)正如最后 link 所提到的,您可以通过将 -fno-builtin-strcmp 添加到命令行(以及不包括标准 header)来抑制自动声明。