Почему system () не работает с кодом ошибки 127? - PullRequest
18 голосов
/ 12 марта 2012

В системе Linux я пытаюсь вызвать программу во время выполнения с помощью вызова system(). Системный вызов завершается с кодом возврата, не равным нулю.

Вызов WEXITSTATUS по коду ошибки дает "127".

Согласно справочной странице системы этот код указывает, что /bin/sh не может быть вызван:

В случае, если /bin/sh не может быть выполнен, статус выхода будет таким, как у команды, которая выполняет exit(127).

Я проверил: /bin/sh - это ссылка на bash. bash там. Я могу выполнить его из оболочки.

Теперь, как я могу узнать, почему /bin/sh не может быть вызван? Любая история ядра или что-то?

Edit:

После очень полезного совета (см. Ниже) я strace -f -p <PID> процесс. Вот что я получаю во время system звонка:

Process 16080 detached
[pid 11779] <... select resumed> )      = ? ERESTARTNOHAND (To be restarted)
[pid 11774] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 127}], 0, NULL) = 16080
[pid 11779] --- SIGCHLD (Child exited) @ 0 (0) ---
[pid 11779] rt_sigaction(SIGCHLD, {0x2ae0ff898ae2, [CHLD], SA_RESTORER|SA_RESTART, 0x32dd2302d0},  <unfinished ...>
[pid 11774] rt_sigaction(SIGINT, {0x2ae1042070f0, [], SA_RESTORER|SA_SIGINFO, 0x32dd2302d0},  <unfinished ...>
[pid 11779] <... rt_sigaction resumed> {0x2ae0ff898ae2, [CHLD], SA_RESTORER|SA_RESTART, 0x32dd2302d0}, 8) = 0
[pid 11779] sendto(5, "a", 1, 0, NULL, 0 <unfinished ...>
[pid 11774] <... rt_sigaction resumed> NULL, 8) = 0
[pid 11779] <... sendto resumed> )      = 1
[pid 11779] rt_sigreturn(0x2 <unfinished ...>
[pid 11774] rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x32dd2302d0},  <unfinished ...>
[pid 11779] <... rt_sigreturn resumed> ) = -1 EINTR (Interrupted system call)
[pid 11779] select(16, [9 15], [], NULL, NULL <unfinished ...>
[pid 11774] <... rt_sigaction resumed> NULL, 8) = 0
[pid 11774] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 11774] write(1, "Problems calling nvcc jitter: ex"..., 49) = 49
[pid 11774] rt_sigaction(SIGINT, {0x1, [], SA_RESTORER, 0x32dd2302d0}, {0x2ae1042070f0, [], SA_RESTORER|SA_SIGINFO, 0x32dd2302d0}, 8) = 0
[pid 11774] rt_sigaction(SIGQUIT, {0x1, [], SA_RESTORER, 0x32dd2302d0}, {SIG_DFL, [], SA_RESTORER, 0x32dd2302d0}, 8) = 0
[pid 11774] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
[pid 11774] clone(Process 16081 attached (waiting for parent)
Process 16081 resumed (parent 11774 ready)
child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fff0177ab68) = 16081
[pid 16081] rt_sigaction(SIGINT, {0x2ae1042070f0, [], SA_RESTORER|SA_SIGINFO, 0x32dd2302d0},  <unfinished ...>
[pid 11774] wait4(16081, Process 11774 suspended
 <unfinished ...>
[pid 16081] <... rt_sigaction resumed> NULL, 8) = 0
[pid 16081] rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x32dd2302d0}, NULL, 8) = 0
[pid 16081] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 16081] execve("/bin/sh", ["sh", "-c", 0xdda1d98], [/* 58 vars */]) = -1 EFAULT (Bad address)
[pid 16081] exit_group(127)             = ?
Process 11774 resumed

Когда речь идет о звонке на /bin/sh, он говорит, что адрес плохой. Почему это?

Edit:

