当前位置: 首页 > news >正文

bug诞生记——动态库加载错乱导致程序执行异常

大纲

  • 背景
  • 问题发生
  • 问题猜测和分析过程
    • 是不是编译了本工程中的其他代码
    • 是不是有缓存
    • 是不是编译了非本工程的文件
    • 是不是调用了其他可执行文件
      • 查看CMakefiles
      • 分析源码
      • 检查正在运行程序的动态库
  • 解决方案

这个案例发生在我研究ROS 2的测试Demo时发生的。

整体现象是:修改了源码,编译也成功了,但是执行流程和没修改前一致,新代码的逻辑没有体现。

最后定位到“动态库加载错乱”这个根本的问题,方案也就呼之欲出。但是整个排查过程经历了若干假设和推导,还是值得记录下。

背景

在《Robot Operating System——Ubuntu上以二进制形式安装环境》这篇文章中,我们安装了二进制的ROS 2,并且通过下面的指令进行了测试

source /opt/ros/jazzy/setup.bash
ros2 run demo_nodes_cpp talker

在这里插入图片描述
后来为了研究它的一些源码,我从github上将demo_nodes_cpp的源码(https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp)给下载到本地。执行编译后会生成build目录。在目录下会生成talker这类的可执行程序。然后我就用这些可执行程序进行编译结果测试。

问题发生

然后我看到demo_nodes_cpp/src/topics/talker_serialized_message.cpp源码时,有这么一段注释

        // We know the size of the data to be sent, and thus can pre-allocate the// necessary memory to hold all the data.// This is specifically interesting to do here, because this means// no dynamic memory allocation has to be done down the stack.// If we don't allocate enough memory, the serialized message will be// dynamically allocated before sending it to the wire.auto message_header_length = 8u;auto message_payload_length = static_cast<size_t>(string_msg->data.size());serialized_msg_.reserve(message_header_length + message_payload_length);

它表达的是:这段代码去掉,程序也可以正常运行。因为rclcpp::SerializedMessage的空间会根据内容而动态分配。

然后我就去掉了这段代码,并新增了一个printf。

        // We know the size of the data to be sent, and thus can pre-allocate the// necessary memory to hold all the data.// This is specifically interesting to do here, because this means// no dynamic memory allocation has to be done down the stack.// If we don't allocate enough memory, the serialized message will be// dynamically allocated before sending it to the wire.// auto message_header_length = 8u;// auto message_payload_length = static_cast<size_t>(string_msg->data.size());// serialized_msg_.reserve(message_header_length + message_payload_length);printf("serialized_msg_ allocate memory\n");

使用下面的指令编译后

colcon build --allow-overriding demo_nodes_cpp

在这里插入图片描述

再运行talker_serialized_message,发现“serialized_msg_ allocate memory”这句并没有输出。
在这里插入图片描述

问题猜测和分析过程

是不是编译了本工程中的其他代码

因为整个工程的编译模块我没细看,只能先盲猜一种最简单的原因,即:是不是编译了其他代码。

然后我搜索了上述输出中的关键字“serialized message”,发现源码文件中只有我修改的文件中才有。
在这里插入图片描述
这个猜测被排除!

是不是有缓存

我决定清掉build目录,重新执行编译。
中间也试过通过增加命令来在编译前清除缓存。

colcon build --cmake-clean-cache --cmake-clean-first --allow-overriding demo_nodes_cpp

很不幸,执行结果还是修改代码前的逻辑。
这个猜测排除!

是不是编译了非本工程的文件

这次测试比较暴力,直接将当前修改文件中printf的语法改错,看看编译是否报错。
在这里插入图片描述
报错了。

这个猜测排除!

将源文件还原成正确语法。

是不是调用了其他可执行文件

因为在《Robot Operating System——Ubuntu上以二进制形式安装环境》这篇文章中,我们使用安装的二进制文件,也运行成功了测试用例,所以怀疑通过源码编译的文件是不是在底层调用了之前通过二进制安装的另外一个环境的逻辑。

查看CMakefiles

在demo_nodes_cpp/build/demo_nodes_cpp/CMakeFiles目录下,有两个有关本例修改的目录。

  • talker_serialized_message_library.dir
  • talker_serialized_message.dir
    在这里插入图片描述
    通过名字可以看出来talker_serialized_message.dir对应于我们运行的可执行文件;talker_serialized_message_library.dir对应于某个库(是静态库还是动态库目前不明)。

我们将重点放在talker_serialized_message.dir上,因为我们运行的程序大概率就是通过它编译的。

在demo_nodes_cpp/build/demo_nodes_cpp/CMakeFiles/talker_serialized_message.dir/DependInfo.cmake文件中,我们看到一个比较陌生的文件node_main_talker_serialized_message.cpp

分析源码


