linux - 两次调用 main 的行为不同

标签 linux glibc

我有以下代码,应该调用主函数两次。

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200112L

#include <limits.h>
#include <link.h>
#include <stdio.h>
#include <time.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>
#include <malloc.h>
#include <stdlib.h>

#include <sys/mman.h>

extern char** environ;

static int (*main_orig)(
    int,
    char**,
    char**
);
static int (*__libc_start_main_orig)(
    int (*)(int, char**, char**),
    int,
    char**,
    int (*)(int, char**, char**),
    void (*)(void),
    void (*)(void),
    void*
);
static int (*init_orig)(
    int,
    char**,
    char**
);
static void (*fini_orig)(void);
static void (*rtld_fini_orig)(void);
static void* stack_end_orig;

static char* libs[] = {
    "libpthread-2.22.so",
    NULL
};

static void my_dlopen(char* lib_name)
{
    int             ret;
    struct timespec before;
    struct timespec after;
    long long       dl_time;
    void*           handle = NULL; 

    (void) clock_gettime(CLOCK_MONOTONIC, &before);

    handle = dlopen(lib_name, RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);
    if (handle == NULL) {
        fprintf(stdout, "%-35s :: dlopen failed with %s :: ", lib_name, dlerror());
    }
    else {
        fprintf(stdout, "%-35s :: dlopen [OK] :: ", lib_name);

        ret = dlclose(handle);
        if (ret != 0) {
            fprintf(stdout, "dlclose failed with %s :: ", dlerror());
        }
        else {
            fprintf(stdout, "dlclose [OK] :: ");
        }
    }

    (void) clock_gettime(CLOCK_MONOTONIC, &after);
    dl_time = ((long long) (after.tv_sec  - before.tv_sec)) * 1000000000ll + 
              ((long long) (after.tv_nsec - before.tv_nsec));
    fprintf(stdout, "%12lld us\n", dl_time / 1000ll);
}

static int my_main(int argc, char** argv, char** envp)
{
    int i;
    int ret;

    fprintf(stdout, "--- Before main --- %d :: %d :: %p\n", getpid(), getppid(), main_orig);
    for (i = 0; argv[i] != NULL; i++) {
        fprintf(stdout, "argv[%2d] = %s\n", i, argv[i]);
    }
    for (i = 0; envp[i] != NULL; i++) {
        fprintf(stdout, "envp[%2d] = %s\n", i, envp[i]);
    }
    ret = main_orig(argc, argv, envp);
    fprintf(stdout, "--- After main --- %d :: %d :: %p\n", getpid(), getppid(), main_orig);

    return ret;
}

int main_hook(int argc, char** argv, char** envp)
{
    int i;
    int ret;
    struct timespec before;
    struct timespec after;
    long long       dl_time;
    void*           dummy_buffer = NULL;
    const size_t    dummy_size   = 256 * 1024 * 1024;

    asm volatile("": : :"memory");

    fprintf(stdout, "****************************************************\n");
    ret = mlockall(MCL_CURRENT | MCL_FUTURE);
    if (ret == -1) {
        fprintf(stdout, "mlockall failed with errno = %d\n", errno);
    }
    else {
        fprintf(stdout, "mlockall [OK]\n");
    }
    ret = mallopt(M_TRIM_THRESHOLD, -1);
    if (ret == 1) {
        fprintf(stdout, "mallopt(M_TRIM_THRESHOLD, -1) [OK]\n");
    }
    else {
        fprintf(stdout, "mallopt(M_TRIM_THRESHOLD, -1) [!!]\n");
    }
    ret = mallopt(M_MMAP_MAX, 0);
    if (ret == 1) {
        fprintf(stdout, "mallopt(M_MMAP_MAX, 0)        [OK]\n");
    }
    else {
        fprintf(stdout, "mallopt(M_MMAP_MAX, 0)        [!!]\n");
    }
    dummy_buffer = malloc(dummy_size);
    if (dummy_buffer != NULL) {
        fprintf(stdout, "dummy_buffer = %p :: %zu\n", dummy_buffer, dummy_size);
        memset(dummy_buffer, 0x00, dummy_size);
        free(dummy_buffer);
    }

    asm volatile("": : :"memory");

    fprintf(stdout, "****************************************************\n");
    (void) clock_gettime(CLOCK_MONOTONIC, &before);
    for (i = 0; libs[i] != NULL; i++) {
        my_dlopen(libs[i]);
    }
    (void) clock_gettime(CLOCK_MONOTONIC, &after);
    dl_time = ((long long) (after.tv_sec  - before.tv_sec)) * 1000000000ll + 
              ((long long) (after.tv_nsec - before.tv_nsec));
    fprintf(stdout, "****************************************************\n");
    fprintf(stdout, "Total dlopen time = %lld ms\n", dl_time / 1000000ll);

    asm volatile("": : :"memory");

    fprintf(stdout, "****************************************************\n");
    ret  = 0;
    ret += my_main(argc, argv, envp);
    ret += my_main(argc, argv, envp);

    return ret;
}

