【问题标题】:Why first arg to execve() must be path to executable为什么第一个 arg 到 execve() 必须是可执行文件的路径
【发布时间】:2010-06-12 03:08:28
【问题描述】:

我了解 execve() 和 family 要求其参数数组的第一个参数与其第一个参数所指向的可执行文件相同。也就是说,在这个:

execve(prog, args, env);

args[0] 通常与 prog 相同。但我似乎找不到关于这是为什么的信息。

我也明白可执行文件(呃,至少是 shell 脚本)在运行时总是将它们的调用路径作为第一个参数,但我认为 shell 会完成将它放在那里的工作,而 execve() 只会使用第一个参数中给出的路径(上面的“prog”)调用可执行文件,然后像在命令行上一样传递参数数组(上面的“args”)......即,我不调用脚本在 args 列表中有重复的可执行路径的命令行上......

/bin/ls /bin/ls /home/john

谁能解释一下?

【问题讨论】:

    标签: c exec


    【解决方案1】:

    不要求第一个参数与可执行文件的名称有任何关系:

    int main(void)
    {
        char *args[3] = { "rip van winkle", "30", 0 };
        execv("/bin/sleep", args);
        return 1;
    }
    

    在 Mac 上试一试(经过三个测试):

    make x; ./x & sleep 1; ps
    

    第三次运行的输出是:

    MiniMac JL: make x; ./x & sleep 1; ps
    make: `x' is up to date.
    [3] 5557
      PID TTY           TIME CMD
     5532 ttys000    0:00.04 -bash
     5549 ttys000    0:00.00 rip van winkle 30
     5553 ttys000    0:00.00 rip van winkle 30
     5557 ttys000    0:00.00 rip van winkle 30
    MiniMac JL: 
    

    EBM cmets:

    是的,这让它变得更加奇怪。在我的测试 bash 脚本(execve 的目标)中,我看不到 execve 在任何地方的 arg[0] 中的值——不是在环境中,也不是 $0。

    修改实验 - 一个名为 'bash.script' 的脚本:

    #!/bin/bash
    
    echo "bash script at sleep (0: $0; *: $*)"
    sleep 30
    

    还有一个修改后的程序:

    int main(void)
    {
        char *args[3] = { "rip van winkle", "30", 0 };
        execv("./bash.script", args);
        return 1;
    }
    

    这会产生 ps 输出:

    bash script at sleep (0: ./bash.script; *: 30)
      PID TTY           TIME CMD
     7804 ttys000    0:00.11 -bash
     7829 ttys000    0:00.00 /bin/bash ./bash.script 30
     7832 ttys000    0:00.00 sleep 30
    

    在我看来有两种可能:

    1. 当通过 shebang ('#!/bin/bash') 行执行脚本时,内核会在命令行中进行调整,或者
    2. Bash 本身使用它的参数列表。

    如何确定差异?我想将 shell 复制到一个替代名称,然后在 shebang 中使用该替代名称会告诉我们一些事情:

    $ cp /bin/bash jiminy.cricket
    $ sed "s%/bin/bash%$PWD/jiminy.cricket%" bash.script > tmp
    $ mv tmp bash.script
    $ chmod +w bash.script
    $ ./x & sleep 1; ps
    [1] 7851
    bash script at sleep (0: ./bash.script; *: 30)
      PID TTY           TIME CMD
     7804 ttys000    0:00.12 -bash
     7851 ttys000    0:00.01 /Users/jleffler/tmp/soq/jiminy.cricket ./bash.script 30
     7854 ttys000    0:00.00 sleep 30
    $
    

    我认为这表明内核在使用shebang机制时重写了argv[0]


    解决 nategoose 的评论:

    MiniMac JL: pwd
    /Users/jleffler/tmp/soq
    MiniMac JL: cat al.c
    #include <stdio.h>
    int main(int argc, char **argv)
    {
        while (*argv)
            puts(*argv++);
        return 0;
    }
    MiniMac JL: make al.c
    cc     al.c   -o al
    MiniMac JL: ./al a b 'c d' e
    ./al
    a
    b
    c d
    e 
    MiniMac JL: cat bash.script
    #!/Users/jleffler/tmp/soq/al
    
    echo "bash script at sleep (0: $0; *: $*)"
    sleep 30
    MiniMac JL: ./x
    /Users/jleffler/tmp/soq/al
    ./bash.script
    30
    MiniMac JL:
    

    这表明调整argv[0] 值的是shebang '#!/path/to/program' 机制,而不是诸如Bash 之类的任何程序。因此,当执行二进制文件时,argv[0] 的值不会被调整;当通过shebang执行脚本时,参数列表由内核调整; argv[0] 是 shebang 上列出的二进制文件;如果 shebang 之后有参数,则变为argv[1];下一个参数是脚本文件的名称,后跟来自 execv() 或等效调用的任何剩余参数。

    MiniMac JL: cat bash.script
    #!/Users/jleffler/tmp/soq/al -arg0
    #!/bin/bash
    #!/Users/jleffler/tmp/soq/jiminy.cricket
    
    echo "bash script at sleep (0: $0; *: $*)"
    sleep 30
    MiniMac JL: ./x
    /Users/jleffler/tmp/soq/al
    -arg0
    ./bash.script
    30
    MiniMac JL: 
    

    【讨论】:

    • 是的,这让它更奇怪了。在我的测试 bash 脚本(execve 的目标)中,我在任何地方都看不到 arg[0] 中 execve 的值——不是在环境中,也不是 $0。
    • 制作一个脚本#!/home/me/print_args 并编写一个简单的打印arg 程序以确定谁在摆弄argv[0]
    【解决方案2】:

    它允许您指定要加载的可执行文件的确切路径,但也允许在pstop 等工具中显示“美化”名称。

    execl("/bin/ls", "ls", "/home/john", (char *)0);
    

    【讨论】:

    • 你需要双引号;假设您使用的是 execl(),可能还有一个 null char * 来终止参数列表。
    • 我相信你的话,但有趣的是,我创建了一个测试,它执行一个简单的 bash 脚本,它会输出 $0,即使我使用“美化”的程序名称,$0 仍然会是完整路径(我假设从 execve() 的第一个参数构造)。
    • 有些程序也使用 arg[0] 来确定要做什么:例如,busybox 就是这样做的。我相信 bash 也会改变它的行为,这取决于你调用它的方式。 (bash 或只是sh
    【解决方案3】:

    根据this,程序名的第一个参数是自定义的。

    按照惯例,第一个元素应该是 已执行程序的名称(例如 例如,路径的最后一个组成部分)

    也就是说,这些值可能不同。例如,如果程序是从符号链接启动的。程序名称可能与用于启动它的链接名称不同。

    而且,你是对的。 shell 通常会完成设置第一个参数的工作。然而,在这种情况下,使用 execve 完全绕过了 shell - 这就是为什么您需要自己设置它。

    【讨论】:

      【解决方案4】:

      这允许程序有多个名称,并且根据调用它的名称而略有不同。

      成像琐碎的程序,例如print0.c编译成print0:

      #include <stdio.h>
      int main(int argc, char **argv)
      {
         printf("%s\n",argv[0]);
         return 0;
      }
      

      以 ./print0 运行它会打印 ./print0 建立一个符号链接,例如print1 到它,现在使用名称 ./print1 运行它 - 它会打印“./print1”。

      现在有了符号链接。但是使用 exec*() 函数,你可以明确地告诉程序它的名字。

      来自 *NIX 的工件,但还是很高兴拥有。

      【讨论】:

      • 请参阅上面我对 Jonathan Leffler 的评论 - 使用测试 bash 脚本作为 execve 调用的目标,arg[0] 不会出现在任何地方。也许一个 C 程序得到它的参数不同,但至少对于一个 bash 脚本,你解释的这个特性似乎不存在。
      • 是的,shell 的 exec 不允许这样做。必须使用符号链接以不同的名称调用程序。
      • 在 bash 中,您可以使用 (exec -a arg0 some_program arg1 arg2)。我使用了(subshell),因为这只适用于exec,替换当前的shell 或子shell。
      猜你喜欢
      • 2015-02-09
      • 2023-03-13
      • 2021-11-22
      • 1970-01-01
      • 2013-10-27
      • 2013-01-25
      • 2019-08-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多