Неожиданное поведение форка

У меня есть программа, которая работает бесконечно. В целях тестирования я сделал программу-оболочку, которая убивает другую через определенное время (указанное через аргументы командной строки/терминала). Разветвляемая программа требует, чтобы ей были переданы две папки с одинаковыми именами (у меня нет контроля над этим), поэтому я просто дважды передаю ей один и тот же аргумент, как показано здесь:

pid_t pid = fork();
if(pid == 0)
{
    //build the execution string
    char* test[2];
    test[0] = argv[2];
    test[1] = argv[2];
    test[2] = NULL;
    cout << "test[0] is " << test[0] << endl;
    cout << "test[1] is " << test[1] << endl;
    cout << "argv[1] is " << argv[1] << endl;
    execvp(argv[1],test);
}

Проблема в том, что программа, передаваемая в argv[1], продолжает сбой сегментации. Если я вызываю сам через терминал, он работает без проблем. Я передаю одну и ту же папку в обоих случаях. Может ли кто-нибудь сказать мне, почему он не работает для execvp?

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

изменить: я добавил нулевой термин для проверки, однако это не устранило проблему.

Форма команды точно такая:

<executable> <wrapped prog> <folder> <duration>

В относительных путях это:

Intel/debug/Tester.exe <program> test 10

person Eric    schedule 27.07.2015    source источник
comment
Какую именно командную строку вы даете ей для выполнения?   -  person Chris Britt    schedule 27.07.2015
comment
Массив test должен начинаться с имени исполняемого файла и заканчиваться NULL.   -  person PSkocik    schedule 27.07.2015
comment
Ну, это должно начинаться с имени файла, но это только для целей соглашения.   -  person gandgandi    schedule 27.07.2015
comment
Используйте strace(1) -as strace -f, чтобы понять, что происходит   -  person Basile Starynkevitch    schedule 27.07.2015
comment
У вас должен быть как минимум вызов exit() и, желательно, также сообщение об ошибке, напечатанное после execvp(). Он возвращается только в случае сбоя, но вы не хотите, чтобы ребенок продолжал делать другие вещи в случае сбоя.   -  person Jonathan Leffler    schedule 28.07.2015
comment
Ребенок больше ничего не делает после execvp(), и в нем происходит segfault.   -  person Eric    schedule 28.07.2015


Ответы (5)


Если длина массива статическая, вам может быть лучше с

execlp

execlp(argv[1], argv[1], argv[2], argv[2], (char*)0);

Что касается execvp, массив должен начинаться с имени исполняемого файла и заканчиваться на NULL.

execvp

char* args[] = { argv[1], argv[2], argv[2], NULL };
execvp(argv[1], args);

запуститьWithTimeout

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

/*runWithTimeout.c
  compile with: make runWithTimeout
  run with: ./runWithTimeout seconds program arguments...
*/
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

int main(int argc, char** argv)
{
  assert(argc >= 1+2);
  int pid, status = 1;
  if((pid = fork()) == 0) {
    alarm(atoi(argv[1]));
    execvp(argv[2], argv + 2); 
    /*^the child ends here if execvp succeeds,
    otherwise fall-through and return the default error status of 1
    (once in child (which has no one to wait on) and 
    then in the parent (which gets the status from the child))*/
    perror("Couldn't exec");
  }else if(pid < 0){ perror("Couldn't fork"); };
  wait(&status);
  return status;
}
person PSkocik    schedule 27.07.2015
comment
Для целей отладки у меня есть цикл for, который печатает все переданные аргументы. Все они отображаются правильно, а argc имеет размер 4: 1) сама 2) программа для вызова 3) папка для передачи 4) максимальное время выполнения - person Eric; 27.07.2015
comment
Я только что попробовал execlp и получил те же результаты, что и execvp. - person Eric; 27.07.2015
comment
В этом суть. В любом случае, я добавил целый функционирующий пример. - person PSkocik; 27.07.2015
comment
Обратите внимание, что execlp() нужен аргумент (char *)0 в качестве последнего аргумента. - person Jonathan Leffler; 28.07.2015
comment
Обратите внимание, что хотя perror() хорош, вам также понадобится какая-то форма exit() после того, как execvp() не сработает. - person Jonathan Leffler; 28.07.2015
comment
@JonathanLeffler Это немного хакерски, но здесь используется тот же выход, что и у родителя - wait должен потерпеть неудачу в дочернем элементе, и в этом случае статус остается 1, что родитель получает через свой вызов wait. Спасибо за комментарий execlp. Я наивно думал, что версии l не нуждаются в терминаторе null. - person PSkocik; 28.07.2015
comment
Да, я понимаю, что вы имеете в виду. Я не в восторге от этого, но с этим кодом он работает разумно. Однако, как правило, вы не должны выпускать дочерний элемент из своего блока операторов — исключения, конечно, есть, но чаще всего уместен ранний выход. (Есть те, кто хотел бы использовать один из _exit(), _Exit() или quick_exit() вместо обычного exit(); обычно я просто использую exit() с ненулевым статусом.) - person Jonathan Leffler; 28.07.2015

