关于linux:谁杀死了这个进程

57次阅读

共计 3882 个字符,预计需要花费 10 分钟才能阅读完成。

之前文章提到服务器上一个过程启动后不到三分钟就挂掉,到底是什么起因挂掉了,这个问题能够写篇文章了。过程死了,无非就两种可能:他杀,自杀。自杀又包含第三方杀害和零碎判死刑。

先来看他杀。

1. 他杀

咱们以 Java 为例,Java 程序在 main 办法运行完就会退出,这种属于他杀。或者像上面这样

System.exit();

这样也属于他杀。

比方上面的代码

public class SelfKill {public static void main(String[] args) throws InterruptedException {while(true){Thread.sleep(5000);
            System.exit(4);
        }
    }
}

运行如下:

[koudai@koudai-pc classes]$ java baicai.other.SelfKill
[koudai@koudai-pc classes]$ echo $?4

咱们能用 shutdownHook 来在临死前记日志。

public class SelfKill {public static void main(String[] args) throws InterruptedException {Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("敞开利用,开释资源");
        }));
        while(true){Thread.sleep(2000);
            System.exit(4);
        }
    }
}

执行后果

[koudai@koudai-pc classes]$ java baicai.other.SelfKill
敞开利用,开释资源

能够看到,无论是被动他杀,还是被他杀,shutdownHook 都能放弃现场。那如果是不反对 shutdownHook 的语言呢,或者程序里没有做 Hook,那咱们是不晓得的。另外,不只是他杀 shutdownHook 能触发,它杀 shutdownHook 也会被触发。

<u> 总结下:对于被动他杀,如果咱们有应用了 shutdownHook,是能记录下他杀工夫和现场的。如果是被他杀(歹意后门调用 System.exit),并且咱们没有做 Hook,那对他杀现场是不知情的。代码里肯定要有欠缺的 shutdownHook。</u>

被动他杀是咱们的被动行为,那怎么防止被动他杀呢?方才说了,被动他杀个别是歹意调用 System.exit 导致,一种是开发者退出的后门,一种是脚本小子退出的后门。System.exit 导致 JVM 间接退出,且没有日志能够查问到是哪个类里的代码导致,因而通常状况下须要屏蔽。System.exit 是能够被禁止的,办法就是自定义 SecurityManager:

public class SelfSecurityManager extends SecurityManager{
    @Override
    public void checkPermission(Permission perm) {if (perm instanceof java.lang.RuntimePermission) {String name = perm.getName();
            if (name != null && name.contains("setSecurityManager")) {throw new SecurityException("System.setSecurityManager denied!");
            }
        }
    }

    @Override
    public void checkPermission(Permission perm, Object context) {//}

    @Override
    public void checkExit(int status) {super.checkExit(status);
        throw new ExitException(status);// 自定义异样
    }
}

System.setSecurityManager(new SelfSecurityManager());//main 办法中

须要留神的是,你能够自定义 SecurityManager,脚本小子也能够自定义。因而光在 Java 类中自定义 SecurityManager 是不够的,你须要在 JVM 启动参数上定义更精密的 policy 文件,以及爱护本人的 SecurityManager 不被重置。尤其须要留神不要被反射绕过。

2. 自杀

后面说了,不只是他杀 shutdownHook 能触发,以下场景都会触发 ShutdownHook :

