用__attribute __constructor声明的函数用LD_PRELOAD多次调用

问题描述

定义共享库,如下所示:

#include <unistd.h>
#include <stdio.h>

static void init(void) __attribute__((constructor));
static void init(void)
{
    fprintf(stderr,"pid=%u\n",(unsigned) getpid());
}

在AMD64计算机上使用GCC 10进行构建:

gcc -shared -fPIC lib.c

运行一个简单的过程来验证:

$ LD_PRELOAD=`realpath a.out` ls
pid=15771
a.out  lib.c

pid=15771按预期方式打印了init()行。

现在,重复一个产生子代和线程的复杂过程:

$ LD_PRELOAD=`realpath a.out` python3
pid=15835
pid=15835
pid=15835
pid=15835
pid=15839
pid=15844
pid=15835
pid=15835
pid=15846
pid=15846
pid=15847
pid=15847
pid=15849
pid=15849
pid=15851
pid=15852
pid=15853
pid=15853
pid=15856
pid=15857
pid=15857
pid=15858
pid=15858
pid=15861
pid=15862
pid=15862
pid=15865
pid=15868
pid=15835
Python 3.8.2 (default,Apr 19 2020,18:33:14) 
[GCC 9.3.0] on linux
Type "help","copyright","credits" or "license" for more information.
>>> 

观察到有重复的条目,例如pid=15835,这表明对于某些进程,init()已经执行了多次。

为什么?

解决方法

启动Python解释器时,您的Python安装会执行一些程序。如果使用execve替换了过程映像,则过程ID不会更改,但是构造函数将为新的过程映像运行。

一个更简单的示例如下:

$ LD_PRELOAD=`realpath a.out` bash -c 'exec /bin/true'

您可以通过调用strace来查看更多详细信息:

$ strace -f -E LD_PRELOAD=`realpath a.out` -eexecve bash -c 'exec /bin/true'
execve("/usr/bin/bash",["bash","-c","exec /bin/true"],0x55b275d8b830 /* 29 vars */) = 0
pid=801315
execve("/bin/true",["/bin/true"],0x5564dedbeb80 /* 29 vars */) = 0
pid=801315
+++ exited with 0 +++