Массив, передаваемый в качестве аргументов, должен заканчиваться нулем. Например:

char *test[3]={0};
...
person gandgandi    schedule 27.07.2015
comment
Для справки; linux.die.net/man/3/execvp ... execvp() ... функции... Массив указателей должен заканчиваться указателем NULL. - person Niall; 27.07.2015
comment
Я только что снова запустил программу, и это не решило проблему (хотя она ушла дальше). - person Eric; 27.07.2015
comment
Попробуйте объединить команду, которую вы хотите вызвать, со строкой /bin/bash (или чем-то, что у вас есть в бинарном файле bash), чтобы она выглядела так: /bin/bash command. - person gandgandi; 27.07.2015

Вы можете включить дампы ядра (не забудьте отключить их, когда закончите) ulimit -c unlimited. Запустите его перед запуском основного процесса. (Я бы с опаской запускал его в развилке, хотя вы, вероятно, можете.)

Когда ваша программа выйдет из строя, это создаст дамп ядра, который вы можете проверить с помощью gdb.

Чтобы получить помощь с основными файлами, вы можете просто поискать их в Google.

Другое тогда это. Вы можете создать скрипт, который запускает ваш файл. Вы можете использовать скрипт для регистрации вещей.

person TLOlczyk    schedule 27.07.2015

Вы хотите:

char* test[3];
test[0] = argv[2];
test[1] = argv[2];
test[2] = NULL;

Вам нужен параметр NULL, чтобы отметить конец списка параметров.

person David Schwartz    schedule 27.07.2015

Учитывая спецификацию:

Форма команды точно такая:

<executable> <wrapped prog> <folder> <duration>

В относительных путях это:

Intel/debug/Tester.exe <program> test 10

а также:

Разветвляемая программа требует, чтобы ей были переданы две папки с одинаковыми именами…

затем, если вы проверили, что оболочке переданы 4 аргумента, вам нужен код:

pid_t pid = fork();
if (pid == 0)
{
    //build the execution string
    char  *test[4];      // Note the size!
    test[0] = argv[1];   // Program name: argv[0] in exec'd process
    test[1] = argv[2];   // Directory name: argv[1] …
    test[2] = argv[2];   // Directory name: argv[2] …
    test[3] = NULL;      // Null terminator
    cout << "test[0] is " << test[0] << endl;
    cout << "test[1] is " << test[1] << endl;
    cout << "test[2] is " << test[2] << endl;
    execvp(test[0], test);
    cerr << "Failed to exec '" << test[0] << "': " << strerror(errno) << endl;
    exit(1);  // Or throw an exception, or …
}

Редко (но не всегда) есть причина для вызова execvp(), кроме использования идиомы execvp(argv[0], argv) для массива аргументов в argv.

Обратите внимание, что этот код гарантирует, что поток управления не выйдет за пределы блока операторов, который должен представлять дочерний элемент. Последующее продолжение дочернего процесса, обычно на самом деле думая, что это родительский процесс, приводит к путанице. Всегда следите за тем, чтобы дочерний процесс выполнялся или завершался. (Это риторическое преувеличение — да, но в этой идее тоже есть большая доля правды.) Кроме того, поскольку это C++, вам может потребоваться рассмотреть Как завершить код C++?. Это усложняет жизнь. Важным моментом является то, что если дочерний процесс не выполняется, он не будет продолжаться, как если бы это был родительский процесс.

person Jonathan Leffler    schedule 28.07.2015
comment
Мой родительский процесс обрабатывает время и гарантированно завершится после дочернего, поэтому мне не нужно об этом беспокоиться. Единственные аргументы, которые нужны ребенку, это argv[1] и argv[2]. Тем не менее, мне стоит помнить о том, как завершить код. - person Eric; 28.07.2015