20 Stimmen

Warum schlägt system() mit Fehlercode 127 fehl?

Auf einem Linux-System versuche ich, ein Programm zur Laufzeit mit der system() anrufen. Der Systemaufruf wird mit einem Rückgabewert ungleich Null beendet.

Aufruf von WEXITSTATUS auf den Fehlercode gibt "127".

Laut der Manpage des Systems zeigt dieser Code an, dass /bin/sh konnte nicht aufgerufen werden:

Für den Fall /bin/sh konnte nicht ausgeführt werden, wird der Exit-Status der eines Befehls sein, der exit(127) .

Ich habe es überprüft: /bin/sh ist ein Link zu bash . bash ist da. Ich kann es über die Shell ausführen.

Wie kann ich nun herausfinden, warum /bin/sh konnte nicht aufgerufen werden ? Gibt es eine Kernelgeschichte oder ähnliches?

Editer :

Nach dem sehr hilfreichen Tipp (siehe unten) i strace -f -p <PID> den Prozess. Das erhalte ich während der system anrufen:

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

Wenn es um den Aufruf zu /bin/sh es wird eine falsche Adresse angezeigt. Warum das?

Editer :

Hier der ganze Teil, der das Scheitern betrifft system (hier ist bereits die sichere Kopie in einen Puffer vorhanden):

  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;

Sortie :

/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!

Bearbeiten (erste Version des Codes):

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

Die Komplexität der String-Erstellung ist hier nicht das Problem. Wie strace zeigt, dass eine "falsche Adresse" das Problem ist. Es ist eine legale Zeichenfolge. Eine "schlechte Adresse" sollte nicht vorkommen.

Soweit ich weiß, ist die std::string::c_str() gibt eine const char * die auf einen Scratch-Space von libc++ verweisen könnte, in dem eine schreibgeschützte Kopie der Zeichenkette aufbewahrt werden könnte.

Leider ist der Fehler nicht wirklich reproduzierbar. Der Aufruf von system mehrmals erfolgreich ist, bevor es scheitert.

Ich will nicht voreilig sein, aber es riecht nach einem Fehler entweder im Kernel, in der libc oder in der Hardware.

Editer :

Ich habe ein ausführlicheres strace Ausgabe ( strace -f -v -s 2048 -e trace=process -p $! ) des ausfallenden execve Systemaufruf:

Zunächst ein anschließender Anruf:

[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

Und jetzt das Scheitern:

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

Hier <list of vars> ist identisch. Es scheint nicht die Liste der Umgebungsvariablen zu sein, die die falsche Adresse verursacht. Wie Chris Dodd erwähnte, ist das dritte Argument von execve der rohe Zeiger 0x14595af0, den strace für ungültig hält (und der Kernel stimmt dem zu). strace nicht als Zeichenkette erkennt (und daher den Hex-Wert und nicht die Zeichenkette ausgibt).

Editer :

Ich habe den Ausdruck des Zeigerwerts eingefügt cmd um zu sehen, welchen Wert dieser Zeiger im übergeordneten Prozess hat:

  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: ";

Ausgabe (für den fehlgeschlagenen Aufruf):

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

Es ist derselbe Zeigerwert wie das 3. Argument von strace . (Ich habe die strace Ausgabe oben).

Bezüglich des 32bit-Aussehens des cmd Zeiger: Ich habe den Wert des cmd Zeiger für einen nachfolgenden Aufruf. Ich kann keinen Unterschied in der Struktur erkennen. Das ist einer der Werte von cmd wenn dann system Der Aufruf ist erfolgreich:

cmd = 0x145d4f20

Also, bevor die system Aufruf ist der Zeiger gültig. Da der strace Die obige Ausgabe legt nahe, dass der Kindprozess (nach dem Aufruf von fork ) erhält den richtigen Zeigerwert. Aber aus irgendeinem Grund wird der Zeigerwert im Kindprozess als ungültig markiert.

Im Moment denken wir, dass es entweder:

  • libc/Kernel-Fehler
  • Hardware-Problem

Editer :

In der Zwischenzeit möchte ich eine Problemlösung anbieten. Es ist so dumm, gezwungen zu sein, so etwas zu implementieren... aber es funktioniert. Der folgende Codeblock wird also ausgeführt, wenn die system Anruf scheitert. Er ordnet neue Befehlszeichenfolgen zu und versucht es so lange, bis er erfolgreich ist (allerdings nicht unbegrenzt).

    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();
    }

