5

I want to write a automatic C program checker. For example, I have a toy "hello.c" program:

#include <stdio.h>

int main()
{
    int a, b;

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

    return 0;
}

And here is my input file "1.in":

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

and output file "1.out":

3
9
20
4
5
11

I use "gcc hello.c -o hello.o" to compile and generate an executable program "hello.o". Obviously, the program will meet "segment fault": (Run in my MAC OS X)

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

But I want to make an auto checker using pipe and diff:

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

And the output is:

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

No error message display! But I want to display them in the terminal (MAC OS X).

I try to redirect stderr to stdout like:

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

But no effect!

I also try to redirect stderr to a file like:

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

and the info "Segmentation fault: 11" display in the terminal while nothing in the file.

The same situation happens when I use

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

Maybe the error info isn't in stderr.

So, how can I solve this problem? Thank you!

4

NOTE: I've replaced hello.o with hello, since the .o file extension in this context would typically denote an object file and not the final executable program.

According to your post, you want to run the command:

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

And you want the error message from running ./hello <1.in to appear in the output of this command. However the error message isn't coming from the hello.o program itself, but from the shell. The closest thing I can think of for approximating the desired effect with a single line is to run the command in a subshell and then use this output with your diff command:

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

This gives us the following output:

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

The only difference is that in this case you get some additional metadata output by the shell (i.e. the line number and the command string). If you want to exactly replicate the error message, then you can use trap to insert a hook which prints out exactly the right string.

I couldn't find a way to programmatically extract the error message, so I went to the Bash source code and searched for the "Segmentation fault" message. I found it in a file called siglist.c, along with a bunch of other signals and error descriptions. Using that information I wrote the following script:

#!/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}"

Now, using this script, we can run the following command:

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

This produces the same string as running ./hello <1.in:

Segmentation fault: 11

But now you can capture that string from standard error (stderr) and pipe it to diff like you wanted:

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

This produces the exact output that you would have gotten if the error message had been written to standard output you had originally expected:

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

You've redirected the program's standard error stream to standard output, but this doesn't redirect the "Segmentation fault" message, because that message is not emitted by your program. Instead, it is emitted by the shell that called the program.

What you should do here depends on what your actual goal is.

If you really just want to redirect standard error to standard output, you can do that with your C program the same as with any command. Redirections like 2>&1, as well as the other methods you used, work just fine. Your program does not actually write anything to standard error, but if you write a C program that does do so, then you'll see that it is redirected. You can use the fputs or fprintf function to write to stderr. For example:

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

On the other hand, if your goal is to redirect the "Segmentation fault" messages your shell writes to standard error after a subprocess is terminated with SIGSEGV, then you can write a compound command that contains the call to your program, and redirect from that compound command. You don't have to have any other command; as Gilles explains, it is sufficient to enclose your one command in { ;}. For example, you can run:

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

That appends all standard error output from running your program--both that which the program produces (which in this case is none) and that which the shell produces--to the file outfile.

(I suspect, however, that you really do want to just redirect error output that your programs may actually produce, which is why I'm answering this rather than flagging it for closure as a duplicate.)


Although having your C program write to an obviously bogus address seems like a reliable way to trigger a segmentation fault deliberately, it really isn't. This is because C compilers are allowed to assume that undefined behavior doesn't occur, and some compilers will take advantage of this assumption even when not compiling with a high level of optimizations. To test the behavior of a program under a segmentation fault, I recommend you send it the SIGSEGV signal while it is running. It can use the raise function to do this to itself, or you can use the kill or killall command to do it.

For example, to test the example above, I just ran { sleep 1000; } >&out and, in another terminal, killall -SEGV sleep. (Since the sleep command might be in use in the background, I should caution you that you might not want to follow that precise procedure on a production machine, at least not as a user who is doing other important stuff and certainly not as root.)

Finally, you might not want to name your executables with .o suffixes, because these are typically used for object files produced by the compiler that must be linked together to produce a file that can actually be executed. On a Unix-like operating system, executable binaries are typically named with no extension.

  • Why use two processes?  kill(getpid(), SIGSEGV); works. – G-Man Nov 2 '17 at 5:36
  • @G-Man If you're asking why I personally used 2 processes (really, more) for testing: it was so I could avoid writing a program altogether. If you're asking why I didn't recommend passing the return value of the getpid function to the kill function: that's because I would consider that to be a more complicated and (on some systems) less robust version my first suggestion, that a program "can use the raise function to do this to itself." I believe raise(SIGSEGV); works on any Unix-like system. On Linux the raise manpage says it's equivalent to your way in single-threaded programs. – Eliah Kagan Nov 2 '17 at 6:08
  • @EliahKagan The command { ./hello.o <1.in; } >>outfile did not work for me, i.e. the segmentation fault was not written to the file. – igal Nov 2 '17 at 13:39
  • 1
    @igal Yeah, >>outfile doesn't do this, which is why I didn't recommend it. It's not enough to group the command in { }--you still need to redirect standard error. >>outfile redirects standard output; it means 1>>outfile. To redirect just standard error, use 2>>outfile, as given in this answer. After grouping the command inside { }, you can apply any redirections to the group. Those that redirect standard error will redirect the "Segmentation fault" message too. (Or did you mean { ./hello.o <1.in; } 2>>outfile didn't work? If so, please let me know; that would be interesting.) – Eliah Kagan Nov 2 '17 at 18:48
  • @EliahKagan Whoops! Sorry - total jumble on my parts. I was actually using a parenthetical grouping, e.g. (./hello <1.in) &>outfile. It looks like your command (with curly-braces) works but the parentheses don't. I don't see an an explanation in the manual for why this might be the case. Any insights? – igal Nov 2 '17 at 22:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.