大纲
- 运行时分析
- 依赖文件分析
- 汇编和符号分析
除了《Robot Operating System——深度解析自动隐式加载动态库的运行模式》中介绍的这种最终在底层依赖了RCLCPP_COMPONENTS_REGISTER_NODE来注册Node工厂类对象之外,还存在一种特殊的方式,即本文介绍的:通过隐式加载Node的实现逻辑,但是在调用上直接通过符号来对接,而不是通过class_loader::ClassLoader这套流程。
我们先看下样例(composition/src/manual_composition.cpp)代码
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "composition/client_component.hpp"
#include "composition/listener_component.hpp"
#include "composition/talker_component.hpp"
#include "composition/server_component.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char * argv[])
{
// Force flush of the stdout buffer.
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
// Initialize any global resources needed by the middleware and the client library.
// This will also parse command line arguments one day (as of Beta 1 they are not used).
// You must call this before using any other part of the ROS system.
// This should be called once per process.
rclcpp::init(argc, argv);
// Create an executor that will be responsible for execution of callbacks for a set of nodes.
// With this version, all callbacks will be called from within this thread (the main one).
rclcpp::executors::SingleThreadedExecutor exec;
rclcpp::NodeOptions options;
// Add some nodes to the executor which provide work for the executor during its "spin" function.
// An example of available work is executing a subscription callback, or a timer callback.
auto talker = std::make_shared<composition::Talker>(options);
exec.add_node(talker);
auto listener = std::make_shared<composition::Listener>(options);
exec.add_node(listener);
auto server = std::make_shared<composition::Server>(options);
exec.add_node(server);
auto client = std::make_shared<composition::Client>(options);
exec.add_node(client);
// spin will block until work comes in, execute work as it becomes available, and keep blocking.
// It will only be interrupted by Ctrl-C.
exec.spin();
rclcpp::shutdown();
return 0;
}
看似main所在的可执行文件将诸如composition::Talker这些Node的具体实现都编译进去了,实则它只是编译进去了符号,具体逻辑还是在动态库中。
我们看CMakeLists.txt的实现
add_executable(manual_composition
src/manual_composition.cpp)
target_link_libraries(manual_composition
talker_component
listener_component
server_component
client_component)
ament_target_dependencies(manual_composition
"rclcpp")
对于talker_component、listener_component、server_component和client_component,链接过程会按需链接(-Wl,–as-needed
)。它们也会隐式自动加载到进程中。
运行时分析
我们可以通过工具来分析。
先执行manual_composition。
./build/composition/manual_composition
然后查看该进程ID
ps -ef | grep manual_composition
然后查看运行时它加载了的动态库
lsof -p 728814 | grep talker_component
lsof -p 728814 | grep listener_component
lsof -p 728814 | grep server_component
lsof -p 728814 | grep client_component
依赖文件分析
ldd ./composition/build/composition/manual_composition
汇编和符号分析
我们反汇编manual_composition到manual_composition.txt
objdump -S ./composition/build/composition/manual_composition > manual_composition.txt
然后沿着main函数去寻找线索
000000000000e5a9 <main>:
e5a9: f3 0f 1e fa endbr64
e5ad: 55 push %rbp
e5ae: 48 89 e5 mov %rsp,%rbp
e5b1: 53 push %rbx
……
e733: e8 6c 14 00 00 call fba4 <_ZSt11make_sharedIN11composition6TalkerEJRN6rclcpp11NodeOptionsEEESt10shared_ptrINSt9enable_ifIXntsrSt8is_arrayIT_E5valueES8_E4typeEEDpOT0_>
……
然后一直追踪下去,会陆续找到:
- ZNSt10shared_ptrIN11composition6TalkerEEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0
- ZNSt12__shared_ptrIN11composition6TalkerELN9__gnu_cxx12_Lock_policyE2EEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0
- ZNSt14__shared_countILN9__gnu_cxx12_Lock_policyE2EEC1IN11composition6TalkerESaIvEJRN6rclcpp11NodeOptionsEEEERPT_St20_Sp_alloc_shared_tagIT0_EDpOT1
- ZNSt23_Sp_counted_ptr_inplaceIN11composition6TalkerESaIvELN9__gnu_cxx12_Lock_policyE2EEC1IJRN6rclcpp11NodeOptionsEEEES2_DpOT
- ZSt10_ConstructIN11composition6TalkerEJRN6rclcpp11NodeOptionsEEEvPT_DpOT0
- _ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE
000000000000e3b0 <_ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE@plt>:
e3b0: f3 0f 1e fa endbr64
e3b4: ff 25 96 9b 00 00 jmp *0x9b96(%rip) # 17f50 <_ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE@Base>
e3ba: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
我们在manual_composition中是找不到_ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE的实现的
nm ./composition/build/composition/manual_composition | grep _ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE
可以看到处于U状态,即未定义。
但是我们在动态链接库libtalker_component.so中可以找到它的实现
nm ./composition/build/composition/libtalker_component.so | grep _ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE
所以我们看到例子manual_composition 虽然也是使用动态链接库的隐式加载,但是它没有使用Node工厂类的注册逻辑,而是直接依赖于符号。