Двоичные данные в качестве аргумента командной строки - PullRequest
0 голосов
/ 06 июня 2018

У меня есть простая программа на 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 (?) Не разрешает это

Но возможно ли отправить ноль какаргумент командной строки таким образом?Неужели c или c ++ накладывают какие-либо ограничения на это?

Редактировать: я не использую это в повседневной работе c ++, этот вопрос просто из любопытства

1 Ответ

0 голосов
/ 06 июня 2018

Этот ответ написан на C, но может быть скомпилирован как C ++ и работает одинаково в обоих случаях.Я цитирую из стандарта C11;в стандартах C ++ .

есть эквивалентные определения. Не существует хорошего способа передачи нулевых байтов аргументам программы

C11 §5.1.2.2.1 Запуск программы :
Если значение argc больше нуля, элементы массива argv[0] до argv[argc-1] включительно должны содержать указатели на строки, которыезадаются значения, определяемые реализацией средой хоста до запуска программы.

C11 §7.1.1 Определения терминов
Строка представляет собой непрерывную последовательностьсимволов, оканчивающихся на первый нулевой символ и включающих его.

Это означает, что каждый аргумент, переданный 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
$

Это работает строго в рамках стандарта, предполагая, что только пустые строки распознаются как допустимые аргументы.На практике эти аргументы уже находятся в памяти, поэтому на многих платформах можно было бы избежать фазы копирования в буфер.Однако в стандарте не предусмотрено, что строки аргументов располагаются непрерывно в памяти.

Если вам требуется несколько аргументов с двоичными данными, вы можете изменить соглашение.Например, вы можете взять управляющий аргумент строки, который указывает, сколько последующих физических аргументов составляют один логический двоичный аргумент.

Все это зависит от программ, интерпретирующих список аргументов по согласованию.Это не совсем общее решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...