自動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
「gcc hello.c -o hello.o」を使用して、実行プログラム「hello.o」をコンパイルして生成します。明らかに、プログラムで「segfault」が発生しました。 (マイ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
そして、「Segmentation failure:11」というメッセージが端末に表示されますが、ファイルには何もありません。
私が使うときも同じです。
./hello.o <1.in &>log
たぶんエラーメッセージがstderrにないかもしれません。
それでは、この問題をどのように解決するのですか?ありがとうございます!
答え1
プログラムの標準エラーストリームを標準出力にリダイレクトしましたが、そのメッセージがプログラムによって発行されなかったため、「セグメントエラー」メッセージはリダイレクトされません。代わりに、プログラムを呼び出すシェルによって発行されます。
ここでやるべきことはあなたの状況によって異なります。実際目標は次のとおりです。
標準エラーを標準出力にリダイレクトする場合は、他のコマンドと同様にCプログラムを使用してこれを実行できます。このようなリダイレクト2>&1
と使用する他の方法は正しく機能します。あなたのプログラムは実際には標準エラーに何も書いていませんが、Cプログラムを書いている場合するこれにより、リダイレクトされていることがわかります。fputs
あるいは、関数を使って書くこともfprintf
できますstderr
。たとえば、次のようになります。
fprintf(stderr, "error: refusing to say hello world\n");
一方、SIGSEGV
リダイレクトコマンドの作成を使用してサブプロセスを終了した後、シェルによって作成された「セグメントエラー」メッセージを標準エラーにリダイレクトすることが目的の場合。他のコマンドを使用する必要はありません。ザイルズは説明する、コマンドの1つを含めるだけで十分です{
;}
。たとえば、次のように実行できます。
{ ./hello.o 1000; } 2>>outfile
これは、プログラム実行のすべての標準エラー出力(プログラムで生成された出力(この場合はなし)とシェルで生成された出力を含む)をファイルに追加しますoutfile
。
(しかし私はあなたが本当に疑わしいです。するプログラムが実際に生成できるエラー出力をリダイレクトしたいので、重複した閉じてマークするのではなく、この質問に答えることです。 )
Cプログラムが明らかに偽のアドレスに書き込むようにしてください。〜らしい意図的に分割エラーを発生させることは信頼できる方法ですが、実際にはそうではありません。これは、Cコンパイラが未定義の動作を引き起こさないと仮定する可能性があり、一部のコンパイラは、高度な最適化なしでコンパイルするときにもこの仮定を利用するためです。分割エラーの下でプログラムの動作をテストするには、プログラムのSIGSEGV
実行中にプログラムにシグナルを送信することをお勧めします。raise
この機能を使用して自分でこれを実行することも、kill
コマンドkillall
を使用して実行することもできます。
たとえば、上記の例をテストするために{ sleep 1000; } >&out
別の端末で実行しましたkillall -SEGV sleep
。 (このsleep
コマンドはバックグラウンドで使用される可能性があるため、本番システムでは正確なプロセスを実行したくない可能性があることを警告する必要があります。少なくとも他の重要なタスクを実行しているユーザーではなく、もちろんrootユーザーになりたくありません。
最後に、実行可能ファイルの名前をサフィックスとして指定したくありません.o
。これらのサフィックスは通常、実際に実行可能ファイルを生成するために互いにリンクする必要があるコンパイラ生成オブジェクトファイルに使用されるためです。 Unixファミリーオペレーティングシステムで実行可能なバイナリは通常、拡張子なしで名前が付けられています。
答え2
メモ:このコンテキストのファイル拡張子は通常、最終実行プログラムではなくオブジェクトファイルを表すためhello.o
に置き換えられました。hello
.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
フック挿入を使用して正しい文字列を印刷できます。
プログラムでエラーメッセージを抽出する方法が見つかりませんでしたバッシュソースコード「分割エラー」メッセージを検索します。というファイルで見つかりました。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
diff
ただし、標準エラー(stderr)からその文字列をキャプチャして、目的の場所にパイプすることができます。
(2>&1 trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in) | diff - 1.out
これにより、エラーメッセージが最初に予想された標準出力に書き込まれたときに得られる正確な出力が生成されます。
1c1,6
< Segmentation fault: 11
---
> 3
> 9
> 20
> 4
> 5
> 11