Подтвердить что ты не робот

Почему сбой системы() с кодом ошибки 127?

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

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

В соответствии с man-страницей системы этот код указывает, что /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 *, который может указывать на пространство царапин libС++, где может храниться только копия строки только для чтения.

К сожалению, ошибка на самом деле не воспроизводится. Вызов 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 не распознает его как строку (поэтому он печатает шестнадцатеричное значение, а не строку).

Edit:

Я вставил печать из значения указателя 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/software tho).

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

Edit:

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

Вывод/продление ответа @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 не использует CLONE_VM и CLONE_VFORK (не уверен, почему нет, они должны сделать вызов намного более эффективным), чтобы ребенок был "нормальным" ребенком (a la old-Unix-style fork ). Коллега предположил, что, возможно, неудачный адрес находится на карте, которая не должна быть скопирована в дочерний процесс. Содержимое /proc/self/maps было бы интересным после сбоя; мы могли бы посмотреть, как отображается неудачный адрес. Сравнение этих карт с таковыми в ребенке было бы еще более интересным. Однако, чтобы получить их в дочернем элементе, вам необходимо переопределить версию glibc system и добавить что-то, чтобы прочитать /proc/self/maps после сбоя execve, прежде чем делать _exit.