我可以在 C 程序中拦截正常的 stdio 调用,做一些工作然后调用原始的吗?

Can I intercept normal stdio calls in a C program, do some work and then call the original ones?

我有一堆 C 文件,它们尝试使用 fread()fwrite()fseek() 等 stdio 函数从磁盘读取和写入 CSV 和其他随机数据。 (如果重要的话,这是一项大学作业,我们将使用不同的块大小和各种结构来试验 IO 性能以跟踪磁盘文件上的数据等。)

我想做的是编译这些源文件(有几十个) 没有来自 <stdio.h>fopen()fread()fwrite() 的定义。我想提供我自己的 fopen()fread()fwrite(),我可以在其中跟踪一些信息,例如哪个进程试图读取哪个文件,以及读取了多少 blocks/pages 等等像那样,然后调用正常的 stdio 函数。

我不想遍历每个文件的每一行并将 fopen() 更改为 my_fopen() ....有没有更好的方法可以在编译时执行此操作?

我正在开发一个 Python 程序,该程序扫描源文件并使用我的函数更改这些调用,但它变得有点混乱,我有点迷路了。我想也许有更好的方法来做到这一点;如果你能给我指出正确的方向,比如搜索什么就太好了。

此外,我不想使用一些 Linux 分析工具来报告哪些系统调用发生在何处;我只想在调用这些函数之前执行一些代码。

不但是是但是不是。据我所知,最好的方法是 LD_PRELOAD 一个提供您自己版本的这些函数的库。您可以通过 dlopening libc.so 获取原件(获取 libc 函数的 dlopen NULL 技巧在这里不适用,因为您的库已经加载)。

LD_PRELOAD 技巧的替代方法(需要您编写一个单独的库并且仅适用于 Linux),您可以使用 GNU [=28] 的 --wrap 选项=]呃。有关此技术的示例,请参阅 here

与LD_PRELOAD的主要区别:

  • 不需要外部库 - 都在可执行文件中;
  • 不需要运行时选项;
  • 只要您使用 GNU 工具链,就可以在任何平台上工作;
  • 仅适用于在 link 时解析的调用 - 动态库仍将使用原始函数

另一种方法:将 -Dfread=my_fread 添加到 Makefile CFLAGS 中,用于任何您希望 "spy" 的 .o 文件。添加 my_fread.o 定义 my_fread [其中有 no -D 技巧].

对您希望拦截的任何函数重复上述操作。与 LD_PRELOAD 大致相同 [在有效性方面可能更容易实施]。我都做过。

或者创建一个 my_func.h 来定义并在每个文件中插入一个 #include "my_func.h"。经销商的选择

更新

忘记了另一种方式。正常编译。破坏目标 .o 的 [symbol table] 中的符号名称(通过自定义程序或 ELF/hex 编辑器):将 fread 更改为长度相同且不与任何内容冲突的内容[你可以控制]。目标名称:qreadfrea_ 或其他名称。

使用新名称添加您的拦截 .o。

这看起来 "dirty",但我们在这里 一项 "dirty" 工作。这是我用于 .o 的 "old school" [叹气:-)] 方法,我没有源代码并且在 LD_PRELOAD 存在之前。

一种方法是重新定义您需要的所有 stdio 函数。 fopen 变为 my_fopen,fread 变为 my_fread,然后让您的 my_fopen 调用 fopen。这可以在一个头文件中完成,该文件包含在要替换对 fopen 的调用的文件中。请参阅下面的示例。

main.c:

#include <stdio.h>
#include "my_stdio.h"

int main(void)
{
  FILE *f;
  char buf[256];
  f = fopen("test.cvs", "r");
  if(f == NULL)
  {
      printf("Couldn't open file\n");
      return 1;
  }
  fread(buf, sizeof(char), sizeof(buf), f);
  fclose(f);
  return 0;
}

my_stdio.c:

#include <stdio.h>

FILE *my_fopen(const char *path, const char *mode)
{
  FILE *fp;
  printf("%s before fopen\n", __FUNCTION__);
  fp = fopen(path,mode);
  printf("%s after fopen\n", __FUNCTION__);
  return fp;
}

int my_fclose(FILE *fp)
{
  int rv;

  printf("%s before fclose\n", __FUNCTION__);
  rv = fclose(fp);
  printf("%s after fclose\n", __FUNCTION__);
  return rv;
}

size_t my_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  size_t s;

  printf("%s before fread\n", __FUNCTION__);
  s = fread(ptr,size,nmemb,stream);
  printf("%s after fread\n", __FUNCTION__);
  return s;
}

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  size_t s;

  printf("%s before fwrite\n", __FUNCTION__);
  s = fwrite(ptr,size,nmemb,stream);
  printf("%s after fwrite\n", __FUNCTION__);
  return s;
}

my_stdio.h:

#ifndef _MY_STDIO_H_
#define _MY_STDIO_H_

#define fopen my_fopen
#define fclose my_fclose
#define fread my_fread
#define fwrite my_fwrite

#endif /* _MY_STDIO_H_ */

生成文件:

main: main.o my_stdio.o
    $(CC) -g -o $@ main.o my_stdio.o

main.o: main.c
    $(CC) -g -c -o $@ $<

my_stdio.o: my_stdio.c my_stdio.h
    $(CC) -g -c -o $@ $<