我写了一个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.c
、session.c
和sshd.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
而是最终结束waitpid
,segfault
然后对此进行报告。请注意,错误报告将根据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 行),因此它本身不太可能导致信号退出情况。 (您确实检查了退出代码,对吧?)