Manipulação de erros de script: precisa relatar o segfault no script executado pelo ssh

2

Eu escrevi um trabalho cron , que usa ssh para executar um script em um servidor. Eu apenas tentei executar o script e agora estou infeliz.

client# ssh server.local /usr/local/bin/script
client#

server# /usr/local/bin/script
Segmentation fault (core dumped)
server#

client# ssh server.local /usr/local/bin/script
client# echo $?
255

Posso confirmar que a falha está no interpretador de scripts, /bin/sh (um link simbólico para /bin/dash ). Por exemplo, quando eu executei script & no servidor, o shell me diz que o trabalho em segundo plano tem o PID 30860, e esse é o próximo PID que aparece na lista de falhas em coredumpctl . Preciso resolver o problema, mas essa questão é apenas sobre como detectar essas falhas.

cron suporta relatório de erros por "envio de email" quando um trabalho imprime qualquer mensagem. Mas o não envia o status de saída diferente de zero . Portanto, meu trabalho cron atual não me enviaria um email sobre esse erro. (E se isso acontecesse, eu realmente gostaria de ter um ponteiro mais útil para solução de problemas do que "Exited with code 255").

cron está contando com a convenção Unix, que "nenhuma notícia é boa". Mas essa convenção está sendo quebrada aqui.

Eu interpreto isso como uma limitação do SSH. Se eu quisesse sempre observar falhas de segmentação em comandos remotos, que regra poderia seguir para contornar essa limitação de SSH?

(Eu também estou interessado se há uma "boa razão" para essa limitação. Eu acho que sei mais ou menos como isso poderia acontecer, no nível da implementação).

    
por sourcejedi 08.08.2018 / 13:19

1 resposta

0

% cat segfault.c
#include <stdio.h>
int main()
{
    char *s = "hello world";
    *s = 'H';
    printf("%s\n", s);
}
% CFLAGS=-g make segfault
gcc -g    segfault.c   -o segfault

O erro vem de algo executando uma chamada waitpid , geralmente o shell:

% ./segfault
zsh: bus error  ./segfault

como aqui zsh caiu de um waitpid e, em seguida, percorreu algum caminho de código envolvendo WIFSIGNALED . (o macOS emite um erro de barramento em vez de um segfault (uma rosa por qualquer outro nome) e o erro exato da string irá variar dependendo do shell.)

O OpenSSH portable (a partir do commit ed7bd5d93fe14c7bd90febd29b858ea985d14d45) faz várias chamadas WIFSIGNALED(status) notavelmente em misc.c , session.c e sshd.c ; alguns desses return -1 que poderiam facilmente se transformar no status de saída 255 , embora eu precise adicionar depuração ou rastreio sshd para ver exatamente como esse caso acontece ( ssh -v -v -v não ajuda, nem o padrão sshd logs).

Como kluge, você pode forçar um waitpid a acontecer. Isso requer enganar um shell para não executar um exec simples para substituir a si mesmo, o que ele fará como uma otimização, se puder:

% ssh localhost 'sh -c ./segfault'
% ssh localhost ':; ./segfault'
% ssh localhost 'sh -c ":; ./segfault"'
sh: line 1:  9068 Bus error: 10           ./segfault
% 

:; ... é complexo o suficiente para que sh não fork / exec e, em vez disso, termine waitpid on segfault e, em seguida, relate isso. Observe que o relatório de erros irá variar dependendo do sabor exato de sh .

% ssh localhost '/usr/local/bin/sh -c ":; ./segfault"'
Bus Error

Agora, se sh está causando o segfault (ou qualquer sinal inesperado), você precisará inspecionar o código de saída do SSH. Outra opção seria chamar um pequeno wrapper que faz o waitpid sem truques de shell:

#include <sys/wait.h>    
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    int status;
    pid_t pid;
    if (argc < 2) {
        fprintf(stderr, "Usage: waiter command [args ..]\n");
        exit(1);
    }
    pid = fork();
    if (pid < 0) {
        err(1, "could not fork");
    } else if (pid == 0) {      /* child */
        argv++;
        execvp(*argv, argv);
        err(1, "could not exec");
    } else {                    /* parent */
        if (waitpid(pid, &status, 0) < 0)
            err(1, "could not waitpid");
        if (WIFEXITED(status)) {
            exit(WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            warnx("child exited with signal %d", WTERMSIG(status));
            exit(128 + WTERMSIG(status));
        } else {
            err(1, "unknown waitpid condition?? status=%d", status);
        }
    }
    exit(1);
}

... embora este wrapper também possa segfault (ou qualquer sinal), especialmente se o servidor tiver problemas de hardware, memória ruim, etc.

% ssh localhost ./waiter ./segfault
waiter: child exited with signal 10
No entanto, o wrapper tem muito menos código do que um típicosh% (~ 50 linhas versus ~ 10.000 para o shell bourne da herança) por isso deve ser menos provável que ele próprio cause uma condição de saída por sinal. (Você verificou o código de saída, certo?)

    
por 08.08.2018 / 16:56