在 gcc 中,有没有办法动态地将函数调用添加到 main() 的开头?

问题描述

通过在我的 malloc() 文件中编写这些函数,我在 glibc 基准 malloc 速度测试 (glibc/benchtests/bench-malloc-thread.c) 中使用我的 fast_malloc() 实现动态覆盖 fast_malloc.c :

// Override malloc() and free(); see: https://stackoverflow.com/a/262481/4561887

inline void* malloc(size_t num_bytes)
{
    static bool first_call = true;
    if (first_call)
    {
        first_call = false;
        fast_malloc_error_t error = fast_malloc_init();
        assert(error == FAST_MALLOC_ERROR_OK);
    }

    return fast_malloc(num_bytes);
}

inline void free(void* ptr)
{
    fast_free(ptr);
}

请注意,我对 malloc() 包装器进行了这种低效的添加,以确保在第一次调用时首先调用 fast_malloc_init(),以初始化一些内存池。如果可能,我想摆脱它并动态地将该 init 调用插入到 main() 的开头,而不修改 glibc 代码。这可能吗?

到目前为止,我编写 malloc() 包装器的方式的缺点是它歪曲了我的基准测试结果,使我的 fast_malloc() 看起来比实际慢,因为 init func 被计时glibc/benchtests/bench-malloc-thread.c我有这个无关的 if (first_call),每次 malloc 调用都会检查它。

目前我动态覆盖 malloc()free(),同时调用 bench-malloc-thread 可执行文件,如下所示:

LD_PRELOAD='/home/gabriel/GS/dev/fast_malloc/build/libfast_malloc.so' \ 
glibc-build/benchtests/bench-malloc-thread 1

情节我将添加我的 fast_malloc() 速度测试(使用 this repo):

enter image description here

我在 LinkedIn 上发布的帖子:https://www.linkedin.com/posts/gabriel-staples_software-engineering-tradeoffs-activity-6815412255325339648-_c8L

相关:

  1. [我的 repo fork] https://github.com/ElectricRCAircraftGuy/malloc-benchmarks
  2. [我如何学习如何在 gcc 中生成 *.so 动态库] https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
  3. Create a wrapper function for malloc and free in C

解决方法

这可能吗?

是的。您正在构建和LD_PRELOAD共享库,共享库可以具有特殊的初始化器和终结器函数,它们分别在库加载和卸载时由动态加载器调用。

正如 kaylum 评论的那样,要创建这样的构造函数,您可以使用 __attribute__((constructor)),如下所示:

__attribute__((constructor))
void fast_malloc_init_ctor()
{
  fast_malloc_error_t error = fast_malloc_init();
  assert(error == FAST_MALLOC_ERROR_OK);
}

// ... the rest of implementation here.

附言

它歪曲了我的基准测试结果,使我的 fast_malloc() 看起来比实际慢,因为 init func 被计时

  1. 您正在与多线程基准进行比较。请注意,您的 static bool fist_call 不是 线程安全的。实际上这无关紧要,因为 malloc 通常在任何线程(主线程除外)存在之前很久就被调用。
  2. 我怀疑这个单一的比较实际上会使您的 fast_malloc() 变慢。即使删除比较,它也可能变慢——编写一个快速的堆分配器需要付出很多努力,聪明的人花了很多年的时间来优化 GLIBC malloc、TCMalloc 和 jemalloc。
,

如何在另一个可执行文件的 main() 函数之前和之后动态注入函数调用。

这是一个完整的、可运行的示例,供任何想要自行测试的人使用。在 Linux Ubuntu 20.04 上测试。

此代码是我的 eRCaGuy_hello_world 存储库的一部分。

hello_world_basic.c:

#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`,`int8_t`,etc.
#include <stdio.h>   // For `printf()`

// int main(int argc,char *argv[])  // alternative prototype
int main()
{
    printf("This is the start of `main()`.\n");
    printf("  Hello world.\n");
    printf("This is the end of `main()`.\n");

    return 0;
}

dynamic_func_call_before_and_after_main.c:

#include <assert.h>
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`,etc.
#include <stdio.h>   // For `printf()`
#include <stdlib.h>  // For `atexit()`

/// 3. This function gets attached as a post-main() callback (a sort of program "destructor")
/// via the C <stdlib.h> `atexit()` call below
void also_called_after_main()
{
    printf("`atexit()`-registered callback functions are also called AFTER `main()`.\n");
}

/// 1. Functions with gcc function attribute,`constructor`,get automatically called **before**
/// `main()`; see:
/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
__attribute__((__constructor__))
void called_before_main()
{
    printf("gcc constructors are called BEFORE `main()`.\n");

    // 3. Optional way to register a function call for AFTER main(),although
    // I prefer the simpler gcc `destructor` attribute technique below,instead.
    int retcode = atexit(also_called_after_main);
    assert(retcode == 0); // ensure the `atexit()` call to register the callback function succeeds
}

/// 2. Functions with gcc function attribute,`destructor`,get automatically called **after**
/// `main()`; see:
/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
__attribute__((__destructor__))
void called_after_main()
{
    printf("gcc destructors are called AFTER `main()`.\n");
}

如何构建和运行动态 lib*.so 共享对象库,并在运行另一个程序时使用 LD_PRELOAD 动态加载它(参见“dynamic_func_call_before_and_after_main_build_and_run.sh”):强>

# 1. Build the other program (hello_world_basic.c) that has `main()` in it which we want to use
mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj hello_world_basic.c \
-o bin/hello_world_basic
# 2. Create a .o object file of this program,compiling with Position Independent Code (PIC); see
# here: https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
gcc -Wall -Wextra -Werror -O3 -std=c11 -fpic -c dynamic_func_call_before_and_after_main.c \
-o bin/dynamic_func_call_before_and_after_main.o
# 3. Link the above PIC object file into a dynamic shared library (`lib*.so` file); link above shows
# we must use `-shared`
gcc -shared bin/dynamic_func_call_before_and_after_main.o -o \
bin/libdynamic_func_call_before_and_after_main.so
# 4. Call the other program with `main()` in it,dynamically injecting this code into that other
# program via this code's .so shared object file,and via Linux's `LD_PRELOAD` trick
LD_PRELOAD='bin/libdynamic_func_call_before_and_after_main.so' bin/hello_world_basic

示例输出。请注意,我们在“hello_world_basic.c”中的 main() 函数之前之后注入了一些特殊的函数调用:

gcc constructors are called BEFORE `main()`.
This is the start of `main()`.
  Hello world.
This is the end of `main()`.
gcc destructors are called AFTER `main()`.
`atexit()`-registered callback functions are also called AFTER `main()`.

参考:

  1. 如何在 Linux 中构建动态 lib*.so 库:https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
  2. @kaylum's comment
  3. @Employed Russian's answer
  4. @Lundin's comment
  5. gcc constructordestructor 函数属性!: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
  6. c atexit() func 用于注册要在 main() 返回或退出后调用的函数!: https://en.cppreference.com/w/c/program/atexit