Segfault на execvp с использованием аргументов командной строки

Я работаю над заданием по программированию, в котором меня просят написать код, способный прочитать команду из командной строки вместе с ее аргументом и выполнить программу с помощью команды execvp. Это мой код:

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

int main(int argc, const char * argv[]) {

    char **cmd;
    int i;

    if (argc == 1){

        fprintf(stdout, "No command inserted!\n");
        exit(EXIT_SUCCESS);

    }

    cmd = (char **) malloc( argc * sizeof(char *));
    cmd[0] = strdup(argv[1]);

    if (argc > 2){

    for (i = 1 ; i < argc - 1  ; i++ ){

            cmd[i] = (char *) malloc( strlen(argv[i+1]) * sizeof(char) );
        strcpy(cmd[i], argv[i+1]);

    }

    cmd[argc] = NULL;
    execvp(cmd[0], cmd);

    fprintf(stderr, "Failed Execution or not existing command!!\n");
    exit(EXIT_FAILURE);

    }

    cmd[1] = NULL;

    execvp(cmd[0], cmd);

    fprintf(stderr, "Failed Execution or not existing command!!\n");
    exit(EXIT_FAILURE);

    return 0;
}

Код отлично работает, вводя команду без аргументов, например:

./a.out who
./a.out ls

но приводит к «Ошибке сегментации: 11» при написании таких команд, как:

./a.out ls -l
./a.out more file.txt

не могу понять где проблема...


person Andre Fox    schedule 07.09.2017    source источник
comment
Я могу запустить ваш код с помощью ls -l отлично, никаких изменений   -  person Tyler    schedule 07.09.2017
comment
какой машиной пользуетесь??   -  person Andre Fox    schedule 07.09.2017
comment
Ubuntu с использованием компилятора gcc   -  person Tyler    schedule 07.09.2017
comment
@Tyler: то, что код не дает сбоев на конкретной машине, не означает, что он свободен от неопределенного поведения. В случае с UB (как в этой программе) выполнение может зависнуть, но может и сделать что-то другое, может даже сработать.   -  person Stephan Lechner    schedule 07.09.2017
comment
относительно: cmd[i] = (char *) malloc( strlen(argv[i+1]) * sizeof(char) ); 1) возвращаемый тип из функций распределения кучи (malloc, calloc, realloc) - void*, который может быть назначен любому указателю. Приведение просто загромождает код (и подвержено ошибкам) ​​2) всегда проверяйте (!=NULL) возвращаемое значение, чтобы убедиться, что операция прошла успешно. 3) функция strlen() возвращает индекс завершающему байту NUL, но индексы начинаются с 0, поэтому для получения правильной длины необходимо использовать +1. (продолжение)   -  person user3629249    schedule 08.09.2017
comment
(продолжение) 4) выражение sizeof(char) определено в стандарте C как 1. Умножение чего-либо на 1 не имеет абсолютно никакого эффекта. Таким образом, использование этого выражения просто загромождает код.   -  person user3629249    schedule 08.09.2017
comment
когда execvp() обнаруживает ошибку и возвращается, она устанавливает errno для указания причины ошибки. Поэтому лучше отображать соответствующее сообщение об ошибке. Таким образом, код может выиграть, заменив эту строку: fprintf(stderr, "Failed Execution or not existing command!!\n"); на perror( " execvp Failed Execution or not existing command!!\n");, которая выведет в stderr заключенный текст, за которым следует сообщение о системной ошибке.   -  person user3629249    schedule 08.09.2017
comment
@Tyler, опубликованный код содержит (как минимум) два случая неопределенного поведения. Неопределенное поведение означает, что может случиться что угодно, включая код, который «кажется» работающим.   -  person user3629249    schedule 08.09.2017
comment
подсказка: зачем копировать содержимое командной строки. Могло бы быть намного проще просто использовать: execvp( argv[1], argv+sizeof( char * ));   -  person user3629249    schedule 08.09.2017
comment
размещенный код имеет некоторые утечки памяти. Существует также логическая проблема, так как исходный процесс заменяется процессом, запущенным с execvp(). Настоятельно рекомендуем: вызовите fork(), чтобы запустить новый процесс, затем в «дочернем» процессе вызовите execvp(), а в родительском процессе вызовите free() для каждой из областей, выделенных malloc().   -  person user3629249    schedule 08.09.2017


Ответы (2)


Есть как минимум две точки, в которых вы превышаете границы массива:

cmd[i] = (char *) malloc( strlen(argv[i+1]) * sizeof(char) )

является единственным, так как он не учитывает завершающий символ \0, так что strcpy(cmd[i], argv[i+1]) затем выйдет за пределы. Напишите ...

cmd[i] = (char *) malloc( (strlen(argv[i+1]) + 1) * sizeof(char) )

вместо. Кстати: sizeof(char) всегда 1 по определению.

Дальше,

cmd = (char **) malloc( argc * sizeof(char *));
...
cmd[argc] = NULL;

опять один раз. Должно быть cmd = (char **) malloc( (argc+1) * sizeof(char *)), если вы хотите назначить cmd[argc] = NULL.

person Stephan Lechner    schedule 07.09.2017

У меня была аналогичная проблема. execvp(), казалось, ничего не делал, но на самом деле дочерний процесс, под которым я его запускал, аварийно завершал работу из-за segfault. Исправление закончилось исправлением массивов символов, которые я использовал для хранения различных преобразований ввода. Я вообще не использовал malloc() для некоторых из этих массивов (я не программист C и не горжусь этой ошибкой), что стало проблемой только по мере усложнения программы. Обычному пользователю, приведенному сюда, я бы посоветовал тщательно проверять наличие нераспределенных указателей или, как указано выше, указателей недостаточного размера. Это произошло со мной, несмотря на то, что массив, переданный в execvp(), был правильно выделен и имел необходимое содержимое (как определено через GDB) с правильными типами.

person Andrew Puglionesi    schedule 24.11.2017