c++ - 二进制数据作为命令行参数
问题描述
我有一个简单的 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++ 中使用它,这个问题只是出于好奇
解决方案
此答案是用 C 编写的,但可以编译为 C++ 并且在两者中的工作方式相同。我引用了 C11 标准;C++ 标准中有等价的定义。
没有将空字节传递给程序参数的好方法
C11 §5.1.2.2.1 程序启动:
如果 的值argc
大于零,则argv[0]
通过argv[argc-1]
inclusive 的数组成员应包含指向字符串的指针,这些指针在程序启动之前由主机环境给出实现定义的值。C11 §7.1.1 术语定义
字符串是由第一个空字符终止并包括第一个空字符的连续字符序列。
这意味着传递给main()
in的每个参数argv
都是一个以 null 结尾的字符串。在字符串末尾的空字节之后没有可靠的数据——搜索那里会超出字符串的范围。
因此,正如问题评论中详细指出的那样,在正常的事件过程中,不可能通过参数列表将空字节发送给程序,因为空字节被解释为每个参数的结尾。
通过特别协议
这并没有留下太多的回旋余地。但是,如果调用/调用程序和被调用/调用程序都同意约定,那么即使有标准施加的限制,您也可以将任意二进制数据(包括任意空字节序列)传递给被调用程序 -直到实现对参数列表长度的限制。
公约必须遵循以下原则:
- 所有参数(除了
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
$
假设仅空字符串被识别为有效参数,这在标准范围内严格工作。实际上,这些参数在内存中已经是连续的,因此在许多平台上可能可以避免将复制阶段复制到缓冲区中。但是,该标准并未规定参数字符串在内存中连续布局。
如果您需要二进制数据的多个参数,您可以修改约定。例如,您可以采用一个字符串的控制参数,该参数指示有多少后续物理参数构成一个逻辑二进制参数。
所有这些都依赖于程序按照约定解释参数列表。这不是一个真正的通用解决方案。
推荐阅读
- node.js - 我无法在 discord.js 中获得公会名称
- java - Spring Data JPA - 如何从 jar 中读取实体?- java.lang.IllegalArgumentException:不是托管类型
- nestjs - 如何使用存储库导出创建服务?
- haskell - 派生方式:无法派生良好的实例
- asp.net - Robots.txt 访问网站时显示的内容
- android - 将文件保存在共享外部存储的下载文件夹中
- ios - when using constraints in ios autolayout, are there benefits to shorter constraint chains?
- vmware - 将我的 vmware esxi 主机从 5.5 升级到 7.0,但看不到现有数据存储
- node.js - 当连接了许多客户端时,我的 socket.io 服务器开始随机断开客户端(出于“ping 超时”原因)
- docker - 如何查看 docker 容器使用卷的最后日期