【问题标题】:All command line arguments to mainmain 的所有命令行参数
【发布时间】:2016-05-31 03:14:32
【问题描述】:

我想知道 main 函数的所有命令行参数是什么(特别是在 C 中,但我猜这无论如何都适用于所有语言)?在我的编译器课程中,我听到一位讲师简要提到(可能我听错或误解了)main() 参数比通常提到的要多,特别是在 argv 指针的负偏移量处,可以访问一些信息。我无法通过谷歌搜索或在我拥有的几本教科书中找到任何东西。我用C写了这个小程序来试试。以下是一些问题:

1) While 循环在段故障前运行 32 次。为什么总共有 32 个参数,我在哪里可以找到它们的规格,为什么其中 32 个不是另一个数量?

打印出来的信息都是关于系统的:密码、学期会话信息、用户信息等等。

2) 在 main 之前是否有任何东西被放入堆栈?在典型的调用过程中,函数的参数放在返回地址之前的堆栈中(给予或接受金丝雀和其他东西)。当一个程序被 shell 调用时,这个过程是一样的,我在哪里可以读到这个?我真的很想知道shell如何调用程序以及与程序内堆栈布局相比的内存布局是什么。

#include <stdio.h>
#include <ctype.h>

int main(int argc, char * argv[]) {
    void * argall = argv[0];

    printf("argc=%d\n", argc);
    int i = 0;
    while (i < 32) {
    //while (argall) { // tried this to find out that it seg faults at i=32
        printf("arg%d %s\n", i, (char* ) argall);
        i++;
        argall = argv[i];
    }

    printf("negative pointers\n");
    // I don't think dereferencing in this part is quite right, but I am 
    // getting chars since I am reading bytes. Output of below code is.
    // How come it is alphabet?
    // I tried reading int values and (char*) for string, but got nothing useful.
    /*
    arg -1 o
    arg -2 n 
    arg -3 m
    arg -4 l
    arg -5 k
    */
    printf("arg -1 %c\n", (char) argv-1);
    printf("arg -2 %c\n", (char) argv-2);
    printf("arg -3 %c\n", (char) argv-3);
    printf("arg -4 %c\n", (char) argv-4);
    printf("arg -5 %c\n", (char) argv-5);

    return 0;
}

非常感谢!很抱歉发了很长的帖子。

更新:这是来自 while 循环的输出:

