ThreadSanitizer工具简单使用(基于ros程序)

  • Post author:
  • Post category:其他




前言

涉及多线程编程的程序一不小心就会因为对共享资源不当访问触发数据竞争。所谓的不当访问即是指:多个线程访问同时同一段内存并且至少存在一个线程进行写操作。

数据竞争通常是需要主动避免的,但在程序设计阶段就做到完全避免无疑是一种挑战。

谷歌开发的TreadSanitizer工具(下文写作TSAN)可以帮助我们检测程序

运行时

是否存在数据竞争。当然,这么强大的工具肯定不仅关注数据竞争,还有诸如死锁之类的问题也会被检测。



一、工具启用

clang 3.2 以及 gcc 4.8 版本开始,该工具就被内置,通过一些编译命令可以启用。

在CmakeList中添加以下代码:

//前略
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
if(NOT ENABLE_TSAN) 
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-sanitize=thread")
endif()
add_executable(cam_recorder_v1
    src/CamRecorderExample.cpp
    src/NodeBase.cpp
    src/ThreadManager.cpp
    src/utils.cpp
    src/CamBase.cpp
    src/CamRecorder.cpp
)
if(ENABLE_TSAN)
  target_compile_options(cam_recorder_v1 PRIVATE -fsanitize=thread)
  set_target_properties(cam_recorder_v1 PROPERTIES LINK_FLAGS -fsanitize=thread)
endif()
//后略

解释:

  • 我的可执行文件叫做cam_recorder_v1
  • 使用option的目的在于快捷控制是否启用TSAN
  • if(NOT ENABLE_TSAN)… 用于在编译模式下主动禁用TSAN。(实测这里不加可能无法关闭TSAN)

编译时命令如下:

catkin_make -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_TSAN=ON

解释:

  • 我使用的catkin_make。非ros程序则将catkin_make换成make
  • -DCMAKE_BUILD_TYPE=RelWithDebInfo用于在启用TSAN的同时保留调试信息,可不加

由于TSAN工具输出的信息可能会很长,而且可能会和程序输出混合,所以将其输出到log文件中,在终端内:

export TSAN_OPTIONS="log_path=tsan_output.log"

编译完成之后,直接运行程序即可。相关信息都会输出到上面的log文件内,该文件会存放在项目文件夹根目录里面。



二、LOG解读

LOG由多个被双横线隔开的段组成,每个段包含了一个被检测出来的问题或者警告,所有能够被检测出来的问题见表:

ThreadSanitizerDetectableBugs


我这里主要关心数据竞争,也就是data race。

下面取一个描述数据竞争的log段进行简单解读

