Como redirecionar informações de erro do programa C executável para stdout? (MAC OS X)

5

Eu quero escrever um verificador automático de programas em C. Por exemplo, eu tenho um programa de brinquedo "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;
}

E aqui está o meu arquivo de entrada "1.in":

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

e arquivo de saída "1.out":

3
9
20
4
5
11

Eu uso "gcc hello.c -o hello.o" para compilar e gerar um programa executável "hello.o". Obviamente, o programa irá encontrar "falha de segmento": (Executar no meu MAC OS X)

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

Mas eu quero fazer um verificador automático usando pipe e diff:

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

E a saída é:

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

Nenhuma exibição de mensagem de erro! Mas eu quero exibi-los no terminal (MAC OS X).

Eu tento redirecionar o stderr para o stdout como:

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

Mas sem efeito!

Eu também tento redirecionar o stderr para um arquivo como:

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

e as informações "Falha de segmentação: 11" são exibidas no terminal enquanto nada está no arquivo.

A mesma situação acontece quando eu uso

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

Talvez a informação do erro não esteja em stderr.

Então, como posso resolver esse problema? Obrigada!

    
por Lizhi Liu 02.11.2017 / 05:49

2 respostas

4

OBSERVAÇÃO: substituí hello.o por hello , pois a extensão do arquivo .o nesse contexto normalmente indica um arquivo objeto e não o programa executável final.

De acordo com sua postagem, você quer executar o comando:

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

E você deseja que a mensagem de erro execute ./hello <1.in para aparecer na saída deste comando. No entanto, a mensagem de erro não vem do programa hello.o , mas do shell. A coisa mais próxima que posso imaginar para aproximar o efeito desejado com uma única linha é executar o comando em um subshell e usar essa saída com o comando diff :

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

Isso nos dá a seguinte saída:

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

A única diferença é que, nesse caso, você obtém alguns metadados adicionais gerados pelo shell (ou seja, o número da linha e a cadeia de caracteres do comando). Se você quiser replicar exatamente a mensagem de erro, use trap para inserir um gancho que imprima exatamente a seqüência correta.

Não consegui encontrar uma maneira de extrair a mensagem de erro por meio de programação. Fui ao código fonte Bash e pesquisei para a mensagem "Falha de segmentação". Eu encontrei em um arquivo chamado siglist.c , juntamente com um monte de outros sinais e erros descrições. Usando essa informação eu escrevi o seguinte 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}"

Agora, usando esse script, podemos executar o seguinte comando:

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

Isso produz a mesma string que está executando ./hello <1.in :

Segmentation fault: 11

Mas agora você pode capturar essa string do erro padrão (stderr) e canalizá-la para diff da maneira que você queria:

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

Isso produz a saída exata que você teria obtido se a mensagem de erro tivesse sido gravada na saída padrão que você esperava originalmente:

1c1,6
< Segmentation fault: 11
---
> 3
> 9
> 20
> 4
> 5
> 11
    
por 02.11.2017 / 07:01
10

Você redirecionou o fluxo de erro padrão do programa para a saída padrão, mas isso não redireciona a mensagem "Falha de segmentação", porque essa mensagem não é emitida pelo programa. Em vez disso, ele é emitido pelo shell que chamou o programa.

O que você deve fazer aqui depende do seu objetivo atual .

Se você realmente quer apenas redirecionar o erro padrão para a saída padrão, você pode fazer isso com o seu programa em C da mesma forma que com qualquer comando. Redirecionamentos como 2>&1 , bem como os outros métodos que você usou, funcionam bem. Seu programa, na verdade, não escreve nada no erro padrão, mas se você escrever um programa em C que faz , você verá que ele é redirecionado. Você pode usar a função fputs ou fprintf para gravar em stderr . Por exemplo:

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

Por outro lado, se seu objetivo for redirecionar as mensagens de "Falha de segmentação" que seu shell grava em erro padrão após um subprocesso ser finalizado com SIGSEGV , você poderá gravar um comando composto que contenha a chamada em seu programa e redirecionar a partir desse comando composto. Você não precisa ter nenhum outro comando; como Gilles explica , basta anexar seu único comando em { ;} . Por exemplo, você pode executar:

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

Isso anexa toda a saída de erro padrão da execução do programa - tanto o que o programa produz (que neste caso não é nenhum) quanto o que o shell produz - para o arquivo outfile .

(Eu suspeito, no entanto, que você realmente faça deseja apenas redirecionar a saída de erro que seus programas podem realmente produzir, e é por isso que estou respondendo a isso em vez de sinalizá-la para fechamento como duplicata .)

Embora ter seu programa em C escrito em um endereço obviamente falso pareça uma maneira confiável de acionar uma falha de segmentação deliberadamente, ela realmente não é. Isso ocorre porque os compiladores C podem supor que o comportamento indefinido não ocorre, e alguns compiladores aproveitam essa suposição mesmo quando não estão compilando com um alto nível de otimizações. Para testar o comportamento de um programa sob uma falha de segmentação, recomendo que você envie o sinal SIGSEGV enquanto estiver em execução. Ele pode usar a função raise para fazer isso sozinho ou você pode usar o comando kill ou killall para fazer isso.

Por exemplo, para testar o exemplo acima, executei apenas { sleep 1000; } >&out e, em outro terminal, killall -SEGV sleep . (Como o comando sleep pode estar em uso em segundo plano, devo avisá-lo que talvez você não queira seguir esse procedimento preciso em uma máquina de produção, pelo menos não como um usuário que está fazendo outras coisas importantes e certamente não como raiz.)

Por fim, talvez você não queira nomear seus executáveis com sufixos .o , porque eles são normalmente usados para arquivos de objeto produzidos pelo compilador que devem ser vinculados para produzir um arquivo que possa realmente ser executado. Em um sistema operacional Unix-like, binários executáveis são tipicamente nomeados sem extensão.

    
por 02.11.2017 / 06:23