argc=1
arg0 ./main-testing.o
arg1 (null)
arg2 TERM_PROGRAM=iTerm.app
arg3 SHELL=/bin/bash
arg4 TERM=xterm-256color
arg5 CLICOLOR=1
arg6 TMPDIR=/var/folders/d0/<redacted>
arg7 Apple_PubSub_Socket_Render=/private/<redacted>
arg8 OLDPWD=/Users/me/problems
arg9 USER=me
arg10 COMMAND_MODE=unix2003
arg11 SSH_AUTH_SOCK=/private/t<redacted>
arg12 _<redacted>
arg13 LSCOLORS=ExFxBxDxCxegedabagacad
arg14 PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
arg15 PWD=/Users/me/problems/c
arg16 LANG=en_CA.UTF-8
arg17 ITERM_PROFILE=Default
arg18 XPC_FLAGS=0x0
arg19 PS1=\[\033[36m\]\u\[\033[m\]@\[\033[32m\]\h:\[\033[33;1m\]\w\[\033[m\]$
arg20 XPC_SERVICE_NAME=0
arg21 SHLVL=1
arg22 COLORFGBG=7;0
arg23 HOME=/Users/me
arg24 ITERM_SESSION_ID=w0t0p0
arg25 LOGNAME=me
arg26 _=./main-testing.o
arg27 (null)
arg28 executable_path=./main-testing.o
arg29
arg30
arg31

【问题讨论】:

  • 你的导师的原话是什么? argv 之前没有任何“隐藏”参数。
  • 我不太记得单词了,对不起。我的理解是在负偏移量的 argv 之前可以访问一些信息。我可能听错了,但我真的很古玩。因为我可以用负偏移量读取它,所以空间显然是我的(属于程序),所以我很想知道通过在堆栈上负数可以得到多远和什么。
  • i0...argc 的范围之外访问argv[i] 是未定义的行为(UB)。而已。有时您可能会在某些机器上发现一些有用的东西,代码可能会崩溃。是UB。 C 并没有指定代码尝试时会发生什么。
  • 对不起,什么是UB?谢谢。
  • 未定义行为。你不能只访问内存的随机位并从中得出结论。仅仅因为它直到某个点才崩溃并不意味着数据是有效的(尽管其中一些确实发生在您的情况下)。也就是说,你的前提和结论是有缺陷的。话虽如此,在某些情况下,C 标准确实为main 定义了第三个参数:“char *envp[],它指向一个以空结尾的指向 char 的指针数组,每个指针都指向一个提供信息的字符串关于这个程序执行的环境”

标签: c memory main


【解决方案1】:

您似乎使用的是 Mac。在 Mac 上,您会获得 4 位数据。

您可以使用main() 的替代声明:

int main(int argcv, char **argv, char **envp)

然后您将能够列出环境,就像您通过访问参数列表的末尾所做的那样。环境跟随参数,并且也被一个空指针终止。

然后,Mac 在环境之后会有更多数据(您可以在输出中看到executable_path=…)。你可以在维基百科的Entry Point 下找到一些相关信息,它指的是The char *apple[] Argument Vector

int main(int argc, char **argv, char **envp, char **applev)

我不知道argv 向量之前的任何标准化。将它们作为单个字符访问不太可能有用。我会将数据打印为地址并寻找模式。

这是我几年前编写的一些代码,用于尝试从environ 中查找参数列表;它一直有效,直到您通过添加一个新变量来修改环境,该变量会更改 environ 指向的位置:

#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>     /* putenv(), setenv() */

extern char **environ;  /* Should be declared in <unistd.h> */

/*
** The object of the exercise is: given just environ (since that is all
** that is available to a library function) attempt to find argv[0] (and
** hence argc).
**
** On some platforms, the layout of memory is such that the number of
** arguments (argc) is available, followed by the argument vector,
** followed by the environment vector.
**
**          argv                            environ
**            |                                |
**            v                                v
** | argc | argv0 | argv1 | ... | argvN | 0 | env0 | env1 | ... | envN | 0 |
**
** This applies to:
** -- Solaris 10 (32-bit, 64-bit SPARC)
** -- MacOS X 10.6 (Snow Leopard, 32-bit and 64-bit)
** -- Linux (RHEL 5 on x86/64, 32-bit and 64-bit)
**
** Sadly, this is not quite what happens on the other two Unix
** platforms.  The value preceding argv0 seems to be a 0.
** -- AIX 6.1          (32-bit, 64-bit)
** -- HP-UX 11.23 IA64 (32-bit, 64-bit)
**       Sub-standard POSIX support (no setenv()) and C99 support (no %zd).
**
** NB: If putenv() or setenv() is called to add an environment variable,
** then the base address of environ changes radically, moving off the
** stack onto heap, and all bets are off.  Modifying an existing
** variable is not a problem.
**
** Spotting the change from stack to heap is done by observing whether
** the address pointed to by environ is more than 128 K times the size
** of a pointer from the address of a local variable.
**
** This code is nominally incredibly machine-specific - but actually
** works remarkably portably.
*/

typedef struct Arguments
{
    char   **argv;
    size_t   argc;
} Arguments;

static void print_cpp(const char *tag, int i, char **ptr)
{
    uintptr_t p = (uintptr_t)ptr;
    printf("%s[%d] = 0x%" PRIXPTR " (0x%" PRIXPTR ") (%s)\n",
            tag, i, p, (uintptr_t)(*ptr), (*ptr == 0 ? "<null>" : *ptr));
}

enum { MAX_DELTA = sizeof(void *) * 128 * 1024 };

static Arguments find_argv0(void)
{
    static char *dummy[] = { "<unknown>", 0 };
    Arguments args;
    uintptr_t i;
    char **base = environ - 1;
    uintptr_t delta = ((uintptr_t)&base > (uintptr_t)environ) ? (uintptr_t)&base - (uintptr_t)environ : (uintptr_t)environ - (uintptr_t)&base;
    if (delta < MAX_DELTA)
    {
        for (i = 2; (uintptr_t)(*(environ - i) + 2) != i && (uintptr_t)(*(environ - i)) != 0; i++)
            print_cpp("test", i, environ-i);
        args.argc = i - 2;
        args.argv = environ - i + 1;
    }
    else
    {
        args.argc = 1;
        args.argv = dummy;
    }

    printf("argc    = %zd\n", args.argc);
    for (i = 0; i <= args.argc; i++)
        print_cpp("argv", i, &args.argv[i]);

    return args;
}

static void print_arguments(void)
{
    Arguments args = find_argv0();
    printf("Command name and arguments\n");
    printf("argc    = %zd\n", args.argc);
    for (size_t i = 0; i <= args.argc; i++)
        printf("argv[%zd] = %s\n", i, (args.argv[i] ? args.argv[i] : "<null>"));
}

static int check_environ(int argc, char **argv)
{
    size_t n = argc;
    size_t i;
    unsigned long delta = (argv > environ) ? argv - environ : environ - argv;
    printf("environ = 0x%lX; argv = 0x%lX (delta: 0x%lX)\n", (unsigned long)environ, (unsigned long)argv, delta);
    for (i = 0; i <= n; i++)
        print_cpp("chkv", i, &argv[i]);
    if (delta > (unsigned long)argc + 1)
        return 0;

    for (i = 1; i < n + 2; i++)
    {
        printf("chkr[%zd] = 0x%lX (0x%lX) (%s)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)),
                (*(environ-i) ? *(environ-i) : "<null>"));
        fflush(0);
    }
    i = n + 2;
    printf("chkF[%zd] = 0x%lX (0x%lX)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)));
    i = n + 3;
    printf("chkF[%zd] = 0x%lX (0x%lX)\n", i, (unsigned long)(environ - i), (unsigned long)(*(environ - i)));
    return 1;
}

int main(int argc, char **argv)
{
    printf("Before setting environment\n");
    if (check_environ(argc, argv))
        print_arguments();

    //putenv("TZ=US/Pacific");
    setenv("SHELL", "/bin/csh", 1);

    printf("After modifying environment\n");
    if (check_environ(argc, argv) == 0)
        printf("Modifying environment messed everything up\n");
    print_arguments();

    putenv("CODSWALLOP=nonsense");

    printf("After adding to environment\n");
    if (check_environ(argc, argv) == 0)
        printf("Adding environment messed everything up\n");
    print_arguments();

    return 0;
}

【讨论】:

  • 谢谢乔纳森。这是一段非常酷的代码,我还在玩它。我也和我的导师谈过并澄清了。我想我误解了这一点,或者它是非常具体的东西(可能是关于另一种语言,因为它是编译器类),但是 argv 上面没有任何东西,就像其他人在这个线程中所说的那样。但是,我了解了 argv 之后的内容仍然很酷。如果我发现更多,我会发布一些东西。在 C 中处理溢出和内存问题很有趣。
【解决方案2】:

在 Linux、*BSD - 以及 Mac OS X - 以及可能的其他类 unix 系统上,environ 数组是在 argv 数组之后的堆栈上构建的。

environ 包含所有环境变量作为字符串数组,每个字符串形式为name=value。虽然单个环境变量通常通过getenv 函数访问,但也允许使用environ 全局变量(Posix)。

main 调用框架下方的堆栈中查找这些字符串是正确的,它也没有比使用environ 提供任何优势。

如果您想查看实际代码,您需要深入了解execve 系统调用的实现,这实际上是启动一个新进程的原因。看起来对 Linux 进程启动 here on lwn.org 进行了相当准确的讨论,其中包括指向代码存储库的指针。在/sys/kern/kern_exec.c 中可以找到在许多方面相似的FreeBSD 实现;你可能会开始阅读here.

【讨论】:

  • 非常感谢 rici。我不知道environgetenv 函数。在 argv 之后手动读取它是有道理的,或者不可靠(就像 chux 说的那样)。就我而言,我不知道是否有任何东西,所以我尝试用不同的类型尽可能阅读,看看是否有任何东西。我还没有机会看execve。查找有关系统以及某些操作如何发生的这些信息(例如,当 shell 启动程序时到底发生了什么非常困难。我想我应该在某个时候开始查看源代码。谢谢!
  • @serge execve 的 Posix 文档对进程启动非常精确,但没有具体说明它是如何实现的。这是一个开始阅读的好地方,因为它说明了需求。我的答案中的链接谈到了补充。
猜你喜欢
  • 2013-12-26
  • 2014-04-02
  • 2011-11-21
  • 2022-01-10
  • 2023-03-11
  • 2019-12-04
  • 1970-01-01
  • 2012-09-18
  • 2011-04-26
相关资源
最近更新 更多