# Consider dependencies only in project.
set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF)# The set of languages for which implicit dependencies are needed:
set(CMAKE_DEPENDS_LANGUAGES)# The set of dependency files which are needed:
set(CMAKE_DEPENDS_DEPENDENCY_FILES"/home/fangliang/demos/demo_nodes_cpp/build/demo_nodes_cpp/rclcpp_components/node_main_talker_serialized_message.cpp" "CMakeFiles/talker_serialized_message.dir/rclcpp_components/node_main_talker_serialized_message.cpp.o" "gcc" "CMakeFiles/talker_serialized_message.dir/rclcpp_components/node_main_talker_serialized_message.cpp.o.d")# Targets to which this target links which contain Fortran sources.
set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES)# Targets to which this target links which contain Fortran sources.
set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES)# Fortran module output directory.
set(CMAKE_Fortran_TARGET_MODULE_DIR "")

打开这个文件,我们发现它实际调用了libtalker_serialized_message_library.so来实现了整体功能。
在这里插入图片描述
这是一个非常重要的发现。它可以让我们将排查的方向指向动态库。

检查正在运行程序的动态库

我们先让程序运行起来
在这里插入图片描述
然后在另外一个终端中查找这个进程ID

ps -ef | grep talker_serialized_message

在这里插入图片描述
然后使用lsof来查看这个进程加载的是哪个目录下的动态库libtalker_serialized_message_library.so。

lsof -p 64759 | grep "libtalker_serialized_message_library.so"

在这里插入图片描述
可以发现它调用的是“/opt/ros/jazzy/lib/libtalker_serialized_message_library.so”,而不是我们编译的结果所在的目录(/home/fangliang/demos/demo_nodes_cpp/build/demo_nodes_cpp)。

这样就可以确定这个离奇的问题发生的原因了:

  • 可执行程序调用了动态库来完成逻辑。
  • 系统中有两份同名动态库。
  • 可执行程序使用了错误路径下得动态库。

解决方案

解决方案也很简单,我们通过export LD_LIBRARY_PATH来修改优先级。
首先我们看下当前环境下的加载优先级(执行了source /opt/ros/jazzy/setup.bash导致环境是面向二进制ROS 2的)

echo $LD_LIBRARY_PATH

/opt/ros/jazzy/opt/rviz_ogre_vendor/lib:/opt/ros/jazzy/lib/x86_64-linux-gnu:/opt/ros/jazzy/opt/gz_math_vendor/lib:/opt/ros/jazzy/opt/gz_utils_vendor/lib:/opt/ros/jazzy/opt/gz_cmake_vendor/lib:/opt/ros/jazzy/lib

可以看到二进制安装的ROS 2环境位于高优先级。

我们只要将我们的路径提前即可

export LD_LIBRARY_PATH=/home/fangliang/demos/demo_nodes_cpp/build/demo_nodes_cpp:$LD_LIBRARY_PATH

然后执行程序,我们就看到我们修改的代码生效了。
在这里插入图片描述

相关文章:

  • 典型二进制翻译系统---用户级翻译
  • NAND Flash 的 SDR、ONFI、DDR 接口
  • deepseek-vl 论文阅读笔记
  • ubuntu在命令行输出里查找内容,dmesg
  • OSPF概述
  • 【cuda】在老服务器上配置CUDA+cmake开发环境
  • 内网渗透—内网穿透工具NgrokFRPNPSSPP
  • Linux进程——环境变量之二
  • 【计算机网络】WireShark和简单http抓包实验
  • 软考:软件设计师 — 5.计算机网络
  • Git 从入门到精通:全面掌握版本控制(IntelliJ IDEA 中 Git 的使用指南)
  • 【LeetCode 随笔】C++入门级,详细解答加注释,持续更新中。。。
  • 【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】
  • 二、【Python】入门 - 【PyCharm】安装教程
  • C语言程序设计(二)
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • [case10]使用RSQL实现端到端的动态查询
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 〔开发系列〕一次关于小程序开发的深度总结
  • echarts花样作死的坑
  • laravel5.5 视图共享数据
  • magento 货币换算
  • Protobuf3语言指南
  • Python_OOP
  • react-native 安卓真机环境搭建
  • scala基础语法(二)
  • Spark学习笔记之相关记录
  • Yii源码解读-服务定位器(Service Locator)
  • 猴子数据域名防封接口降低小说被封的风险
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 入手阿里云新服务器的部署NODE
  • 设计模式 开闭原则
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 微信开放平台全网发布【失败】的几点排查方法
  • 一个JAVA程序员成长之路分享
  • 在electron中实现跨域请求,无需更改服务器端设置
  • AI算硅基生命吗,为什么?
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • 整理一些计算机基础知识!
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • ######## golang各章节终篇索引 ########
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (4)(4.6) Triducer
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (回溯) LeetCode 40. 组合总和II
  • (六)c52学习之旅-独立按键
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (十三)MipMap
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表