Robot Operating System——深度解析通过符号和隐式加载动态库的运行模式

发布于:2024-08-09 ⋅ 阅读:(91) ⋅ 点赞:(0)

除了《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工厂类的注册逻辑,而是直接依赖于符号。


网站公告

今日签到

点亮在社区的每一天
去签到