Debug EOS:nodeos + mongo_db_plugin

上文书说到区块链的存储方式,并结合了EOSIO进行分析,其中也提到了使用CLion调试EOS的方法。本文将继续深入细致地展开对加载了mongo_db_plugin的nodeos的调试过程以及心得。

关键字:源码分析,Debug EOS,nodeos,mongo_db_plugin,CLion,C++,boost::asio::signal_set,queue

本文涉及的环境:clang-6.0, clang++-6.0, GDB Debugger, make 4.1, mongodb-linux-x86_64-3.6.3, boost 1.67.0

调试EOS: nodeos

关于EOS的调试环境的搭建这里不再赘述了,下文开始针对nodeos程序进行调试。

(一)CMakeList.txt

nodeos开始运行前,要先使用项目的总CmakeList.txt配置,这里我配置了boost库的位置,如果你配置了boost的环境变量可以跳过这里。

set( BOOST_ROOT "/home/evsward/opt/boost")

这个文件中有很多的set语句,这些语句都是开关,或者路径,或者全局变量,是配置各个子CMakeList.txt而用的。

include 语句是为runtime引入相关依赖库。

add_subdirectory语句设置了子目录程序。

install语句是将相关命令安装到指定位置以供runtime后续使用。

总的CMakeList文件介绍完了,下面会执行到nodeos目录下的CMakeList.txt文件:

add_executable( nodeos main.cpp )语句设定了nodeos程序执行入口。

set, configure_file, include, install 等都是为runtime准备的环境相关的。

重点语句target_link_libraries,这里定义了链runtime环境需要启动的plugin。(注意记住这个顺序)

(二)static register plugin

我们打开每一个plugin的cpp文件,会发现有一个static的register方法的调用。这里会首先执行按上面plugin定义的顺序中第一个login_plugin,它的static语句如下:

static appbase::abstract_plugin& _login_plugin = app().register_plugin<login_plugin>();

执行此语句时,会先执行app(),这是application单例方法。

(三)application

application是nodeos的执行者,上面调用的app函数:

application& application::instance() { static application _app; return _app; } application& app() { return application::instance(); }

application与plugin拥有相同的实现函数,而由于它作为执行者、统筹者的存在,它会安排调用所有plugin,例如set_program_options。

执行app()以后获取到了application的实例,然后调用了register_plugin函数,通过模板类(泛型类)携带了login_plugin的类型。register_plugin函数是模板函数,定义在application.hpp文件中。

application.hpp 中定义了私有的内存变量

map<string, std::unique_ptr<abstract_plugin>> plugins;

abstract_plugin是所有plugin的基类,它定义了虚函数,需要继承它的子类去实现。他们与application的关系是:

abstract_plugin=>plugin(对基类的虚函数进一步使用,由application定义管理)=>各个plugin

template<typename Plugin> auto& register_plugin() { auto existing = find_plugin<Plugin>(); // 从plugins寻找该plugin是否已注册。 if(existing) return *existing; // 如果已注册了直接返回plugin的实例 auto plug = new Plugin(); // 创建该未注册plugin的实例 plugins[plug->name()].reset(plug); // 先插入到上面定义的内存变量plugins plug->register_dependencies();// 注册该plugin的依赖plugins,每个plugin内部都会调用APPBASE_PLUGIN_REQUIRES((chain_plugin))来注册自己依赖的别的plugin。 return *plug; // 返回plugin的实例 } (四)main.cpp->main()

在编译runtime环境结束以后,进入入口函数main(),

int main(int argc, char** argv)

main函数的参数就是调用命令nodeos的通过--加入的参数,我们可以通过nodeos的Edit Configuration来调整。其中argc是个数,argv是参数的值,是一个数组类型。如下图:

Debug EOS:nodeos + mongo_db_plugin

我们接着来看main函数,它的函数体是通过app()对application单例进行的设置,包括版本号、data路径、config路径,然后是对于application实例内部方法的调用:

initialize<chain_plugin, http_plugin, net_plugin, producer_plugin>

startup()

exec()

main函数执行了内部函数initialize_logging()还通过ilog打印了日志,输出了版本号以及eosio root路径地址。

由于main函数是入口函数,上面也介绍了它主要是对application实例的使用,以及一些异常处理等,接下来会逐一进行debug跟踪分析。

(五)initialize plugin

这个初始化函数是一个模板函数,模板类参数是plugin基类,在main函数调用该函数时传入了基本的插件依赖(这些是不需要我们在config中配置的,是链启动的基础插件):chain_plugin, http_plugin, net_plugin, producer_plugin。下面来看initialize函数在application头文件中的声明:

/** * @brief 查看 --plugin(存在于命令行或者config配置文件中)调用这些plugin的 initialize方法。 * * @tparam Plugin plugin的列表用来初始化,即使在config中没有配置的但被其他plugin所依赖的plugin,都可以统一使用该模板类没有影响。 * @return true:plugin初始化完成,false:出现异常。 */ template<typename... Plugin> bool initialize(int argc, char** argv) { return initialize_impl(argc, argv, {find_plugin<Plugin>()...}); // ...是可变参数的语法,上面通过main函数的调用,我们传入了多个plugin。 }

实现类initialize_impl的内容较多,不粘贴源码,直接总结一下:

(1)set_program_options()函数

application.cpp文件中的set_program_options()函数是用来生成初始化的config.ini文件内容以及nodeos命令行--help的输出内容。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssjyw.html