종료 코드 소스에 대한 추가 정보를 얻으려면 어떻게 해야 합니까?

종료 코드 소스에 대한 추가 정보를 얻으려면 어떻게 해야 합니까?

때로는 다른 프로그램과 스크립트를 호출하는 쉘 스크립트를 호출하는 프로그램을 유지 관리해야 할 때도 있습니다. 따라서 기본 셸 스크립트가 종료 코드 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추적 시 셸에서 실행된 명령이 표시됩니다.

답변3

이것은 약간의 해킹이지만 일부 C 코드를 shim으로 미리 로드하여 호출을 포착 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);
}

관련 정보