Здесь вся часть, которая включает сбой system (здесь уже есть безопасная копия в буфер):

  std::ostringstream jit_command;

  jit_command << string(CUDA_DIR) << "/bin/nvcc -v --ptxas-options=-v ";
  jit_command << "-arch=" << string(GPUARCH);
  jit_command << " -m64 --compiler-options -fPIC,-shared -link ";
  jit_command << fname_src << " -I$LIB_PATH/include -o " << fname_dest;

  string gen = jit_command.str();
  cout << gen << endl;

  char* cmd = new(nothrow) char[gen.size()+1];
  if (!cmd) ___error_exit("no memory for jitter command");
  strcpy(cmd,gen.c_str());

  int ret;

  if (ret=system(cmd)) {

    cout << "Problems calling nvcc jitter: ";

    if (WIFEXITED(ret)) {
      printf("exited, status=%d\n", WEXITSTATUS(ret));
    } else if (WIFSIGNALED(ret)) {
      printf("killed by signal %d\n", WTERMSIG(ret));
    } else if (WIFSTOPPED(ret)) {
      printf("stopped by signal %d\n", WSTOPSIG(ret));
    } else if (WIFCONTINUED(ret)) {
      printf("continued\n");
    } else {
      printf("not recognized\n");
    }

    cout << "Checking shell.. ";
    if(system(NULL))
      cout << "ok!\n";
    else
      cout << "nope!\n";

    __error_exit("Nvcc error\n");

  }
  delete[] cmd;
  return true;

Выход:

/usr/local/cuda/bin/nvcc -v --ptxas-options=-v -arch=sm_20 -m64 --compiler-options -fPIC,-shared -link bench_cudp_Oku2fm.cu -I$LIB_PATH/include -o bench_cudp_Oku2fm.o
Problems calling nvcc jitter: exited, status=127
Checking shell.. ok!

Редактировать (первая версия кода):

