Can*_*ani 5 shell-script exit error-handling exit-status
有时我需要维护调用 shell 脚本的程序,这些脚本调用其他程序和脚本。因此,当主 shell 脚本以退出代码 126 结束时,很难找出哪个调用的脚本和命令设置了退出代码。
有没有办法查看哪个命令是退出代码的原因,以便更轻松地检查其权限?
如果在 Linux 上,您可以运行该命令strace -fe process以了解哪个进程执行了一个exit_group(126)命令以及它(或它的任何父级,如果它本身没有执行任何内容)在执行此操作之前最后执行的命令:
$ strace -fe process sh -c 'env sh -c /; exit'
execve("/bin/sh", ["sh", "-c", "env sh -c /; exit"], [/* 53 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f24713b1700) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24713b19d0) = 26325
strace: Process 26325 attached
[pid 26324] wait4(-1, <unfinished ...>
[pid 26325] execve("/usr/bin/env", ["env", "sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fbdb4e2c700) = 0
[pid 26325] execve("/bin/sh", ["sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fef90b3b700) = 0
[pid 26325] clone(strace: Process 26326 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fef90b3b9d0) = 26326
[pid 26325] wait4(-1, <unfinished ...>
[pid 26326] execve("/", ["/"], [/* 53 vars */]) = -1 EACCES (Permission denied)
sh: 1: /: Permission denied
[pid 26326] exit_group(126) = ?
[pid 26326] +++ exited with 126 +++
[pid 26325] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26326
[pid 26325] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26326, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
[pid 26325] exit_group(126) = ?
[pid 26325] +++ exited with 126 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26325
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26325, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
exit_group(126) = ?
+++ exited with 126 +++
Run Code Online (Sandbox Code Playgroud)
上面是进程 26326,它首先以 126 退出,那是因为它试图执行/. 最后执行的是进程 26325 的子进程sh -c /。
如果这些脚本是bash脚本,或者它们是sh脚本并且sh恰好bash在您的系统上,您可以执行以下操作:
$ env SHELLOPTS=xtrace \
BASH_XTRACEFD=7 7>&2 \
PS4='[$?][$BASHPID|${BASH_SOURCE:-$BASH_EXECUTION_STRING}|$LINENO]+ ' \
sh -c 'env sh -c /; exit'
[0][30625|env sh -c /; exit|0]+ env sh -c /
[0][30626|/|0]+ /
sh: /: Is a directory
[126][30625|env sh -c /; exit|0]+ exit
Run Code Online (Sandbox Code Playgroud)
这并不能准确地告诉我们哪个进程以 126 退出,但可以为您提供足够的线索。
我们使用BASH_TRACEFD=7 7>&2以便在原始stderr上输出跟踪,即使在脚本中重定向 stderr 时也是如此。否则,如果这些跟踪消息执行类似(....) 2>&1 | .... 假设这些脚本本身没有明确使用或关闭 fd 7(这不太可能,比它们重定向 stderr 的可能性要大得多)。
这有点像黑客,但您可以预加载一些 C 代码作为填充程序来捕获调用并向进程组exit(126)发出SIGSTOP信号,这将暂停进程(及其同一组中的父进程)。
例如,如果我们在 shim 中捕获退出代码 2,然后ls在不存在的文件上运行:
LD_PRELOAD=/home/meuh/shim_exit.so bash -c ' sh -c "ls -l xxx; echo"; echo '
Run Code Online (Sandbox Code Playgroud)
它将以消息作为背景
[1]+ Stopped ...
Run Code Online (Sandbox Code Playgroud)
您可以看到处于等待状态的进程T:
~ $ ps f
PID TTY STAT TIME COMMAND
30528 pts/3 T 0:00 \_ bash -c sh -c "ls -l xxx;echo";echo
30529 pts/3 T 0:00 | \_ sh -c ls -l xxx;echo
30530 pts/3 T 0:00 | \_ ls -l xxx
Run Code Online (Sandbox Code Playgroud)
在此阶段,您可以附加到可调试的进程,或者只是将进程置于前台或 SIGCONT 以便继续。
这里是shim_exit.c代码,编译见C注释。
/*
* capture calls to a routine and replace with your code
* http://unix.stackexchange.com/a/308694/119298
* gcc -Wall -O2 -fpic -shared -ldl -o shim_exit.so shim_exit.c
* LD_PRELOAD=/home/meuh/shim_exit.so ./test
*/
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>
/* doesnt work for syscall exit_group() */
void exit(int status){
static void (*real_exit)(int status) = NULL;
if (!real_exit) {
real_exit = dlsym(RTLD_NEXT, "exit");
char *error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
_exit(1);
}
}
if (status==126/* || status==2*/)kill(0,SIGSTOP);
real_exit(status);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1368 次 |
| 最近记录: |