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

Lesezeit: 12 Minuten

Auf einem Linux-System versuche ich, ein Programm zur Laufzeit mit dem aufzurufen system() Anruf. Der Systemaufruf wird mit einem Rückkehrcode ungleich Null beendet.

Berufung WEXITSTATUS auf dem Fehlercode gibt “127”.

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

Im Falle /bin/sh nicht ausgeführt werden konnte, ist der Exit-Status der eines Befehls, der dies tut exit(127).

Ich überprüfte: /bin/sh ist ein Link zu bash. bash Gibt es. Ich kann es von der Shell ausführen.

Wie kann ich jetzt herausfinden, warum /bin/sh konnte nicht angerufen werden? Irgendeine Kernel-Geschichte oder so etwas?

Bearbeiten:

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

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 Anruf geht /bin/sh es sagt schlechte adresse. Warum das ?

Bearbeiten:

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;

Ausgabe:

/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 Stringerstellung ist hier nicht das Problem. Wie strace zeigt eine “schlechte Adresse” ist das Problem. Es ist eine legale Zeichenfolge. Eine “schlechte Adresse” sollte nicht auftreten.

Soweit ich weiß die std::string::c_str() gibt a zurück const char * das könnte auf einen Scratch Space von libc++ verweisen, wo eine schreibgeschützte Kopie des Strings aufbewahrt werden könnte.

Leider ist der Fehler nicht wirklich reproduzierbar. Der Aufruf an system gelingt mehrmals, bevor es fehlschlägt.

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

Bearbeiten:

Ich produzierte eine ausführlichere strace Ausgang (strace -f -v -s 2048 -e trace=process -p $!) des Versagens execve Systemaufruf:

Zuerst ein nachfolgender 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

Jetzt das Fehlende:

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

Hier <list of vars> ist identisch. Es scheint, dass es nicht die Liste der Umgebungsvariablen ist, die die schlechte Adresse verursachen. Wie Chris Dodd erwähnte, ist das dritte Argument für execve der Rohzeiger 0x14595af0, den Strace für ungültig hält (und der Kernel stimmt zu). strace erkennt es nicht als Zeichenfolge (also druckt es den Hex-Wert und nicht die Zeichenfolge).

Bearbeiten:

Ausdruck des Zeigerwertes habe ich eingefügt cmd um zu sehen, was der Wert dieses Zeigers im übergeordneten Prozess ist:

  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 Anruf):

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

Es ist der gleiche Zeigerwert wie das dritte Argument von strace. (Ich habe die aktualisiert strace Ausgabe oben).

Betrachtet die 32bit-Suche der cmd Zeiger: Ich habe den Wert der überprüft cmd Zeiger für einen nachfolgenden Aufruf. Kann keinen Unterschied in der Struktur erkennen. Das ist einer der Werte von cmd wann dann system Anruf erfolgreich:

cmd = 0x145d4f20

Also vor dem system Aufruf des Zeigers ist gültig. Als die strace Die Ausgabe von oben schlägt den untergeordneten Prozess vor (nach dem Aufruf von fork) erhält den korrekten Zeigerwert. Aber aus irgendeinem Grund wird der Zeigerwert im untergeordneten Prozess als ungültig markiert.

Im Moment denken wir, es ist entweder:

  • libc/Kernel-Fehler
  • Hardwareproblem

Bearbeiten:

Lassen Sie mich in der Zwischenzeit einen Workaround posten. Es ist so dumm, gezwungen zu sein, so etwas zu implementieren … aber es funktioniert. Der folgende Codeblock wird also ausgeführt, falls die system Anruf schlägt fehl. Es weist neue Befehlszeichenfolgen zu und versucht es erneut, bis es erfolgreich ist (also nicht auf unbestimmte Zeit).

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

Bearbeiten:

Ich habe gerade gesehen, dass diese Problemumgehung in einem bestimmten Lauf nicht funktioniert hat. Es ging den ganzen Weg, 1000 Versuche, alle mit neu vergebenen cmd Befehlszeichenfolgen. Alle 1000 sind fehlgeschlagen. Nicht nur das. Ich habe es auf einem anderen Linux-Host versucht (gleiche Linux-/Softwarekonfiguration).

Unter Berücksichtigung dessen würde man ein Hardwareproblem vielleicht ausschließen. (Muss dann auf 2 physisch unterschiedlichen Hosts sein). Bleibt ein Kernel-Bug ??

Bearbeiten:

Torek, ich werde versuchen, eine modifizierte zu installieren system Anruf. Gib mir etwas Zeit dafür.

  • Verwenden strace auf Ihrem Programm, um herauszufinden, was passiert.

    – Basile Starynkevitch

    12. März 2012 um 19:41 Uhr


  • Gehen Sie mit durch das Programm gdb. Eine einfacher zu verwendende Version finden Sie unter pyclewn, wenn Sie vim verwenden, gdb-Modus in emacs, cgdboder dd.

    – Spencer Rathbun

    12. März 2012 um 19:47 Uhr

  • Zeigen Sie uns den aktuellen Code für Ihren Anruf an system().

    – Keith Thompson

    12. März 2012 um 20:23 Uhr

  • Das EFAULT bedeutet, dass Sie einen schlechten Zeiger auf übergeben system(). Wie andere gesagt haben, zeigen Sie uns bitte genau, wie Sie es aufrufen und was vorher mit seinen Parametern passiert.

    – mpontillo

    12. März 2012 um 20:53 Uhr

  • Ziemlich verwirrend. Die Schlüsselzeile in der strace Ausgang: [pid 16081] execve("/bin/sh", ["sh", "-c", 0xdda1d98], [/* 58 vars */]) = -1 EFAULT (Bad address) zeigt, dass die new space ist bei 0xdda1d98 und lässt 58 env-Variablen aus; Die EFAULT-Adresse muss entweder die angezeigte oder eine dieser 58 Umgebungsvariablen sein. Vielleicht könntest du alle Variablen einchecken environ für die Gültigkeit, nur um sicherzustellen, dass sie zu diesem Zeitpunkt alle in Ordnung sind,

    – Torek

    13. März 2012 um 0:43 Uhr