string gen = jit_command.str();
cout << gen << endl;
int ret;
if (ret=system(gen.c_str())) {
  ....

Сложность создания строки здесь не проблема. Как показывает strace, проблема заключается в «неправильном адресе». Это законная строка. «Неправильный адрес» не должен возникать.

Насколько я знаю, std::string::c_str() возвращает const char *, который может указывать на пустое место в libc ++, где может храниться копия строки только для чтения.

К сожалению, ошибка на самом деле не воспроизводима. Вызов system завершается успешно несколько раз, прежде чем происходит сбой.

Я не хочу быть поспешным, но пахнет как ошибка в ядре, libc или аппаратном обеспечении.

Edit:

Я произвел более подробный strace вывод (strace -f -v -s 2048 -e trace=process -p $!) сбойного системного вызова execve:

Первый последующий звонок:

[pid  2506] execve("/bin/sh", ["sh", "-c", "/usr/local/cuda/bin/nvcc -v --ptxas-options=-v -arch=sm_20 -m64 --compiler-options -fPIC,-shared -link /home/user/toolchain/kernels-empty/bench_cudp_U11PSy.cu -I$LIB_PATH/include -o /home/user/toolchain/kernels-empty/bench_cudp_U11PSy.o"], ["MODULE_VERSION_STACK=3.2.8", ... ]) = 0

Теперь провал:

[pid 17398] execve("/bin/sh", ["sh", "-c", 0x14595af0], <list of vars>) = -1 EFAULT (Bad address)

Здесь <list of vars> идентично. Кажется, это не список переменных среды, которые вызывают неправильный адрес. Как отметил Крис Додд, третий аргумент execve - это необработанный указатель 0x14595af0, который strace считает (и ядро ​​соглашается) недопустимым. strace не распознает его как строку (поэтому печатает шестнадцатеричное значение, а не строку).

Edit:

Я вставил print из значения указателя cmd, чтобы увидеть значение этого указателя в родительском процессе:

  string gen = jit_command.str();
  cout << gen << endl;
  char* cmd = new(nothrow) char[gen.size()+1];
  if (!cmd) __error_exit("no memory for jitter command");
  strcpy(cmd,gen.c_str());
  cout << "cmd = " << (void*)cmd << endl;
  int ret;
  if (ret=system(cmd)) {
    cout << "failed cmd = " << (void*)cmd << endl;
    cout << "Problems calling nvcc jitter: ";

Вывод (для сбойного вызова):

cmd = 0x14595af0
failed cmd = 0x14595af0
Problems calling nvcc jitter: exited, status=127
Checking shell.. ok!

Это то же значение указателя, что и в третьем аргументе из strace. (Я обновил вывод strace выше).

Относительно 32-битного вида указателя cmd: я проверил значение указателя cmd для последующего вызова. Не вижу никакой разницы в структуре. Это одно из значений cmd, когда system вызов завершается успешно:

cmd = 0x145d4f20

Итак, до вызова system указатель действителен. Как показывает вывод strace, указанный выше, дочерний процесс (после вызова fork) получает правильное значение указателя. Но по какой-то причине значение указателя помечается как недопустимое в дочернем процессе.

Сейчас мы думаем, что либо:

  • ошибка libc / kernel
  • аппаратная проблема

Edit:

А пока позвольте мне опубликовать обходной путь. Так глупо быть вынужденным реализовать что-то подобное ... но это работает. Поэтому следующий кодовый блок выполняется в случае сбоя вызова system. Он распределяет новые командные строки и повторяет попытки, пока не выполнится (ну, не бесконечно).

    list<char*> listPtr;
    int maxtry=1000;
    do{
      char* tmp = new(nothrow) char[gen.size()+1];
      if (!tmp) __error_exit("no memory for jitter command");
      strcpy(tmp,gen.c_str());
      listPtr.push_back( tmp );
    } while ((ret=system(listPtr.back())) && (--maxtry>0));

    while(listPtr.size()) {
      delete[] listPtr.back();
      listPtr.pop_back();
    }

Edit:

Я только что увидел, что этот обходной путь в одном конкретном прогоне не работал. Он прошел весь путь, 1000 попыток, все с недавно выделенными cmd строками команд. Все 1000 провалились. Не только это. Я пробовал на другом хосте Linux (та же конфигурация Linux / программного обеспечения).

Принимая это во внимание, можно исключить аппаратную проблему. (Должно быть на 2 физически разных хостах). Остается ошибка ядра ??

Edit:

Торек, я попытаюсь установить модифицированный system вызов.Дай мне немного времени для этого.

Ответы [ 2 ]

5 голосов
/ 13 марта 2012

Это странно. strace понимает, что аргументы для execve являются (указателями на) строками, поэтому он выводит указанные строки, если указатель недействителен - в этом случае он выводит необработанное шестнадцатеричное значение указателя. Так что прямая линия

[pid 16081] execve("/bin/sh", ["sh", "-c", 0xdda1d98], [/* 58 vars */]) = -1 EFAULT (Bad address)

имеет смысл - третий аргумент execve - это необработанный указатель 0xdda1d98, который strace считает (и ядро ​​соглашается) недопустимым. Таким образом, вопрос в том, как получить недопустимый указатель. Это должен быть cmd, который только что вернулся с нового.

Я бы предложил поставить строку

printf("cmd=%p\n", cmd);

непосредственно перед системным вызовом, чтобы выяснить, что код C считает указателем.

Глядя на остальную часть strace, похоже, что вы работаете в 64-битной системе (из напечатанных указателей), а неверный 0xdda1d98 выглядит как 32-битный указатель, так что, похоже, какой-то сбой 32/64 бита (кто-то только сохраняет и восстанавливает 32 бита 64-битного регистра или что-то подобное).

2 голосов
/ 13 марта 2012

Отключая / расширяя ответ @Chris Dodd, учтите, что system само выглядит (специально упрощено) следующим образом:

int system(char *cmd) {
    pid_t pid = fork();
    char *argv[4];
    extern char **environ;

    if (pid == 0) { /* child */
        argv[0] = "sh";
        argv[1] = "-c";
        argv[2] = cmd;
        argv[3] = NULL;
        execve("/bin/sh", argv, environ);
        _exit(127);
    }
    if (pid < 0) ... handle error ...
    ... use OS wait() calls to wait for result from child process ...
    return status; /* as provided by sh -c, or from _exit(127) above */
}

Учитывая, что "64-битная система" и "регистр, кажется, заблокированы"на 32 бита "возможно, стоит сделать objdump для кода и посмотреть, установлен ли argv [2] из регистра, чьи старшие биты могут быть каким-то образом потеряны во время вызова clone (где у меня fork выше, glibc использует clone для эффективности).


Обновление: согласно выводу strace, вызов клона не использует CLONE_VM и CLONE_VFORK (не уверен, почему нет, это должно сделать вызов намного более эффективным), так что дочерний элемент является "нормальным" дочерним элементом (aв старом стиле Unix fork).Коллега предположил, что, возможно, неверный адрес находится на карте, которая не должна копироваться в дочерний процесс.Содержимое /proc/self/maps будет интересно после сбоя;мы могли бы посмотреть, как отображается неверный адрес.Сравнение этих карт с теми, что у ребенка, было бы еще интереснее.Однако, чтобы получить те, которые есть у ребенка, вам нужно переопределить glibc версию system и добавить что-то для чтения /proc/self/maps после сбоя execve, прежде чем выполнять _exit.
...