==================
WARNING: ThreadSanitizer: data race (pid=63934)
  Read of size 8 at 0x7ffe36488960 by main thread:
    #0 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_bucket_index(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const <null> (cam_recorder_v1+0x694e6)
    #1 std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x679eb)
    #2 std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, ros::Time, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> > >::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x659bf)
    #3 NodeBase::checkTopics() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:82 (cam_recorder_v1+0x5ea8d)
    #4 CamRecorder::run() /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorder.cpp:122 (cam_recorder_v1+0x87d59)
    #5 main /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorderExample.cpp:26 (cam_recorder_v1+0x558a4)

  Previous write of size 8 at 0x7ffe36488960 by thread T5:
    #0 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_rehash_aux(unsigned long, std::integral_constant<bool, true>) <null> (cam_recorder_v1+0x6c61a)
    #1 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_rehash(unsigned long, unsigned long const&) <null> (cam_recorder_v1+0x6b168)
    #2 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, true>*, unsigned long) <null> (cam_recorder_v1+0x69844)
    #3 std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x67a62)
    #4 std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, ros::Time, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> > >::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x659bf)
    #5 NodeBase::checkTopicsAlive() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:106 (cam_recorder_v1+0x5ee96)
    #6 void std::__invoke_impl<void, void (NodeBase::*&)(), NodeBase*&>(std::__invoke_memfun_deref, void (NodeBase::*&)(), NodeBase*&) <null> (cam_recorder_v1+0x6d262)
    #7 std::__invoke_result<void (NodeBase::*&)(), NodeBase*&>::type std::__invoke<void (NodeBase::*&)(), NodeBase*&>(void (NodeBase::*&)(), NodeBase*&) <null> (cam_recorder_v1+0x6c7db)
    #8 void std::_Bind<void (NodeBase::*(NodeBase*))()>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) <null> (cam_recorder_v1+0x6b5bf)
    #9 void std::_Bind<void (NodeBase::*(NodeBase*))()>::operator()<, void>() <null> (cam_recorder_v1+0x69bf5)
    #10 std::_Function_handler<void (), std::_Bind<void (NodeBase::*(NodeBase*))()> >::_M_invoke(std::_Any_data const&) <null> (cam_recorder_v1+0x67cda)
    #11 std::function<void ()>::operator()() const /usr/include/c++/9/bits/std_function.h:688 (cam_recorder_v1+0x6fabe)
    #12 ManagedThread::threadLoop() /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:76 (cam_recorder_v1+0x6e703)
    #13 void std::__invoke_impl<void, void (ManagedThread::*)(), ManagedThread*>(std::__invoke_memfun_deref, void (ManagedThread::*&&)(), ManagedThread*&&) /usr/include/c++/9/bits/invoke.h:73 (cam_recorder_v1+0x74095)
    #14 std::__invoke_result<void (ManagedThread::*)(), ManagedThread*>::type std::__invoke<void (ManagedThread::*)(), ManagedThread*>(void (ManagedThread::*&&)(), ManagedThread*&&) /usr/include/c++/9/bits/invoke.h:95 (cam_recorder_v1+0x73f28)
    #15 void std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) /usr/include/c++/9/thread:244 (cam_recorder_v1+0x73e12)
    #16 std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> >::operator()() /usr/include/c++/9/thread:251 (cam_recorder_v1+0x73d3f)
    #17 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> > >::_M_run() /usr/include/c++/9/thread:195 (cam_recorder_v1+0x73c6a)
    #18 <null> <null> (libstdc++.so.6+0xd6de3)

  Location is stack of main thread.

  Location is global '<null>' at 0x000000000000 ([stack]+0x00000001e960)

  Thread T5 (tid=63951, running) created by main thread at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x5ea79)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8)
    #2 ManagedThread::ManagedThread(std::function<void ()>, int) /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:16 (cam_recorder_v1+0x6deb5)
    #3 void __gnu_cxx::new_allocator<ManagedThread>::construct<ManagedThread, std::function<void ()>, int&>(ManagedThread*, std::function<void ()>&&, int&) /usr/include/c++/9/ext/new_allocator.h:146 (cam_recorder_v1+0x7386f)
    #4 void std::allocator_traits<std::allocator<ManagedThread> >::construct<ManagedThread, std::function<void ()>, int&>(std::allocator<ManagedThread>&, ManagedThread*, std::function<void ()>&&, int&) /usr/include/c++/9/bits/alloc_traits.h:483 (cam_recorder_v1+0x7348e)
    #5 std::_Sp_counted_ptr_inplace<ManagedThread, std::allocator<ManagedThread>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<std::function<void ()>, int&>(std::allocator<ManagedThread>, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:548 (cam_recorder_v1+0x72f39)
    #6 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<ManagedThread, std::allocator<ManagedThread>, std::function<void ()>, int&>(ManagedThread*&, std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:679 (cam_recorder_v1+0x727ee)
    #7 std::__shared_ptr<ManagedThread, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<ManagedThread>, std::function<void ()>, int&>(std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:1344 (cam_recorder_v1+0x722f3)
    #8 std::shared_ptr<ManagedThread>::shared_ptr<std::allocator<ManagedThread>, std::function<void ()>, int&>(std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:359 (cam_recorder_v1+0x71b92)
    #9 std::shared_ptr<ManagedThread> std::allocate_shared<ManagedThread, std::allocator<ManagedThread>, std::function<void ()>, int&>(std::allocator<ManagedThread> const&, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:702 (cam_recorder_v1+0x70e89)
    #10 std::shared_ptr<ManagedThread> std::make_shared<ManagedThread, std::function<void ()>, int&>(std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:718 (cam_recorder_v1+0x702c5)
    #11 ThreadManager::addThread(std::function<void ()>, int) /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:98 (cam_recorder_v1+0x6eb1d)
    #12 NodeBase::buildCheckTopicsAliveThread() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:121 (cam_recorder_v1+0x5f534)
    #13 NodeBase::NodeBase(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:15 (cam_recorder_v1+0x5d9d6)
    #14 CamBase::CamBase(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x595e3)
    #15 CamRecorder::CamRecorder(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x597ff)
    #16 main /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorderExample.cpp:20 (cam_recorder_v1+0x55881)

SUMMARY: ThreadSanitizer: data race (/home/jzd/get_gazedate_ws/devel/lib/capture_master/cam_recorder_v1+0x694e6) in std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_bucket_index(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const
==================

个人认为一些关键的信息段:


1. WARNING: ThreadSanitizer: data race (pid=63934)


对问题的描述,指明问题类别为数据竞争


2. Read of size 8 at 0x7ffe36488960 by main thread:


read进程的函数调用栈(一般read进程是main进程)


3. Previous write of size 8 at 0x7ffe36488960 by thread T5:


write进程的函数调用栈


4. Thread T5 (tid=63951, running) created by main thread at:


write进程何时被main进程创立

在简单使用中通过函数调用栈找到对应的代码段就能发现问题了。



其他

TSAN会占用程序运行性能,所以如果不需要使用了记得关掉(这也是为什么我在CMAKELIST中要用OPTION,这样不加ON就会自动关掉)

更多参考:

ThreadSanitizerCppManual



版权声明:本文为weixin_42691563原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。