int __libc_start_main(  int (*main)(int, char**, char**),
                        int argc,
                        char** argv,
                        int (*init)(int, char**, char**),
                        void (*fini)(void),
                        void (*rtld_fini)(void),
                        void* stack_end)
{
    int i;
    int ret;

    for (i = 0; environ[i] != NULL; i++) {
        char* substr = strstr(environ[i], "LD_PRELOAD=");

        if (substr != NULL) {
            fprintf(stdout, "%s found and replaced with ", environ[i]);
            memset(&environ[i][0], 'x', strlen(environ[i]));
        }
        fprintf(stdout, "%s\n", environ[i]);
    }

    __libc_start_main_orig  = dlsym(RTLD_NEXT, "__libc_start_main");
    main_orig               = main;
    init_orig               = init;
    fini_orig               = fini;
    rtld_fini_orig          = rtld_fini;
    stack_end_orig          = stack_end;
    ret = __libc_start_main_orig(main_hook, argc, argv, init, fini, rtld_fini, stack_end);

    return ret;
}

此代码编译为共享库:

$CC -ggdb -fPIC -shared replace_main.c -o replace_main -ldl -lrt -pthread.

我是这样使用so文件的:

LD_PRELOAD=/var/log/replace_main ls -l /

这给了我:

LD_PRELOAD=/var/log/replace_main found and replaced with xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TERM=xterm-256color
SHELL=/bin/sh
SSH_CLIENT=xxxxxxxxx
OLDPWD=/var/log
SSH_TTY=/dev/pts/0
USER=root
LD_LIBRARY_PATH=xxxxxxxxx
MAIL=/var/mail/root
PATH=xxxxxxxxx
LTTNG_UST_CLOCK_PLUGIN=xxxxxxxxx
LTTNG_UST_WITHOUT_BADDR_STATEDUMP=xxxxxxxxx
PWD=/var/log
EDITOR=vi
TZ=UTC
PS1=\u@\h:\w\$ 
SHLVL=1
HOME=/root
LOGNAME=root
SSH_CONNECTION=xxxxxxxxx
_=/bin/ls
****************************************************
mlockall [OK]
mallopt(M_TRIM_THRESHOLD, -1) [OK]
mallopt(M_MMAP_MAX, 0)        [OK]
dummy_buffer = 0x5a4020 :: 268435456
****************************************************
libpthread-2.22.so                  :: dlopen [OK] :: dlclose [OK] ::          596 us
****************************************************
Total dlopen time = 0 ms
****************************************************
--- Before main --- 25870 :: 3265 :: 0x12068
argv[ 0] = ls
argv[ 1] = -l
argv[ 2] = /
envp[ 0] = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
envp[ 1] = TERM=xterm-256color
envp[ 2] = SHELL=/bin/sh
envp[ 3] = SSH_CLIENT=xxxxxxxxx
envp[ 4] = OLDPWD=/var/log
envp[ 5] = SSH_TTY=/dev/pts/0
envp[ 6] = USER=root
envp[ 7] = LD_LIBRARY_PATH=xxxxxxxxx
envp[ 8] = MAIL=/var/mail/root
envp[ 9] = PATH=xxxxxxxxx
envp[10] = LTTNG_UST_CLOCK_PLUGIN=xxxxxxxxx
envp[11] = LTTNG_UST_WITHOUT_BADDR_STATEDUMP=xxxxxxxxx
envp[12] = PWD=/var/log
envp[13] = EDITOR=vi
envp[14] = TZ=UTC
envp[15] = PS1=\u@\h:\w\$ 
envp[16] = SHLVL=1
envp[17] = HOME=/root
envp[18] = LOGNAME=root
envp[19] = SSH_CONNECTION=xxxxxxxxx
envp[20] = _=/bin/ls
total 19
drwxr-xr-x   2 root  root     6176 Jul 17  2018 bin
drwxr-xr-x   2 root  root        3 Jul 17  2018 boot
-rw-rw-r--   1 sirpa tracing  2447 Jul 17  2018 cxp9025851_3.xml
drwxr-xr-x   8 root  root     8300 Apr 24 11:48 dev
drwxr-xr-x  38 root  root     1584 Jul 17  2018 etc
drwxr-xr-x   3 root  root       28 Jul 17  2018 home
drwxr-xr-x   8 root  root     5263 Jul 17  2018 lib
-rwxr-xr-x   1 root  root      509 Apr 25  2018 linuxrc.sh
drwxr-xr-x   2 root  root        3 Apr 22  2018 media
drwxr-xr-x   2 root  root        3 Apr 22  2018 mnt
drwxr-xr-x   8 root  root      115 Jul 17  2018 opt
dr-xr-xr-x 759 root  root        0 Jan  1  1970 proc
drwxrwxrwx  28 sirpa users    4096 Apr 23 17:51 rcs
drwxr-xr-x   2 root  root        3 Apr 22  2018 root
drwxrwxrwt  12 root  root      540 Apr 24 11:49 run
drwxr-xr-x   2 root  root     2868 Jul 17  2018 sbin
drwxr-xr-x  53 root  root    12288 Apr 24 10:39 software
dr-xr-xr-x  12 root  root        0 Apr 24 11:48 sys
drwxrwxrwt  10 root  root      740 Apr 24 12:49 tmp
drwxr-xr-x  10 root  root      150 Apr 22  2018 usr
drwxr-xr-x  13 root  root      186 Jul 17  2018 var
--- After main --- 25870 :: 3265 :: 0x12068
--- Before main --- 25870 :: 3265 :: 0x12068
argv[ 0] = ls
argv[ 1] = -l
argv[ 2] = /
envp[ 0] = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
envp[ 1] = TERM=xterm-256color
envp[ 2] = SHELL=/bin/sh
envp[ 3] = SSH_CLIENT=xxxxxxxxx
envp[ 4] = OLDPWD=/var/log
envp[ 5] = SSH_TTY=/dev/pts/0
envp[ 6] = USER=root
envp[ 7] = LD_LIBRARY_PATH=xxxxxxxxx
envp[ 8] = MAIL=/var/mail/root
envp[ 9] = PATH=xxxxxxxxx
envp[10] = LTTNG_UST_CLOCK_PLUGIN=xxxxxxxxx
envp[11] = LTTNG_UST_WITHOUT_BADDR_STATEDUMP=xxxxxxxxx
envp[12] = PWD=/var/log
envp[13] = EDITOR=vi
envp[14] = TZ=UTC
envp[15] = PS1=\u@\h:\w\$ 
envp[16] = SHLVL=1
envp[17] = HOME=/root
envp[18] = LOGNAME=root
envp[19] = SSH_CONNECTION=xxxxxxxxx
envp[20] = _=/bin/ls
bin  boot  cxp9025851_3.xml  dev  etc  home  lib  linuxrc.sh  media  mnt  opt  proc  rcs  root  run  sbin  software  sys  tmp  usr  var
--- After main --- 25870 :: 3265 :: 0x12068
root@du1:/var/log# 

