execvp() вызывает EXC_SOFTWARE и странный цикл cin.getline?

Я запускаю некоторый код на Mac OSX 10.6.6 и XCode 3.2.4, и у меня есть довольно стандартный код: fork(), если pid == 0, то execvp с командой и аргументами (аргументы включают команду как первый элемент в массиве, и массив завершается нулем).

Мы рассмотрим это на моем занятии по операционным системам, и наша задача — написать простую оболочку. Запускайте команды с их аргументами и переключателями, как с перенаправлением (‹ и >), так и с каналом (|). У меня возникает несколько проблем.

1) Иногда я получаю сигнал EXC_SOFTWARE во время отладки (до сих пор я не получил его, если запускаю приложение вне XCode, но я новичок в Mac и не знал бы, как это будет выглядеть, если бы я это сделал)

2) Иногда getline для следующей команды получает мусор, который, кажется, печатается другими couts. Это начинает бесконечно зацикливаться, экспоненциально ломаясь. Я тестировал печать getpid() с каждым приглашением, и только начальный процесс распечатывает их, похоже, у меня нет случайной «форк-бомбы».

Вот что у меня есть до сих пор:

#include <iostream>
#include <string>
#include <unistd.h>

using namespace std;

char** Split(char* buffer, int &count) {
    count = 1;
    for (int i = 0; i < strlen(buffer); i++) {
        if (buffer[i] == ' ') {
            count++;
        }
    }
    const char* delim = " ";
    char* t = strtok(buffer, delim);
    char** args = new char*[count + 1];
    for (int i = 0; i < count; i++) {
        args[i] = t;
        t = strtok(NULL, delim);
    }
    args[count] = 0;
    return args;
}

void Run(char** argv, int argc) {
    int pid = 0;
    if ((pid = fork()) == 0) {
        //for testing purposes, print all of argv
        for (int i = 0; i < argc; i++) {
            cout << "{" << argv[i] << "}" << endl;
        }
        execvp(argv[0], argv);
        cout << "ERROR 1" << endl;
        exit(1);
    } else if (pid < 0) {
        cout << "ERROR 2" << endl;
        exit(2);
    }
    wait(NULL);
}

int main(int argc, char * const argv[]) {
    char buffer[512];
    char prompt[] = ":> ";
    int count = 0;
    while (true) {
        cout << prompt;
        cin.getline(buffer, 512);
        char **split = Split(buffer, count);
        Run(split, count);
    }
}

Это именно то, что у меня есть, вы должны уметь вырезать, вставлять и строить.

Я не силен в C++, и, скорее всего, произойдет утечка памяти, если я не удалю split, но основное внимание я уделяю сигналу EXC_SOFTWARE и смотрю, что я делаю неправильно с проблемой зацикливания. Есть предположения?

РЕДАКТИРОВАТЬ:

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


person Corey Ogburn    schedule 05.02.2011    source источник


Ответы (2)


Одна проблема заключается в том, что вы не проверяете возврат из cin.getline(), поэтому, если вы наберете EOF, код зациклится. У вас также происходит утечка памяти.

Пытаться:

