我想写一个自动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”。显然,程序会遇到“段错误”:(在我的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 中。
那么,我该如何解决这个问题呢?谢谢你!
答案1
您已将程序的标准错误流重定向到标准输出,但这不会重定向“分段错误”消息,因为该消息不是由您的程序发出的。相反,它是由调用该程序的 shell 发出的。
你在这里应该做什么取决于你的实际的目标是。
如果您确实只想将标准错误重定向到标准输出,则可以使用 C 程序执行此操作,就像使用任何命令一样。像 之类的重定向2>&1
以及您使用的其他方法都可以正常工作。您的程序实际上不会向标准错误写入任何内容,但是如果您编写一个 C 程序做这样做,然后你会看到它被重定向了。您可以使用fputs
orfprintf
函数写入stderr
.例如:
fprintf(stderr, "error: refusing to say hello world\n");
另一方面,如果您的目标是在使用 终止子进程后将 shell 写入的“分段错误”消息重定向到标准错误SIGSEGV
,那么您可以编写一个包含对程序的调用的复合命令,并从该复合进行重定向命令。您不必有任何其他命令;作为吉尔斯解释说,将您的一个命令括在 中就足够了{
;}
。例如,您可以运行:
{ ./hello.o 1000; } 2>>outfile
这会将运行程序时的所有标准错误输出(包括程序产生的输出(在本例中没有)和 shell 产生的输出)附加到文件 中outfile
。
(不过,我怀疑你真的做只想重定向您的程序可能实际产生的错误输出,这就是为什么我回答这个问题而不是将其标记为重复的关闭。)
尽管让你的 C 程序写入一个明显伪造的地址似乎就像故意触发分段错误的可靠方法一样,它确实不是。这是因为 C 编译器可以假设未定义的行为不会发生,并且即使未使用高级优化进行编译,某些编译器也会利用此假设。要测试程序在分段错误下的行为,我建议您SIGSEGV
在程序运行时向其发送信号。它可以使用该raise
函数对其自身执行此操作,或者您可以使用kill
orkillall
命令来执行此操作。
例如,为了测试上面的示例,我刚刚{ 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
程序本身,而是来自 shell。我能想到的用一行来近似达到所需效果的最接近的方法是在子 shell 中运行该命令,然后将此输出与您的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
唯一的区别是,在这种情况下,您会获得 shell 输出的一些附加元数据(即行号和命令字符串)。如果您想准确地复制错误消息,那么您可以使用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
但现在您可以从标准错误 (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