Das ist seltsam. strace versteht, dass die Argumente für execve (Zeiger auf) Strings sind, also gibt es die Strings aus, auf die gezeigt wird, es sei denn, der Zeiger ist ungültig – in diesem Fall gibt es den rohen Hexadezimalwert des Zeigers aus. Also die Strichlinie

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

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

Ich würde vorschlagen, die Linie zu setzen

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

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

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

  • Du liegst absolut richtig. Das system Der Aufruf ist mehrmals erfolgreich, bevor er fehlschlägt. Wenn es gelingt, sieht man das cmd Zeichenfolge als 3. Argument. (Ich werde die Frage aktualisieren). Wenn es fehlschlägt, sehen wir die Hex-Nummer. Ich habe Ihren Rat befolgt und die Zeigerwerte gedruckt. (Siehe Frage)

    – Ritter

    14. März 2012 um 9:12 Uhr

  • Betrachtet die 32bit-Suche der cmd pointerL Ich habe das überprüft cmd Zeiger für einen erfolgreichen Aufruf. Sieht genauso aus (Differenzwert, aber auch 32bit Wert). Siehe Frage

    – Ritter

    14. März 2012 um 11:41 Uhr

  • Riecht nach einem einfachen Pufferüberlauf irgendwo. Hast du das Programm unter valgrind ausgeführt?

    – Nr

    15. März 2012 um 12:17 Uhr

Bedenken Sie, dass Sie die Antwort von @ Chris Dodd huckepack nehmen / erweitern system selbst sieht (absichtlich stark vereinfacht) so 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 */
}

Angesichts des “64-Bit-Systems” und “das Register scheint bei 32 Bit abgeschnitten zu sein” könnte es sich lohnen, einen objdump für den Code durchzuführen und zu sehen, ob argv[2] wird von einem Register gesetzt, dessen obere Bits dabei irgendwie verloren gehen könnten clone Anruf (wo ich habe fork Oben, glibc benutzt clone für Effizienz).


Update: Gemäß der Strace-Ausgabe wird der Clone-Aufruf nicht verwendet CLONE_VM und CLONE_VFORK (nicht sicher, warum nicht, diese sollten 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 möglicherweise in einer Zuordnung befindet, die so eingestellt ist, dass sie nicht in den untergeordneten Prozess kopiert wird. Die Inhalte 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 Karten mit denen des Kindes zu vergleichen. Um die Einsen im untergeordneten Element zu erhalten, müssen Sie jedoch die überschreiben glibc Version von systemund fügen Sie etwas zum Lesen hinzu /proc/self/maps nach dem execve fehlschlägt, bevor Sie das tun _exit.

  • Das ist hier vielleicht zu stark vereinfacht. cmd wird nicht nur als ein Argument übergeben. Berücksichtigen Sie das strace versteht darunter a char*[].

    – Ritter

    13. März 2012 um 22:18 Uhr

  • char **zeigt eigentlich auf das erste Element eines Arrays – in diesem Fall &argv[0]. strace extrahiert dann argv[0]argv[1]…, argv[i] bis argv[i] ist NULLund behandelt dann jeden extrahierten Wert als a char * zeigt auf das erste Element eines C-Strings und extrahiert jedes char bis zum Erreichen des 0-Byte. Da es 0xdda1d98 in seiner Ausgabe nicht erweitert, bedeutet dies, dass 0xdda1d98 die ungültige Adresse ist, die die verursacht EFAULT Error.

    – Torek

    13. März 2012 um 22:23 Uhr


  • Richtig! Update: Ich hatte den Wert des Zeigers cmd um gedruckt zu werden. Siehe Frage. Es ist derselbe Wert, der als drittes Argument eingegeben wird execve und kann nicht als Befehlszeichenfolge interpretiert werden.

    – Ritter

    14. März 2012 um 11:23 Uhr

  • Nun, dieses Problem ist weiterhin ziemlich beeindruckend. 🙂 Der Zeiger gilt vor dem clone Systemaufruf, der Wert ist auch davor und danach gleich, aber die Adresse ist ungültig geworden. Wenn es sich nicht um einen Kernel- oder Hardwarefehler handelt, besteht die einzige verbleibende Möglichkeit darin, dass der gemeinsam genutzte virtuelle Adressraum von einem anderen Thread / Prozess geändert wird, der Zugriff darauf hat. Dies erscheint äußerst unwahrscheinlich, da diese Änderung dies tun würde Auch den übergeordneten Prozess beeinflussen. Also … Kernel-/Hardwarefehler?

    – Torek

    14. März 2012 um 19:38 Uhr

  • Ich habe ein Update mit einem interessanten Punkt über Karten hinzugefügt, basierend auf einem Gespräch über “interessante Probleme”, das ich heute im Fitnessstudio geführt habe.

    – Torek

    15. März 2012 um 0:25 Uhr

1228910cookie-checkWarum schlägt system() mit Fehlercode 127 fehl?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy