web-dev-qa-db-ja.com

実行可能Cプログラムのエラー情報をstdoutにリダイレクトする方法は? (MAC OS X)

自動Cプログラムチェッカーを書きたいのですが。たとえば、おもちゃの「hello.c」プログラムがあります。

#include <stdio.h>

int main()
{
    int a, b;

    while (scanf("%d %d", (&a)-1000000000000000, &b) != EOF)
    {
        printf("%d\n", a+b);
    }

    return 0;
}

そして、これが私の入力ファイル「1.in」です。

1 2
4 5
10 10
2 2
3 2
7 4

そして出力ファイル "1.out":

3
9
20
4
5
11

「gcchello.c-o hello.o」を使用して、実行可能プログラム「hello.o」をコンパイルおよび生成します。明らかに、プログラムは「セグメンテーション違反」に遭遇します:(私のMAC OS Xで実行)

$ ./hello.o <1.in
Segmentation fault: 11

しかし、私はパイプと差分を使用して自動チェッカーを作りたいです:

./hello.o <1.in | diff - 1.out

そして出力は:

0a1,6
> 3
> 9
> 20
> 4
> 5
> 11

エラーメッセージは表示されません!しかし、私はそれらをターミナル(MAC OS X)に表示したいと思います。

Stderrを次のようにstdoutにリダイレクトしようとします。

./hello.o <1.in 2>&1 | diff - 1.out

しかし、効果はありません!

また、stderrを次のようなファイルにリダイレクトしようとします。

./hello.o <1.in 2>log

情報「セグメンテーション違反:11」がターミナルに表示されますが、ファイルには何も表示されません。

私が使用するときに同じ状況が発生します

./hello.o <1.in &>log

エラー情報がstderrにない可能性があります。

では、どうすればこの問題を解決できますか?ありがとうございました!

5
Lizhi Liu

注:hello.ohelloに置き換えました。このコンテキストの.oファイル拡張子は通常は、最終的な実行可能プログラムではなく、オブジェクトファイルを示します。

あなたの投稿によると、あなたはコマンドを実行したいです:

./hello <1.in 2>&1 | diff - 1.out

また、このコマンドの出力に./hello <1.inの実行によるエラーメッセージを表示する必要があります。ただし、エラーメッセージはhello.oプログラム自体からではなく、シェルから送信されます。希望する効果を1行で概算するために考えられる最も近いことは、コマンドをサブシェルで実行してから、この出力をdiffコマンドで使用することです。

2>&1 bash -c './hello <1.in' | diff - 1.out

これにより、次の出力が得られます。

1c1,6
< bash: line 1: 58469 Segmentation fault: 11  ./hello < 1.in
---
> 3
> 9
> 20
> 4
> 5
> 11

唯一の違いは、この場合、シェルによって追加のメタデータ出力(つまり、行番号とコマンド文字列)が得られることです。エラーメッセージを正確に複製する場合は、trapを使用して、正しい文字列を出力するフックを挿入できます。

プログラムでエラーメッセージを抽出する方法が見つからなかったので、 Bashソースコード に移動し、「Segmentationfault」メッセージを検索しました。 siglist.c というファイルで、他のシグナルやエラーの説明とともに見つけました。その情報を使用して、次のスクリプトを作成しました。

#!/bin/bash 

# trapdesc.sh
#
#   Take an error code from the `trap` command and
#   print out the corresponding error message.
#
#   Example usage:
#
#       (trap 'bash trapdesc.sh $?' EXIT; <COMMAND>)
#

# List of signal codes and corresponding error messages
#
# Taken from bash source (siglist.c):
#
#   https://github.com/tpruzina/bash/blob/master/siglist.c
#
declare -a SIGNALS=(
"SIGHUP":"Hangup"
"SIGINT":"Interrupt"
"SIGQUIT":"Quit"
"SIGILL":"Illegal instruction"
"SIGTRAP":"BPT trace/trap"
"SIGABRT":"ABORT instruction"
"SIGEMT":"EMT instruction"
"SIGFPE":"Floating point exception"
"SIGKILL":"Killed"
"SIGBUS":"Bus error"
"SIGSEGV":"Segmentation fault"
"SIGSYS":"Bad system call"
"SIGPIPE":"Broken pipe"
"SIGALRM":"Alarm clock"
"SIGTERM":"Terminated"
"SIGURG":"Urgent IO condition"
"SIGSTOP":"Stopped (signal)"
"SIGTSTP":"Stopped"
"SIGCONT":"Continue"
"SIGCLD":"Child death or stop"
"SIGTTIN":"Stopped (tty input)"
"SIGIO":"I/O ready"
"SIGXCPU":"CPU limit"
"SIGXFSZ":"File limit"
"SIGVTALRM":"Alarm (virtual)"
"SIGPROF":"Alarm (profile)"
"SIGWINCH":"Window changed"
"SIGLOST":"Record lock"
"SIGUSR1":"User signal 1"
"SIGUSR2":"User signal 2"
"SIGMSG":"HFT input data pending"
"SIGPWR":"power failure imminent"
"SIGDANGER":"system crash imminent"
"SIGMIGRATE":"migrate process to another CPU"
"SIGPRE":"programming error"
"SIGGRANT":"HFT monitor mode granted"
"SIGRETRACT":"HFT monitor mode retracted"
"SIGSOUND":"HFT sound sequence has completed"
"SIGINFO":"Information request"
)

# Make sure we get an integer 
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
    2>&1 echo "Not a signal identifier: $1"
    exit 1
fi

# Convert the signal from the `trap` function value to the signal ID
sid="$(($1 - 128))"

# Make sure the signal ID is in the valid range
if [[ "${sid}" -lt 0 || "${sid}" -gt 40 ]]; then
    2>&1 echo "Unrecognized signal: ${sid}"
    exit 1
fi

# Get the array-index for the signal
index="$((sid-1))"

# Get the signal description
description="$(echo ${SIGNALS[index]} | cut -d: -f2)"

# Print the error description
echo "${description}: ${sid}"

このスクリプトを使用して、次のコマンドを実行できます。

(trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in)

これにより、./hello <1.inを実行するのと同じ文字列が生成されます。

Segmentation fault: 11

しかし、今では、その文字列を標準エラー(stderr)からキャプチャして、必要に応じてdiffにパイプすることができます。

(2>&1 trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in) | diff - 1.out

これにより、エラーメッセージが当初期待していた標準出力に書き込まれた場合に得られるであろう正確な出力が生成されます。

1c1,6
< Segmentation fault: 11
---
> 3
> 9
> 20
> 4
> 5
> 11
4
igal

プログラムの標準エラーストリームを標準出力にリダイレクトしましたが、「セグメンテーション違反」メッセージはプログラムによって発行されないため、リダイレクトされません。代わりに、プログラムを呼び出したシェルによって発行されます。

ここで何をすべきかは、実際の目標が何であるかによって異なります。

本当に標準エラーを標準出力にリダイレクトしたいだけなら、Cプログラムでも他のコマンドと同じようにできます。 2>&1のようなリダイレクトや、使用した他のメソッドは問題なく機能します。あなたのプログラムは実際には標準エラーに何も書きませんが、そうするCプログラムを書くと、リダイレクトされていることがわかります。 fputsまたはfprintf関数を使用してstderrに書き込むことができます。例えば:

fprintf(stderr, "error: refusing to say hello world\n");

一方、サブプロセスがSIGSEGVで終了した後、シェルが書き込む「セグメンテーション違反」メッセージを標準エラーにリダイレクトすることが目標である場合は、プログラムへの呼び出しを含む複合コマンドを記述できます。 、その複合コマンドからリダイレクトします。他のコマンドは必要ありません。 Gillesの説明 のように、1つのコマンドを{;}で囲むだけで十分です。たとえば、次のコマンドを実行できます。

{ ./hello.o 1000; } 2>>outfile

これにより、プログラムの実行によるすべての標準エラー出力(プログラムが生成するもの(この場合はなし)とシェルが生成するものの両方)がファイルoutfileに追加されます。

(ただし、実際にはdoは、プログラムが実際に生成する可能性のあるエラー出力をリダイレクトしたいだけだと思います。そのため、私はむしろこれに答えています。重複として閉鎖のフラグを立てるよりも。)


セグメンテーション違反を意図的にトリガーする信頼できる方法のように、Cプログラムに明らかに偽のアドレスに書き込みように思わせても、実際にはそうではありません。これは、Cコンパイラは未定義の動作が発生しないと想定することが許可されており、一部のコンパイラは高レベルの最適化でコンパイルしていなくても、この想定を利用します。セグメンテーションフォールトでのプログラムの動作をテストするには、実行中にSIGSEGVシグナルを送信することをお勧めします。 raise関数を使用してこれを行うことも、killまたはkillallコマンドを使用してそれを行うこともできます。

たとえば、上記の例をテストするには、{ sleep 1000; } >&outを実行し、別のターミナルではkillall -SEGV sleepを実行しました。 (sleepコマンドはバックグラウンドで使用されている可能性があるので、少なくとも他の重要なことや確かにルートとしてではありません。)

最後に、実行可能ファイルに.oサフィックスを付けたくない場合があります。これらは通常、コンパイラによって生成されるオブジェクトファイルに使用され、実際に実行できるファイルを生成するためにリンクする必要があるためです。 Unixライクなオペレーティングシステムでは、実行可能バイナリは通常、拡張子なしで名前が付けられます。

10
Eliah Kagan