1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • entr で C プログラムを繰り返しコンパイル・実行すると hello は異常終了したが、コンパイル出力の後に Segmentation fault メッセージが表示されない
  • このメッセージを出力するのはクラッシュしたプログラム自身ではなく、子プロセスを回収し SIGSEGV による終了を確認した 親シェル
  • bash -c を明示しても、残っているコマンドが実質 1 つだけなら Bash は新しいプロセスを作らず execve で自分自身を置き換える ことがあり、メッセージを出す親シェルが消えてしまう
  • 別スクリプトでは shebang で起動した Bash が ./hello を子として実行して待機するため、SIGSEGV を確認して Segmentation fault (core dumped) を出力する
  • ./helloサブシェル (./hello) で実行するか、後ろに ; true を追加して後続処理を残すと exec 最適化を防ぎ、エラーメッセージを確認できる

メッセージが消えた実行経路

  • 次の entr コマンドで hello.c が変わるたびにコンパイルと実行を繰り返す

    ls hello.c | entr -s "gcc -o hello hello.c && ./hello"
    
  • hello はセグメンテーションフォルトで終了したのに、Segmentation fault メッセージも目に見える非 0 の終了ステータスもなく、何も出力が残らない

  • bash -c でコマンドを包んでも結果は同じだった

    ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'"
    
  • 一方、同じコマンドを run.sh に移して実行するとエラーが表示される

    #!/bin/bash
    gcc -o hello hello.c && ./hello
    
    ls hello.c | entr -s ./run.sh
    ./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello
    
  • Segmentation fault を出力する主体

    • クラッシュしたプログラムはすでに終了しており、親シェルが子プロセスを回収 するときに SIGSEGV で死んだ事実を確認してからメッセージを出力する
    • したがって、メッセージを出力する親シェルが残っていなければ何も表示されない
    • Bash は bash -c "some_command" で実行すべきことが実質そのコマンドだけなら、新しいプロセスを fork せず execve で自分自身を置き換える` ことがある
    • この最適化はほとんどの実行では外から見えない
    • スクリプト方式では entr./run.sh を子として実行し、shebang が新しい Bash を起動する
    • 新しい Bash は gcc 実行後に ./hello を fork して待機するため、子の SIGSEGV による終了を確認してエラーメッセージを残せる

親シェルを残す 2 つの方法

  • クラッシュするコマンドを サブシェル で実行するとメッセージが再び表示される
    ls hello.c | entr -s "gcc -o hello hello.c && (./hello)"
    hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )
    
    • 括弧は ./hello が fork されたサブシェルで実行されるよう強制する
    • Bash が親の役割を維持するため、サブシェルの SIGSEGV による終了を回収してメッセージを出力できる
  • クラッシュするコマンドの後ろに実行する処理を残しても exec 最適化 を防げる
    ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'"
    bash: line 1: 109516 Segmentation fault (core dumped) ./hello
    
    • ; true があるため Bash は ./hello が終わった後にもやることが残っており、自分自身をそのプログラムに置き換えない
    • 別のラッパースクリプトがなくてもセグメンテーションフォルトのメッセージを確認できる

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rsの意見
  • 最近のLinuxディストリビューションはデフォルトでコアダンプを無効化していることが多く、困ることが多い
    ulimit -c unlimitedで再び有効化できるが、コアファイルは実行ファイルと同じディレクトリには作られないようで、systemdがどこかへ移しているようだ
    OpenBSDのようにコアダンプをデフォルトで有効にし、実行ファイルと同じディレクトリに置いてくれるとよいのだが、今ではクラッシュが起きるとたいてい実行ファイルをgdbの中で再実行しなければならない

    • systemdでは**coredumpctl**で最近のコアダンプを一覧表示して取得できる
    • 実行ファイルのある場所にコアダンプを置くというのは、かなり奇妙な選択に思える
      /usr/binのような場所でコアダンプを探そうとは思わないし、そのような場所は容量が小さいか読み取り専用のファイルシステムであることも多い
      デフォルトの保存先としては作業ディレクトリのほうがずっとよいのではないか
    • コアファイルがデフォルトで無効化されている理由は、セキュリティ専門家がユーザーはLinuxを安全に運用できず、放置されたコアファイルが悪用され得ると考えているからのようだ
      しかもユーザーがコアファイルを有効化しても何をしているのか分かっていないと見なされ、rootですら見つけにくい場所に隠しているように感じられる
  • CやDなどでプログラムを書いていて終了原因を知りたいなら、waitシステムコールの戻り値を確認すればよく、プログラムがシグナルで終了したかどうかが分かる
    OSがコアダンプを別の場所へ転送すれば、必要なときにすぐデバッグしたり、ディストリビューションにバグレポートを送ったりできるという利点もある
    Linuxカーネルはコアダンプを生成するとき、**/proc/sys/kernel/core_pattern**という特別なファイルを読み、保存場所を決める
    ファイルに書き出す代わりにプログラムへパイプで渡すこともできるので、GUIダイアログの表示、デバッガの即時起動、開発者へのレポートメール送信といった処理が可能だ
    systemdはカーネルのcore_patternを自分で管理する値に設定するが、systemdを使わなくても同じカーネル機能を使える
    Windows XPの開発当時、コアダンプをMicrosoftへ送信したことで、存在すら知らなかった多くのバグを修正できたという話もあり、Windowsに開発ツールをインストールすると自分のプログラムを開発する際にオンデマンドデバッガがかなり便利だ
    この機能は2003年にLinuxへ導入され、詳しくはman 5 coreで確認できる

  • コアダンプはデバッグには優れているが、無関係に見えるファイルの横に置かれると一般ユーザーを混乱させるし、診断レポートの仕組みとして使うにはひどいこともある
    提出されたWindowsのクラッシュレポートにどれほど多くのデータが入っているのかを初めて見たときは愕然とした
    プライバシーを保護するクラッシュレポートの仕組みを前提にするなら、提出されたクラッシュを診断できるようにスタックとレジスタ情報だけで原因を推測する方法を身につける必要がある

  • Bashがスクリプト内のコマンドをときどき直接**exec**するという事実はまったく知らず、驚くべき機能だ
    Bashプロセス自体の負荷が大きいわけでもなく、必要な場面では習慣的にexecを使っているので、やや設計を誤った機能のようにも感じられる
    ただ、全体の実行回数を合計すると予想以上に大きな差を生むのかもしれないとも思う