上一篇文章提到服务器上的一个进程启动后不到三分钟就挂了。挂机的原因是什么?这个问题可以写成一篇文章。当进程死亡时,只有两种可能:自杀或他杀。杀人罪还包括第三方杀人和系统性死刑。我们先来看自杀。1.自杀我们以Java为例。Java程序运行完main方法就会退出,就是自杀。或者像下面这样的System.exit();这也是自杀。例如,下面的代码publicclassSelfKill{publicstaticvoidmain(String[]args)throwsInterruptedException{while(true){Thread.sleep(5000);系统.exit(4);}}}运行如下:[koudai@koudai-pcclasses]$javabaicai.other.SelfKill[koudai@koudai-pcclasses]$echo$?4我们可以在死前使用shutdownHook来记录。publicclassSelfKill{publicstaticvoidmain(String[]args)throwsInterruptedException{Runtime.getRuntime().addShutdownHook(newThread(()->{System.out.println("关闭应用程序,释放资源");}));while(true){Thread.sleep(2000);系统.exit(4);}}}执行结果[koudai@koudai-pcclasses]$javabaicai.other.SelfKill关闭应用,释放资源,可以看到,无论是主动自杀还是自杀,shutdownHook都能保持现场。所以如果是不支持shutdownHook的语言,或者程序中没有Hook,那我们就不知道了。另外,不仅可以触发suicideshutdownHook,还可以触发suicideshutdownHook。总结:对于自愿自杀,如果我们使用shutdownHook,我们可以记录自杀时间和场景。如果是自杀(恶意后门调用System.exit),而我们没有钩子,那么我们不知道自杀现场。代码中必须有一个完整的shutdownHook。主动自杀是我们的主动行为,那么如何避免被动自杀呢?刚才说了,被动自杀一般是恶意调用System.exit导致的,一种是开发者加的后门,一种是脚本小子加的后门。System.exit导致JVM直接退出,没有日志查询是在哪个类中的代码导致的,所以通常需要屏蔽。System.exit可以通过自定义SecurityManager来禁止:if(name!=null&&name.contains("setSecurityManager")){thrownewSecurityException("System.setSecurityManagerdenied!");}}}@OverridepublicvoidcheckPermission(Permissionperm,Objectcontext){//}@OverridepublicvoidcheckExit(intstatus){super.checkExit(status);thrownewExitException(status);//自定义异常}}System.setSecurityManager(newSelfSecurityManager());//主要方法需要注意,可以自定义SecurityManager,脚本小子也可以。因此,仅仅在Java类中自定义SecurityManager是不够的。您需要在JVM启动参数上定义一个更精细的策略文件,并保护您的SecurityManager不被重置。特别注意不要被反射绕过。2、前面说了不仅可以触发自杀shutdownHook,以下场景也会触发ShutdownHook:代码执行结束,JVM正常退出应用代码,应用代码中调用System#exit方法.应用出现OOM错误,导致JVM关闭。在终端中使用Ctrl+C(不在后台运行)主动关闭应用程序让我们尝试模拟Ctrl+C。如下图[koudai@koudai-pcclasses]$javabaicai.other.SelfKill^C关闭应用,释放资源[koudai@koudai-pcclasses]$可以看到ctrl+C可以捕获。那么kill命令可以捕获吗?[koudai@koudai-pcclasses]$kill14675[koudai@koudai-pcclasses]$javabaicai.other.SelfKill关闭应用程序并释放资源。可以看到普通的kill是可以捕获的。kill-9[koudai@koudai-pc类]$ps-ef|grepKillkoudai149488231000:57pts/100:00:00javabaicai.other.SelfKillkoudai1523514640000:58pts/200怎么样:00:00grep--colour=autoKill[koudai@koudai-pcclasses]$kill-914948[koudai@koudai-pcclasses]$javabaicai.other.SelfKill已经被kill了,可以看到kill-9不能被进程本身捕获。是这里吗?真正的问题来了,即使我有shutdownHook写下我的遗言,最重要的是我无法知道是谁杀了我。尤其是在分析一些木马的情况下,如果系统把我杀了,一般都是OOM,这还好。Linux内核有一种叫做OOMkiller(OutOfMemorykiller)的机制,它会监控那些占用内存过多的进程,尤其是那些占用内存非常快的进程,然后自动kill进程,防止内存耗尽。内核检测到系统内存不足,选择杀死某个进程。参考内核源码linux/mm/oom_kill.c。当系统内存不足时,会触发out_of_memory(),然后调用select_bad_process()选择一个“bad”进程杀死。这种OOM记录在日志里,可以用下面的方法查看grep"Outofmemory"/var/log/messagessudodmesg|grep"Outofmemory"系统杀了,我觉得我倒霉。最麻烦的就是被第三方查杀,比如木马,各种监控脚本,各种sh脚本。我怎么知道哪个进程杀死了它?这需要使用systemtap。3、systemtap的使用systemtap是一个开源工具,用于简化linux系统运行状态信息的收集。基于性能诊断和bug调试,提供对内核态和用户态程序的动态跟踪功能。用户可以自定义检测事件来跟踪程序的运行状态,如函数调用路径、CPU使用率、磁盘IO等。案件。使用systemtap,你可以在不修改代码甚至不重启程序的情况下分析程序的运行状态。systemtap的核心思想是定义一个事件(event),并赋予一个句柄(Handler)来处理该事件。当特定事件发生时,内核运行处理程序,就像快速调用子程序一样,处理完成后返回到内核的原始状态。我们先安装它yuminstallsystemtapsystemtap-runtimestap-prepstap-e'probebegin{printf("Hello,World");exit();}'#测试验证由于我们不需要高级功能,所以我们还没有安装内核符号文档。接下来我们写一个stap脚本vimsigmon.stp#内容如下probebegin{printf("%-8s%-16s%-5s%-16s%6s%-16s\n","SPID","SNAME","RPID","RNAME","SIGNUM","SIGNNAME")}probesignal.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-x28262sigmon.stpSIGKILLSPIDSNAMERPIDRNAMESIGNUMSIGNNAME2362819bash28262rsyslogd9SIGKILL这样我们就知道进程28262被进程2362819杀死了,也就是bash。当然,如果我们事前不监控的话,事后我们是拿不到日志信息的。如果我们既不做ShutdownHook,也不使用systemtap进行监控,而仅仅依靠操作系统内置的日志,那么我们将无法挽救死亡现场,也很难知道谁是幕后黑手。进程被杀死
