如何获取有关退出代码来源的更多信息?

如何获取有关退出代码来源的更多信息?

有时我需要维护调用 shell 脚本的程序,而 shell 脚本又调用其他程序和脚本。因此,当主 shell 脚本以退出代码 126 结束时,很难找出哪个调用的脚本和命令设置了该退出代码。

有没有办法查看哪个命令是退出代码的原因,以便更轻松地检查其权限?

答案1

如果在 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 +++

上面,进程 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

这并没有告诉我们到底哪个进程以 126 退出,但可以给你足够的线索。

我们使用BASH_TRACEFD=7 7>&2以便痕迹输出在原来的stderr,即使 stderr 在脚本内重定向也是如此。否则,如果脚本执行类似的操作,这些跟踪消息可能会影响脚本的行为(....) 2>&1 | ...。假设这些脚本本身没有显式使用或关闭 fd 7(这是不可能的,比它们重定向 stderr 的可能性要大得多)。

答案2

如果设置该选项set -x,那么您会在跟踪中看到在 shell 中执行的命令。

答案3

这有点像黑客,但您可以预加载一些 C 代码作为填充程序来捕获调用exit(126)并向进程组发出信号SIGSTOP,这将暂停进程(及其同一组中的父进程)。

例如,如果我们在 shim 中捕获退出代码 2,然后ls在不存在的文件上运行:

LD_PRELOAD=/home/meuh/shim_exit.so bash -c ' sh -c "ls -l xxx; echo"; echo '

它将以消息作为背景

[1]+  Stopped  ...

您可以看到处于等待状态的进程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

在此阶段,您可以附加到可调试的进程,或者只是将进程置于前台或 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);
}

相关内容