Editer :

Ich habe gerade gesehen, dass diese Abhilfe in einem bestimmten Durchlauf nicht funktioniert hat. Es ging den ganzen Weg, 1000 Versuche, alle mit neu zugewiesenen cmd Befehlszeichenfolgen. Alle 1000 schlugen fehl. Nicht nur dies. Ich habe es auf einem anderen Linux-Host versucht (allerdings mit derselben Linux-/Softwarekonfiguration).

Wenn man dies berücksichtigt, könnte man ein Hardwareproblem ausschließen. (Dann muss es sich um 2 physisch verschiedene Hosts handeln). Bleibt ein Kernel-Bug ??

Editer :

torek, ich werde versuchen, eine modifizierte system anrufen. Geben Sie mir dafür etwas Zeit.

5voto

Chris Dodd Punkte 109402

Das ist eine seltsame Sache. strace versteht, dass die Argumente für execve (Zeiger auf) Zeichenketten sind, also gibt es die Zeichenketten aus, auf die gezeigt wird, es sei denn, der Zeiger ist ungültig -- in diesem Fall gibt es den rohen Hex-Wert des Zeigers aus. Also die strace-Zeile

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

macht absolut Sinn -- das 3. Argument für execve ist der rohe Zeiger 0xdda1d98, den strace für ungültig hält (und der Kernel stimmt dem zu). Die Frage ist also, wie ein ungültiger Zeiger hierher kommt. Dies sollte cmd sein, das gerade von new zurückkam.

Ich würde vorschlagen, die Zeile

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

kurz vor dem Systemaufruf, um herauszufinden, wofür der C-Code den Zeiger hält.

Wenn man sich den Rest des Strace anschaut, sieht es so aus, als ob Sie auf einem 64-Bit-System laufen (von den Zeigern, die gedruckt werden), und das ungültige 0xdda1d98 sieht wie ein 32-Bit-Zeiger aus, also scheint es eine Art von 32/64-Bit-Schlamassel zu sein (jemand, der nur 32 Bits eines 64-Bit-Registers speichert und wiederherstellt, oder so etwas).

2voto

torek Punkte 381305

Um die Antwort von @Chris Dodd zu ergänzen, sollten Sie Folgendes bedenken system selbst sieht (absichtlich stark vereinfacht) wie folgt aus:

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 */
}

In Anbetracht von "64-Bit-System" und "Register scheint bei 32 Bits abgehackt zu sein" könnte es sich lohnen, einen objdump auf den Code zu machen und zu sehen, ob argv[2] von einem Register gesetzt wird, dessen obere Bits irgendwie während der clone Aufruf (wo ich die fork oben, glibc verwendet clone für Effizienz).


Update: Laut der Strace-Ausgabe verwendet der Clone-Aufruf nicht CLONE_VM y CLONE_VFORK (ich bin mir nicht sicher, warum nicht, das sollte den Aufruf viel effizienter machen), so dass das Kind ein "normales" Kind ist (a la old-Unix-style fork ). Ein Kollege schlug vor, dass sich die fehlerhafte Adresse vielleicht in einer Map befindet, die nicht in den Kindprozess kopiert werden soll. Der Inhalt von /proc/self/maps wäre nach dem Ausfall interessant; wir könnten uns ansehen, wie die fehlerhafte Adresse abgebildet wird. Noch interessanter wäre es, diese Zuordnungen mit denen des Childs zu vergleichen. Um die Adressen im Child zu erhalten, müssen Sie allerdings die Funktion glibc Version von system , und fügen Sie etwas zum Lesen hinzu /proc/self/maps nach dem execve fehlschlägt, bevor man die _exit .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X