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

先来看他杀。

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 Killkoudai     14948    8231  0 00:57 pts/1    00:00:00 java baicai.other.SelfKillkoudai     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-runtimestap-prepstap -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背地的凶手了