二进制数据作为命令行参数

时间:2018-06-06 16:51:23

标签: c++ c command-line arguments

我有一个简单的c ++程序(和c类似的程序)只打印出第一个参数

#include <iostream>

int main(int argc, char** argv)
{
    if(argc > 1)
        std::cout << ">>" << argv[1] << "<<\n";
}

我可以传递二进制数据(我已尝试过bash)作为参数,如

$./a.out $(printf "1\x0123")
  >>1?23<<

如果我尝试传递空值,我会得到

./a.out $(printf "1\x0023")
bash: warning: command substitution: ignored null byte in input
>>123<<

显然bash(?)不允许这个

但是可以通过这种方式发送null作为命令行参数吗? c或c ++是否对此有任何限制?

编辑:我不是在日常的c ++中使用它,这个问题只是出于好奇

1 个答案:

答案 0 :(得分:1)

这个答案是用C语言编写的,但可以编译为C ++,并且在两者中都是一样的。我引用C11标准; C++ standards中有相同的定义。

没有把空字节传递给程序参数的好方法

  

<强> C11 §5.1.2.2.1 Program startup
  如果argc的值大于零,则数组成员argv[0]argv[argc-1]包含指向字符串的指针,在程序启动之前由主机环境给出实现定义的值

     

<强> C11 §7.1.1 Definitions of terms
  字符串是由第一个空字符终止并包含第一个空字符的连续字符序列。

这意味着传递给main()argv的每个参数都是以空字符结尾的字符串。在字符串末尾的空字节之后没有可靠的数据 - 搜索将访问超出字符串的范围。

因此,正如在问题的注释中详细说明的那样,在正常的事件过程中不可能通过参数列表向程序获取空字节,因为空字节被解释为每个参数的结尾。

通过特别协议

这并没有留下太多的蠕动空间。但是,如果调用/调用程序和被调用/调用程序都同意约定,那么,即使有标准规定的限制,您也可以将任意二进制数据(包括任意空字节序列)传递给调用程序 - 达到实施所强加的参数列表长度的限制。

惯例必须遵守:

  • 所有参数(argv[0]除外,被忽略,最后一个参数argv[argc-1])由非空字节流后跟空值组成。
  • 如果需要相邻的空值,则必须在命令行上提供空参数。
  • 如果需要尾随空值,则必须提供空参数作为命令行的最后一个参数。

这可能导致一个程序,如(null19.c):

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void hex_dump(const char *tag, size_t size, const char *buffer);

int main(int argc, char **argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s arg1 [arg2 '' arg4 ...]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    size_t len_args = 0;
    for (int i = 1; i < argc; i++)
        len_args += strlen(argv[i]) + 1;

    char buffer[len_args];

    size_t offset = 0;
    for (int i = 1; i < argc; i++)
    {
        size_t arglen = strlen(argv[i]) + 1;
        memmove(buffer + offset, argv[i], strlen(argv[i]) + 1);
        offset += arglen;
    }
    assert(offset != 0);
    offset--;

    hex_dump("Argument list", offset, buffer);
    return 0;
}

static inline size_t min_size(size_t x, size_t y) { return (x < y) ? x : y; }

static void hex_dump(const char *tag, size_t size, const char *buffer)
{
    printf("%s (%zu):\n", tag, size);
    size_t offset = 0;
    while (size != 0)
    {
        printf("0x%.4zX:", offset);
        size_t count = min_size(16, size);
        for (size_t i = 0; i < count; i++)
            printf(" %.2X", buffer[offset + i] & 0xFF);
        putchar('\n');
        size -= count;
        offset += count;
    }
}

可以使用以下方法调用:

$ ./null19 '1234' '5678' '' '' '' '' 'def0' ''
Argument list (19):
0x0000: 31 32 33 34 00 35 36 37 38 00 00 00 00 00 64 65
0x0010: 66 30 00
$

第一个参数被认为由5个字节组成 - 四个数字和一个空字节。第二个是类似的。第三个到第六个参数每个代表一个空字节(如果你需要大量连续的空字节会很痛苦),然后还有另一个五个字节的字符串(三个字母,一个数字,一个空字节)。最后一个参数为空,但确保最后有一个空字节。如果省略,则输出将不包括该最终终端空字节。

$ ./null19 '1234' '5678' '' '' '' '' 'def0' 
Argument list (18):
0x0000: 31 32 33 34 00 35 36 37 38 00 00 00 00 00 64 65
0x0010: 66 30
$

除了数据中没有尾随空字节外,这与之前相同。问题中的两个例子很容易处理:

$ ./null19 $(printf "1\x0123")
Argument list (4):
0x0000: 31 01 32 33
$ ./null19 1 23
Argument list (4):
0x0000: 31 00 32 33
$

这严格遵循标准,假设只有空字符串被识别为有效参数。实际上,这些参数在内存中已经是连续的,因此在许多平台上都可以避免将复制阶段放入缓冲区。但是,该标准没有规定参数字符串在内存中是连续布局的。

如果您需要具有二进制数据的多个参数,则可以修改约定。例如,您可以获取字符串的控制参数,该参数指示有多少后续物理参数构成一个逻辑二进制参数。

所有这些都依赖于按照约定解释参数列表的程序。这不是一般的解决方案。