19

Linux システムでは、実行時に呼び出しでプログラムを呼び出そうとしていsystem()ます。システム コールは、ゼロ以外のリターン コードで終了します。

エラーコードを呼び出すWEXITSTATUSと、「127」が返されます。

system の man ページによると、このコードは を/bin/sh呼び出すことができなかったことを示しています。

/bin/sh実行できなかった場合の終了ステータスは、 を実行したコマンドの終了ステータスになりますexit(127)

確認しました:/bin/shへのリンクbashです。bashある。シェルから実行できます。

では、なぜ を呼び出せなかったのかを調べるにはどうすれ/bin/shばよいでしょうか? カーネルの歴史か何か?

編集:

非常に役立つヒント (以下を参照) の後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なると、アドレスが悪いと言われます。どうして ?

編集:

ここでは、失敗に関係する部分全体を示します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、またはハードウェアのいずれかのバグのようなにおいがします。

編集:

失敗したシステム コールのより詳細な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>は同一です。不正なアドレスの原因となる環境変数のリストではないようです。Chris Dodd が述べたように、execve の 3 番目の引数は raw ポインター 0x14595af0 であり、strace はこれを無効と見なします (そしてカーネルは同意します)。straceはそれを文字列として認識しません (したがって、文字列ではなく 16 進値を出力します)。

編集:

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!

の 3 番目の引数と同じポインター値straceです。strace(上記の出力を更新しました)。

ポインターの 32 ビットの外観について:後続の呼び出しcmdのポインターの値を確認しました。cmd構造上の違いは見られません。これは、呼び出しが成功cmdしたときの値の 1 つです。system

cmd = 0x145d4f20

したがって、system呼び出しの前にポインターは有効です。上記のstrace出力が示唆しているように、( を呼び出した後fork) 子プロセスは正しいポインター値を受け取ります。しかし、何らかの理由で、ポインター値は子プロセスで無効とマークされています。

現在、次のいずれかだと考えています。

  • libc/カーネルのバグ
  • ハードウェアの問題

編集:

その間、回避策を投稿させてください。そのようなものを実装することを余儀なくされるのはとてもばかげています...しかし、それは機能します。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();
    }

編集:

ある特定の実行でこの回避策が機能しないことがわかりました。1000回の試行で、すべてが新しく割り当てられたcmdコマンド文字列で行われました。1000 はすべて失敗しました。これだけではありません。別の Linux ホスト (同じ Linux/ソフトウェア構成) で試してみました。

これを考慮に入れると、ハードウェアの問題を除外できる可能性があります。(その場合、2 つの物理的に異なるホスト上にある必要があります)。カーネルのバグのまま??

編集:

torek、修正したsystem通話をインストールしてみます。そのために少し時間をください。

4

2 に答える 2

5

これは奇妙なものです。 straceは、execve への引数が文字列 (へのポインタ) であることを理解しているため、ポインタが無効でない限り、指定された文字列を出力します。無効な場合は、ポインタの生の 16 進値を出力します。だから、ストレースライン

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

完全に理にかなっています-execveへの3番目の引数は生のポインター0xdda1d98であり、straceはこれを無効と見なします(そしてカーネルは同意します)。問題は、無効なポインターがどのようにここに到達するかです。これは、new から戻ったばかりの cmd である必要があります。

ラインを入れることをお勧めします

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

システム コールの直前に、C コードがポインタを何と見なしているかを把握します。

strace の残りの部分を見ると、(出力されたポインターから) 64 ビット システムで実行しているように見え、無効な​​ 0xdda1d98 は 32 ビット ポインターのように見えるため、ある種の32/64 ビットの失敗 (誰かが 64 ビット レジスタの 32 ビットのみを保存および復元するなど)。

于 2012-03-13T18:38:22.480 に答える
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を実行し、clone呼び出し中に上位ビットが何らかの理由で失われる可能性のあるレジスタからargv[2]が設定されているかどうかを確認する価値があります。 (私がfork上に持っているところでglibcは、効率のために使用cloneしています)。


更新:strace出力ごとに、clone呼び出しは使用してCLONE_VMおらCLONE_VFORKず(理由はわかりませんが、呼び出しがはるかに効率的になるはずです)、子は「通常の」子(la old-Unixスタイルfork)です。同僚は、おそらく失敗したアドレスが、子プロセスにコピーされないように設定されたマップにあることを示唆しました。失敗後の内容は/proc/self/mapsおもしろいでしょう。失敗したアドレスがどのようにマッピングされているかを見ることができます。それらの地図を子供の地図と比較することはさらに興味深いでしょう。ただし、子に含まれるものを取得するには、のglibcバージョンをオーバーライドし、失敗した後、を実行する前systemに読み取るものを追加する必要があります。/proc/self/mapsexecve_exit

于 2012-03-13T19:08:15.767 に答える