脚本错误处理:需要在 ssh 运行的脚本中报告段错误

脚本错误处理:需要在 ssh 运行的脚本中报告段错误

我写了一个cron作业,用于ssh在服务器上运行脚本。我刚刚尝试运行脚本,但现在我不高兴。

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

我可以确认崩溃发生在脚本解释器中/bin/sh(指向 的符号链接/bin/dash)。例如,当我script &在服务器上运行时,shell 告诉我后台作业的 PID 为 30860,这是coredumpctl.我需要解决崩溃问题,但这个问题只是关于如何检测此类崩溃。

cron当作业打印任何消息时,支持通过“发送邮件”来报告错误。但它不以非零退出状态发送邮件。所以我当前的 cron 作业不会向我发送有关此错误的邮件。 (如果确实如此,我真的希望有一个比“以代码 255 退出”更有用的故障排除指针)。

cron依赖于 Unix 惯例,即“没有消息就是好消息”。但这一惯例在这里被打破了。

我将此解释为 SSH 的限制。如果我想始终注意到远程命令中的分段错误,我可以遵循什么规则来解决此 SSH 限制?

(我也很感兴趣这种限制是否有“充分的理由”。我想我或多或少知道如何它可能会发生,在实施层面)。

答案1

% 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

该错误来自于执行某些waitpid调用的东西,通常是 shell:

% ./segfault
zsh: bus error  ./segfault

因为这里zsh已经退出了 a waitpid,然后徘徊在一些涉及 的代码路径中WIFSIGNALED。 (macOS 发出总线错误而不是段错误(任何其他名称的玫瑰),并且确切的字符串错误将根据 shell 的不同而有所不同。)

OpenSSH 可移植(截至提交 ed7bd5d93fe14c7bd90febd29b858ea985d14d45)确实会进行各种WIFSIGNALED(status)调用,特别是在misc.csession.csshd.c;中。其中一些return -1可以很容易地变成观察到的255退出状态,尽管我需要添加调试或跟踪sshd以准确了解这种情况是如何发生的(ssh -v -v -v没有帮助,默认sshd日志也没有帮助)。

作为一个克鲁格,你可以强迫某件事waitpid发生。这需要欺骗 shell 不执行简单的exec替换自身操作,如果可以的话,它将作为一种优化:

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

:; ...足够复杂,sh不会fork/exec而是最终结束waitpidsegfault然后对此进行报告。请注意,错误报告将根据sh.

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

现在,如果sh它本身导致了段错误(或任何意外信号),您将需要检查 SSH 的退出代码。另一种选择是调用一个小包装器来执行waitpid无 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);
}

...虽然这个包装纸可能会出现段错误(或任何信号),特别是如果服务器存在硬件问题、内存故障等。

% ssh localhost ./waiter ./segfault
waiter: child exited with signal 10

然而,包装器的代码比典型的要少得多sh(传家宝 bourne shell 的代码约为 50 行,而传家宝 bourne shell 的代码约为 10,000 行),因此它本身不太可能导致信号退出情况。 (您确实检查了退出代码,对吧?)

相关内容