【问题标题】:Can I intercept normal stdio calls in a C program, do some work and then call the original ones?我可以在 C 程序中拦截正常的 stdio 调用,做一些工作然后调用原始的吗?
【发布时间】:2016-02-19 18:13:06
【问题描述】:

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

我想做的是编译这些源文件(有几十个) 没有来自<stdio.h>fopen()fread()fwrite() 的定义。我想提供我自己的fopen()fread()fwrite() 来跟踪一些信息,比如哪个进程试图读取哪个文件,以及读取了多少块/页面等等,然后调用正常标准输出函数。

我不想遍历每个文件的每一行并将fopen() 更改为my_fopen() .... 在编译时有更好的方法吗?

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

另外,我不想使用一些 Linux 性能分析的东西来报告哪些系统调用是在哪里进行的,哪些不是;我只是想在调用这些函数之前执行一些代码。

【问题讨论】:

    标签: c gcc makefile stdio


    【解决方案1】:

    除了 LD_PRELOAD 技巧(它要求您编写一个单独的库并且仅适用于 Linux),您可以使用 GNU 链接器的 --wrap 选项。有关此技术的示例,请参阅here

    与 LD_PRELOAD 的主要区别:

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        一种方法是重新定义您需要的所有 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 $@ $<
        

        【讨论】:

        • gcc -Dfopen=my_fopen 来说是个坏主意,&lt;stdio.h&gt;&lt;bits/stdio.h&gt; 中的fopen 令牌将被my_fopen 替换。一个更好的主意是将#define fopen my_fopen 放在my_stdio.h 中,它应该始终包含在&lt;stdio.h&gt; 之后
        • 但这需要更改所有使用 stdio.h 的文件。为什么在 stdio.h 中将 fopen 替换为 my_fopen 会有问题?代码还是会调用 my_fopen?
        • @Puppe:在许多标准头文件中看到了宏机制,我不愿意替换其中的任何内容,当然也不是标准函数的名称。
        • 我看到了你的观点,已经更新了我的示例,改为在 my_stdio.h 中重新定义。
        【解决方案4】:

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

        对您希望拦截的任何函数重复上述操作。与LD_PRELOAD 大致相同[就有效性而言,可能更易于实施]。我都做了。

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

        更新

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

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

        这可能看起来“肮脏”,但我们在这里所做的一项“肮脏”的工作。这是我用于 .o 的“老派”[叹气 :-)] 方法,我没有源代码并且在 LD_PRELOAD 存在之前。

        【讨论】:

        • “与 LD_PRELOAD 差不多”不,这是一种完全不同的技术(粗暴的文本替换与更改加载器的工作方式),具有不同的适用性和不同的缺点。说它们大致相同是严重误导。
        • @MatteoItalia 我不认为这就是他的意思。无论如何,这是一种有效的方法,并且在某些方面更简单。最大的警告是它不会拦截库进行的函数调用,除非它们也被重建。
        • @MatteoItalia 在有效性方面它们大致相同。 “残酷”的文本替换实际上很容易。如果您执行 LD_PRELOAD,则必须执行 dlopendlsym [在您的拦截函数中],这实际上需要更多的工作并且您必须维护状态信息。顺便说一句,其他人提出了相同的解决方案并得到了接受的答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-23
        • 1970-01-01
        • 2014-04-06
        • 1970-01-01
        相关资源
        最近更新 更多