  • 代码执行完结,JVM 失常退出
  • 利用代码中调用 System#exit 办法
  • 利用中产生 OOM 谬误,导致 JVM 敞开
  • 终端中应用 Ctrl+C(非后盾运行)
  • 被动敞开利用

咱们模仿 Ctrl+ C 试试。如下所示

[koudai@koudai-pc classes]$ java baicai.other.SelfKill^C 敞开利用,开释资源[koudai@koudai-pc classes]$

能够看到 ctrl+ C 是能被捕捉到的。那么 kill 指令能被捕捉吗

[koudai@koudai-pc classes]$ kill 14675[koudai@koudai-pc classes]$ java baicai.other.SelfKill 敞开利用,开释资源

能够看到一般的 kill 是能被捕捉的,那么 kill - 9 呢

[koudai@koudai-pc classes]$ ps -ef|grep Kill
koudai     14948    8231  0 00:57 pts/1    00:00:00 java baicai.other.SelfKill
koudai     15235   14640  0 00:58 pts/2    00:00:00 grep --colour=auto Kill
[koudai@koudai-pc classes]$ kill -9 14948
[koudai@koudai-pc classes]$ java baicai.other.SelfKill
已杀死

能够看到 kill - 9 是无奈被过程本身所捕捉的。

到这里就完结了吗?

真正的问题来了,就算我有 shutdownHook 能记下临终遗嘱,然而最要害的是我无奈晓得是谁杀死了我。尤其是剖析某些木马的场景下

如果是零碎杀死我的,那个别就是 OOM,这种状况也还好。

Linux 内核有个机制叫 OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是霎时占用内存很快的过程,而后避免内存耗尽而主动把该过程杀掉。内核检测到零碎内存不足、筛选并杀掉某个过程的过程能够参考内核源代码 linux/mm/oom_kill.c,当零碎内存不足的时候,out_of_memory()被触发,而后调用 select_bad_process()抉择一个”bad”过程杀掉。

这种 OOM,是有日志记录的,能够用上面的办法查看

grep "Out of memory" /var/log/messagessudo 
dmesg|grep "Out of memory"

零碎杀的,自认倒霉。

最麻烦的是被第三方杀的,比方木马,各种监控脚本,各种 sh 脚本,我咋样才晓得是哪个过程杀的呢?这就须要用到 systemtap 了。

3.systemtap 应用

systemtap 是一个 用于简化 linux 零碎运行状态信息收集的开源工具。它立足于性能诊断和 bug 调试,对内核及用户态程序提供了动静追踪性能,用户能够自定探测事件来跟踪程序的运行状况,如函数的调用门路、CPU 占用和磁盘 IO 等一系列能够探测的状况。有了 systemtap,能够在程序不批改代码,甚至不必重启就能剖析出程序的运行状况。

systemtap 的核心思想是定义一个事件(event),以及给出解决该事件的句柄(Handler)。当一个特定的事件产生时,内核运行该解决句柄,就像疾速调用一个子函数一样,解决完之后复原到内核原始状态。

先来装置它

yum install systemtap systemtap-runtime
stap-prep
stap -e 'probe begin{printf("Hello, World"); exit();}' #测试验证

因为咱们并不需要高级性能,所以暂不装置内核符号文件。接下来,咱们写一个 stap 脚本

vim sigmon.stp
# 内容如下
probe begin
{
  printf("%-8s %-16s %-5s %-16s %6s %-16s\n",
         "SPID", "SNAME", "RPID", "RNAME", "SIGNUM", "SIGNAME")
}

probe signal.send
{if (sig_name == @1 && sig_pid == target())
    printf("%-8d %-16s %-5d %-16s %-6d %-16s\n",
      pid(), execname(), sig_pid, pid_name, sig, sig_name)
}

当初咱们须要监控某个过程,就这么调用

(base) [root@VM-0-7-centos ~]# stap -x 28262  sigmon.stp SIGKILLSPID     SNAME            RPID  RNAME            SIGNUM SIGNAME         2362819  bash             28262 rsyslogd         9      SIGKILL

这样咱们就晓得 28262 这个过程是被 2362819 这个过程,也就是 bash 所杀死的。

当然,如果不在事先监控,预先咱们是拿不到日志信息的。

如果咱们即不做 ShutdownHook,也不应用 systemtap 进行监控,仅仅靠操作系统自带日志,那咱们是无奈保留死亡现场,也很难晓得谁是过程 killed 背地的凶手了

正文完
 0