如您所见,ls 的 main 被调用了两次,但第二次似乎忽略了 -l 参数。可能是什么原因?

PS:我在内核为 4.1 的 ARM Linux 变体上运行代码

谢谢,

最佳答案

What can the cause?

选项解析库具有内部状态,您不会重置该状态。

来自 man 3 getopt:

extern int optind, ...

The variable optind is the index of the next element to be processed in argv.
The system initializes this value to 1.  The caller can reset it to 1 to restart
scanning of the same argv, or when scanning a new argument vector.

由于您没有/bin/ls 中的 optind 重置为 1,当真正的 main 调用 getopt,它立即得到一个“没有更多选项”的回答。

这可以用一个简单的程序来证明:

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

int main(int argc, char *argv[])
{
  int rc;
  while ((rc = getopt(argc, argv, "abc")) != -1) {
    printf("getopt: '%c'\n", rc);
  }
  return 0;
}

gcc main.c -o main
LD_PRELOAD=./replace_main.so ./main -ab

...
--- Before main --- 34822 :: 14915 :: 0x55ce59e2768a
argv[ 0] = ./main
argv[ 1] = -ab
getopt: 'a'
getopt: 'b'
--- After main --- 34822 :: 14915 :: 0x55ce59e2768a
--- Before main --- 34822 :: 14915 :: 0x55ce59e2768a
argv[ 0] = ./main
argv[ 1] = -ab
--- After main --- 34822 :: 14915 :: 0x55ce59e2768a

请注意,第二次调用中缺少 getopt 行(正如人们所期望的那样)。

如果我在 main.c 中的 while 循环之前添加 optind = 1;,它就会按预期开始工作:

--- Before main --- 35487 :: 14915 :: 0x55939fb706ca
argv[ 0] = ./main
argv[ 1] = -ab
getopt: 'a'
getopt: 'b'
--- After main --- 35487 :: 14915 :: 0x55939fb706ca
--- Before main --- 35487 :: 14915 :: 0x55939fb706ca
argv[ 0] = ./main
argv[ 1] = -ab
getopt: 'a'
getopt: 'b'
--- After main --- 35487 :: 14915 :: 0x55939fb706ca

关于linux - 两次调用 main 的行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55830957/

相关文章:

linux - 对于成功执行的进程使用非零返回代码是否可以?

linux - 在 Suse Enterprise 10.0 上安装 MonoDevelop

linux - bash 数组在命令提示符下工作但在作为 ksh 执行时不工作

c# - 从 C# 进行 glibc 系统调用

clang - 如何阻止 Clang 复制标准 C 头文件中的函数?

c - fallocate 和 ftruncate 有什么区别

linux - Linux 上的 USB HID 设备轮询间隔

c - 如何将 platform_device 的通用资源/数据转发给驱动程序

c - 原始克隆系统调用无法正常工作

c++ - glibc 检测到双重释放或损坏