写在前面
前面已经从代码层面讲解了Tomcat的架构,这是内存马系列文章的第五篇,带来的是Tomcat
Executor类型的内存马实现。有了前面第四篇中的了解,才能更好的看懂内存马的构造。
前置
什么是Executor
Executor是一种可以在Tomcat组件之间进行共享的连接池。
我们可以从代码中观察到对应的描述:
The Executor implementations provided in this package implement
ExecutorService, which is a more extensive interface. The ThreadPoolExecutor
class provides an extensible thread pool implementation. The Executors class
provides convenient factory methods for these Executors.
Memory consistency effects: Actions in a thread prior to submitting a
Runnable object to an Executor happen-before its execution begins, perhaps
in another thread.
Executes the given command at some time in the future. The command may
execute in a new thread, in a pooled thread, or in the calling thread, at
the discretion of the Executor implementation.
Params: command – the runnable task
Throws: RejectedExecutionException – if this task cannot be accepted for
execution
NullPointerException – if command is null
对于他的作用,允许为一个Service的所有Connector配置一个共享线程池。
在运行多个Connector的状况下,这样处理非常有用,而且每个Connector必须设置一个maxThread值,但不希望Tomcat实例并发使用的线程最大数永远与所有连接器maxThread数量的总和一样高。
这是因为如果这样处理,则需要占用太多的硬件资源。相反,您可以使用Executor元素配置一个共享线程池,而且所有的Connector都能共享这个线程池。
分析流程
通过上篇文章的分析我们知道,
在启动Tomcat的时候首先会。
调用启动类,并传入参数start预示着Tomcat启动:
这里调用start方法进行相关配置的初始化操作,
一直走到了org.apache.catalina.startup.Catalina
类中load方法中调用了。this.getServer().init()
方法进行Server的初始化操作,
即调用了LifecycleBase#init
方法,进而调用了initInternal
方法,即来到了他的实现类StandardServer#initInternal
中来了。
上篇中也提到过,将会循环的调用所有service的init方法,进而调用了StandardService#initInternal
方法进行初始化,调用了Engine#init
方法,因为没有配置Executor,所以在初始化的时候不会调用他的init方法,之后再调用mapperListener.init()
进行Listener的初始化操作,在获取了所有的connector之后将会循环调用其init方法进行初始化。
在初始化结束之后将会调用start
方法
即调用了Bootstrap#start
方法,进而调用了Server.start方法
来到了StandardService#startInternal
方法,紧跟着调用了上面调用了Init方法的start方法,成功启动Tomcat。
正文
接下来我们来分析一下为什么选用Executor来构造内存马,和如构造内存的流程。
分析注入方式
在成功开启了Tomcat之后,我们可以在Executor
中的execute
方法中打下断点,
之后运行访问8080端口
在前面那一篇文章中我们知道Acceptor
是生产者,而Poller
是消费者,
在执行Endpoint.start()
会开启Acceptor线程
来处理请求。
在其run方法中存在
-
运行过程中,如果
Endpoint
暂停了,则Acceptor
进行自旋(间隔50毫秒); -
如果
Endpoint
终止运行了,则Acceptor
也会终止; -
如果请求达到了最大连接数,则wait直到连接数降下来;
-
接受下一次连接的socket。
这一步己经在运行Tomcat容器的时候已经进行了,
在我们访问Tomcat的页面之后将会创建一个线程,并调用target属性的run方法,这里的target就是Poller对象(消费者)。
即调用了NioEndpoint$Poller#run
方法,跟进
public void run() {
while(true) {
boolean hasEvents = false;
label58: {
try {
if (!this.close) {
hasEvents = this.events();
if (this.wakeupCounter.getAndSet(-1L) > 0L) {
this.keyCount = this.selector.selectNow();
} else {
this.keyCount = this.selector.select(NioEndpoint.this.selectorTimeout);
}
this.wakeupCounter.set(0L);
}
if (!this.close) {