while (cout << prompt && cin.getline(buffer, sizeof(buffer))
{
    int count = 0;
    char **split = Split(buffer, count);
    Run(split, count);
    delete[] split;
}

Код в Split() на самом деле плохо обрабатывает пустые строки. Кажется, что для запуска execvp() требуется эон, когда единственными аргументами являются нулевые указатели, что и происходит, если вы возвращаете пустую строку.


Я могу запускать несколько простых команд (таких как 'vim makefile' и 'make shell', 'ls -l' и 'cat shell.cpp' и т. д. — я даже сделал несколько с более чем двумя аргументами) ОК с этим, и я могу выйти из команды (оболочки) с помощью Control-D и так далее. Я исправил его, чтобы он компилировался без предупреждений от g++ -O -Wall -o shell shell.cpp. Я не исправил код разделения, чтобы он правильно обрабатывал пустые строки или все пустые строки.

#include <iostream>
#include <string>
#include <unistd.h>

using namespace std;

char** Split(char* buffer, int &count) {
    count = 1;
    for (size_t i = 0; i < strlen(buffer); i++) {  // #1
        if (buffer[i] == ' ') {
            count++;
        }
    }
    char** args = new char*[count + 1];
    const char* delim = " ";
    char* t = strtok(buffer, delim);
    for (int i = 0; i < count; i++) {
        args[i] = t;
        t = strtok(NULL, delim);
    }
    args[count] = 0;
    return args;
}

void Run(char** argv, int argc) {
    int pid = 0;
    if ((pid = fork()) == 0) {
        //for testing purposes, print all of argv
        for (int i = 0; i < argc; i++)
        {
            if (argv[i] != 0)  // #2
                cout << "{" << argv[i] << "}" << endl;
            else
                cout << "{ NULL }" << endl;  // #3
        }
        execvp(argv[0], argv);
        cout << "ERROR 1" << endl;
        exit(1);
    } else if (pid < 0) {
        cout << "ERROR 2" << endl;
        exit(2);
    }
    wait(NULL);
}

int main(int argc, char * const argv[]) {
    char buffer[512];
    char prompt[] = ":> ";
    while (cout << prompt && cin.getline(buffer, sizeof(buffer)))  // #4
    {
        int count = 0;
        char **split = Split(buffer, count);
        if (count > 0)  // #5
            Run(split, count);
        delete[] split;  // #6
    }
}

Я отметил существенные изменения (в основном они не такие большие). Я компилирую с GCC 4.2.1 на MacOS X 10.6.6.

Я не могу легко объяснить мусорные символы, которые вы видите в буфере.

person Jonathan Leffler    schedule 05.02.2011
comment
Что поместит EOF в cin? Я думаю, что это корень моей проблемы. Я попробовал ваш код, и теперь он выходит, а не зацикливается. Лично это улучшение ха-ха. Я только тестирую (пока) использование ls и ls -l без кавычек, конечно. Когда я ввожу свою вторую команду, всегда возникает ошибка, независимо от того, что это за две команды. - person Corey Ogburn; 05.02.2011
comment
@corey: Как еще вы завершаете свою оболочку? Заставлять кого-то печатать прерывание неприятно. И если вы перенаправляете ввод из файла, то файл достигает EOF, а затем ваш код выходит из строя с «быстрой бомбой», даже если не «форк-бомбой». Всегда помните об обработке вырожденных случаев — пустой ввод, пустые строки. - person Jonathan Leffler; 05.02.2011
comment
Нам сказали просто завершить работу с помощью Ctrl+C. Я знаю о Ctrl+D и могу объяснить это позже. У меня точно такая же проблема с бомбой, о которой вы говорите. Если я запускаю команду в ответвлении (включая команды с перенаправленным вводом/выводом), разве EOF не повлияет на cin.getline(...) основного процесса? - person Corey Ogburn; 05.02.2011
comment
@Corey: при всем уважении, займись этим сейчас. Если вы перенаправите стандартный ввод с /dev/null, вы получите бомбу подсказки. Если вы перенаправляете из файла, вы в конечном итоге получите EOF для файла, а затем подсказку бомбы. Из-за утечки памяти, если вы оставите его работающим достаточно долго, вы получите ошибку памяти и, возможно, даже дамп ядра (зависит от среды — по умолчанию дампы ядра отключены). Глупо не иметь дело с такими случаями, как EOF и пустая строка (или вся пустая строка, или строка, содержащая несколько пробелов) сейчас, в то время как оболочка проста в обращении. Позже будет сложнее модифицировать обработку ошибок. - person Jonathan Leffler; 05.02.2011
comment
Я понимаю, что вы имеете в виду, и обязательно буду практиковать это на ходу. Тем не менее, как я могу после запуска команды очистить EOF, чтобы получить другую команду? - person Corey Ogburn; 05.02.2011
comment
Оказывается, после моей первой команды в буфере остались байты -17, -100 и -126. т.е. если я запускаю ls, то буфер будет (как и ожидалось) «l», «s», «\ 0», но когда я читаю во второй команде, у меня есть «\ xef», «\ x9c», «\ x82», « л', 'с', '\0'. Есть идеи, почему эти байты остаются в буфере? - person Corey Ogburn; 05.02.2011
comment
И последнее замечание: кажется, что эти дополнительные байты появляются только при отладке в XCode. Если я запускаю программу из терминала, она работает точно так, как ожидалось. Трудно сказать наверняка, учитывая полуслучайное появление вещей, но я могу запустить любое количество основных команд и их аргументов. - person Corey Ogburn; 05.02.2011
comment
@Corey: Хорошо, я считаю, что среды разработки Xcode и GUI слишком сложны в использовании. Вы там сами по себе - я тестирую командную строку. - person Jonathan Leffler; 05.02.2011

Вы делаете предположение, что строка ввода содержит на один токен больше, чем пробелов. Это предположение может не сработать, если строка ввода пуста, заканчивается или начинается с пробела или содержит несколько последовательных пробелов. В этих случаях один из вызовов strtok вернет NULL, и это приведет к сбою разветвленного процесса, когда вы попытаетесь напечатать этот аргумент в Run. Это единственные случаи, когда я столкнулся с проблемами; если вы столкнулись с другими, укажите свой вклад.

Чтобы избежать этого предположения, вы можете выполнять подсчет с помощью strtok так же, как и токенизацию. Как правило, это хорошая идея: если вам нужно, чтобы две вещи совпадали, и вы можете сделать их одинаково, вы вводите дополнительный источник ошибок, если вместо этого делаете их по-разному.

person joriki    schedule 05.02.2011
comment
Назначение требует очень ограниченной проверки ошибок, и я предполагаю, что все входные данные верны. Под правильным я подразумеваю правильно отформатированное и ограниченное для моего приложения выполнение команды, т. е. без странного количества пробелов, без & для запуска асинхронных команд, без команд с несколькими конвейерами и т. д. Что касается еще одного токена, чем пробелы, это то, что команда ls содержит один токен без пробелов, а ls -l имеет 1 пробел, но 2 токена. Ни в коем случае это не законченный проект, я даже не начал ‹, › и | но как только я смогу запускать команды, я не думаю, что у меня возникнут проблемы с ними. Я должен был упомянуть об этом в вопросе, извините. - person Corey Ogburn; 05.02.2011
comment
Хорошо, нет проблем, но тогда, пожалуйста, предоставьте правильный ввод, на котором код не работает - кажется, он работает для меня. - person joriki; 05.02.2011
comment
Я пытаюсь ls и ls -l без кавычек. Хорошие простые команды без побочных эффектов, таких как постоянные попытки запуска сенсорных команд или сбой rm, когда я узнаю о разветвлении. В любом случае, с комментарием Джонатана Леффлера, я делаю успехи, но вторая команда всегда терпит неудачу, независимо от того, какие команды я ввожу. - person Corey Ogburn; 05.02.2011