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

Binary data as command line argument

本文关键字:命令行 参数 数据 二进制      更新时间:2023-10-16

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

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

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

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

如果我尝试在那里传递一个空值,我会得到

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

显然 bash(?

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

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

这个答案是用 C 语言编写的,但可以编译为 C++ 并且在两者中的工作方式相同。 我引用 C11 标准;C++标准中有等效的定义。

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

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

C11 §7.1.1 术语
的定义 字符串是以第一个空字符结尾并包括第一个空字符的连续字符序列。

这意味着传递给main()inargv的每个参数都是以 null 结尾的字符串。 字符串末尾的空字节之后没有可靠的数据 — 搜索那里将访问字符串的边界之外。

因此,正如问题注释中详细指出的那样,在事件的正常过程中,不可能通过参数列表将空字节传递给程序,因为空字节被解释为每个参数的结尾。

通过特别协议

这并没有留下太多的回旋余地。 但是,如果调用/调用程序和被调用/调用程序都同意约定,那么,即使有标准施加的限制,您也可以将任意二进制数据(包括任意空字节序列(传递给调用的程序 — 直到实现施加的参数列表长度限制。

公约必须遵循以下方针:

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

这可能会导致诸如 (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 "1x0123")
Argument list (4):
0x0000: 31 01 32 33
$ ./null19 1 23
Argument list (4):
0x0000: 31 00 32 33
$

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

如果需要包含二进制数据的多个参数,可以修改约定。 例如,您可以采用字符串的控制参数,该参数指示组成一个逻辑二进制参数的后续物理参数的数量。

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