不落辰

知不可乎骤得,托遗响于悲风

0%

记一次stack-use-after-scope和ASAN

bug及解决

日志

  • 起因:参考《Linux多线程服务端编程》第8章写my_muduo的class EventLoopThread时,想通过timerfd定时器测试功能,结果死循环。

    • 注册回调:在main中给EventLoopThread注册回调函数,在回调函数中创建channel以及事件。注册的回调函数会在EventLoopThread startLoop前调用。
  • 两处罪魁祸首

  • 问题发生处

  • 因此会与预期不符。

  • 可以看到 注册到epoll上的是timerfd=5,实际上返回的events.data.ptr指向的channel却是fd=0。

  • 日志打到这里时怀疑是使用了一个已经释放的对象,当时以为会是移动造成,又或者是使用了已经free的堆造成的。

  • 但其实还是不能肯定是内存方面的问题。因为没core dump.很奇怪为啥没core dump?

ASAN

  • 参考资料配置AddressSanitizer
  • 很强。一下子确定了在何处发生了什么错误。如下,在channel->handleEvent()时,发生stack-use-after-scope。比吭哧吭哧打日志方便很多。
    • 并且还可以编译时warning是否有初始化顺序不恰当的代码,比如类的成员在构造函数中初始化与在类中的声明顺序不一致。
  • 如此,可以确定时channel指针引用的对象已经不存在,并且是stack-use-after-scope而非heap-use-after-free。问题范围缩小一大半,直接去找定义该channel的地方。发现是在注册的回调函数中定义。因此,该channel出了回调函数就会被析构。(之前误以为注册给EventLoopThread的回调函数在其内部调用会展开,故认为loop结束前channel不会被析构,槽点过多)。
  • 更改channel定义的位置,使其不会在loop结束前被析构即可。
  • 也不是什么难bug,就是之前对这个内存方面的错误有些阴影,又恰好了解了asan这么好用的工具,因此在这里记录下。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    [INFO] 2022-09-20-12-30-33 : /home/shc/Muduo/src/main.cpp (64) <main>   startloop return eventloop 0x7f329bafdc40 

    [INFO] 2022-09-20-12-30-38 : /home/shc/Muduo/src/EpollPoller.cpp (147) <poll> 1 events happneded

    [INFO] 2022-09-20-12-30-38 : /home/shc/Muduo/src/EpollPoller.cpp (180) <fillActiveChannels> fill 5

    [INFO] 2022-09-20-12-30-38 : /home/shc/Muduo/src/EventLoop.cpp (109) <loop> 1 IN HANDLING 0

    =================================================================
    # error :stack-use-after-scope
    ==14905==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7f329bafd1d8 at pc 0x55840a7a315c bp 0x7f329bafba70 sp 0x7f329bafba60
    READ of size 1 at 0x7f329bafd1d8 thread T1
    # 显示过程调用栈 自出现问题处回溯一个个函数
    #0 0x55840a7a315b in Channel::handleEvent(Timestamp const&) /home/shc/Muduo/src/Channel.cpp:44
    #1 0x55840a7b2707 in EventLoop::loop() /home/shc/Muduo/src/EventLoop.cpp:112
    #2 0x55840a7bab27 in EventLoopThread::threadFunc() /home/shc/Muduo/src/EventLoopThread.cpp:93
    #3 0x55840a7bc5f3 in void std::__invoke_impl<void, void (EventLoopThread::*&)(), EventLoopThread*&>(std::__invoke_memfun_deref, void (EventLoopThread::*&)(), EventLoopThread*&) /usr/include/c++/7/bits/invoke.h:73
    #4 0x55840a7bc450 in std::__invoke_result<void (EventLoopThread::*&)(), EventLoopThread*&>::type std::__invoke<void (EventLoopThread::*&)(), EventLoopThread*&>(void (EventLoopThread::*&)(), EventLoopThread*&) /usr/include/c++/7/bits/invoke.h:95
    #5 0x55840a7bc2e9 in void std::_Bind<void (EventLoopThread::*(EventLoopThread*))()>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) /usr/include/c++/7/functional:467
    #6 0x55840a7bbf99 in void std::_Bind<void (EventLoopThread::*(EventLoopThread*))()>::operator()<, void>() /usr/include/c++/7/functional:551
    #7 0x55840a7bb9a2 in std::_Function_handler<void (), std::_Bind<void (EventLoopThread::*(EventLoopThread*))()> >::_M_invoke(std::_Any_data const&) /usr/include/c++/7/bits/std_function.h:316
    #8 0x55840a7a483b in std::function<void ()>::operator()() const /usr/include/c++/7/bits/std_function.h:706
    #9 0x55840a7c27c2 in operator() /home/shc/Muduo/src/Thread.cpp:63
    #10 0x55840a7c2f78 in __invoke_impl<void, Thread::start()::<lambda()> > /usr/include/c++/7/bits/invoke.h:60
    #11 0x55840a7c2bc2 in __invoke<Thread::start()::<lambda()> > /usr/include/c++/7/bits/invoke.h:95
    #12 0x55840a7c32e5 in _M_invoke<0> /usr/include/c++/7/thread:234
    #13 0x55840a7c326b in operator() /usr/include/c++/7/thread:243
    #14 0x55840a7c31cf in _M_run /usr/include/c++/7/thread:186
    #15 0x7f329f47e4bf (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xd44bf)
    #16 0x7f329f7be6da in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x76da)
    #17 0x7f329eec261e in __clone (/lib/x86_64-linux-gnu/libc.so.6+0x12161e)
    # 在哪里发生错误
    Address 0x7f329bafd1d8 is located in stack of thread T1 at offset 5720 in frame
    #0 0x55840a7b189d in EventLoop::loop() /home/shc/Muduo/src/EventLoop.cpp:81

    This frame has 26 object(s):
    [32, 33) '<unknown>'
    [96, 97) '<unknown>'
    [160, 161) '<unknown>'
    [224, 225) '<unknown>'
    [288, 289) '<unknown>'
    [352, 353) '<unknown>'
    [416, 417) '<unknown>'
    [480, 481) '<unknown>'
    [544, 552) '__for_begin'
    [608, 616) '__for_end'
    [672, 704) '<unknown>'
    [736, 768) '<unknown>'
    [800, 832) '<unknown>'
    [864, 896) '<unknown>'
    [928, 960) '<unknown>'
    [992, 1024) '<unknown>'
    [1056, 1088) '<unknown>'
    [1120, 1152) '<unknown>'
    [1184, 1696) 'header'
    [1728, 2240) 'header'
    [2272, 2784) 'header'
    [2816, 3328) 'header'
    [3360, 4384) 'buf'
    [4416, 5440) 'buf'
    [5472, 6496) 'buf' <== Memory access at offset 5720 is inside this variable
    [6528, 7552) 'buf'
    HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
    (longjmp and C++ exceptions *are* supported)

    # Thred T1在何处由T0生成
    Thread T1 created by T0 here:
    #0 0x7f329fa0dd2f in __interceptor_pthread_create (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x37d2f)
    #1 0x7f329f47e765 in std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xd4765)
    #2 0x55840a7c2931 in Thread::start() /home/shc/Muduo/src/Thread.cpp:64
    #3 0x55840a7ba49a in EventLoopThread::startLoop() /home/shc/Muduo/src/EventLoopThread.cpp:53
    #4 0x55840a7c5b0a in main /home/shc/Muduo/src/main.cpp:62
    #5 0x7f329edc2c86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

    # 总结:handleEvent时 发生 stack-use-after-scope
    SUMMARY: AddressSanitizer: stack-use-after-scope /home/shc/Muduo/src/Channel.cpp:44 in Channel::handleEvent(Timestamp const&)
    Shadow bytes around the buggy address:
    0x0fe6d37579e0: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d37579f0: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a10: f8 f8 f8 f8 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8 f8 f8
    0x0fe6d3757a20: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    =>0x0fe6d3757a30: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8[f8]f8 f8 f8 f8
    0x0fe6d3757a40: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a50: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a60: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a70: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    0x0fe6d3757a80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    Shadow byte legend (one shadow byte represents 8 application bytes):
    Addressable: 00
    Partially addressable: 01 02 03 04 05 06 07
    Heap left redzone: fa
    Freed heap region: fd
    Stack left redzone: f1
    Stack mid redzone: f2
    Stack right redzone: f3
    Stack after return: f5
    Stack use after scope: f8
    Global redzone: f9
    Global init order: f6
    Poisoned by user: f7
    Container overflow: fc
    Array cookie: ac
    Intra object redzone: bb
    ASan internal: fe
    Left alloca redzone: ca
    Right alloca redzone: cb
    ==14905==ABORTING

ASan Address Sanitizer

概述

  • ASan 是 Address Sanitizer 简称,它是一种基于编译器用于快速检测原生代码中内存错误的工具。
  • 简而言之,ASan 就是一个用于快速检测内存错误的工具,目前已经集成在LLVM 3.1+和GCC 4.8+中
  • 可检测类型
  • 原理
    • 在编译时,ASan会替换malloc/free接口
    • 在程序申请内存时,ASan会额外分配一部分内存来标识该内存的状态
    • 在程序使用内存时,ASan会额外进行判断,确认该内存是否可以被访问,并在访问异常时输出错误信息

设置

  • CMakeLists.txt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 编译设置
    add_compile_options(-O0 -ggdb -std=c++14 -Wall -Wextra -mavx2
    -fsanitize=address
    -fno-omit-frame-pointer
    -fno-optimize-sibling-calls
    -fsanitize-address-use-after-scope
    -fsanitize-recover=address)
    # 链接设置
    target_link_libraries(muduo pthread -fsanitize=address)
  • 环境变量

    • llvm-symbolizer运行路径:export ASAN_SYMBOLIZER_PATH=”/usr/bin/llvm-symbolizer”
    • ASAN_OPTIONS为ASan运行的Flags:export ASAN_OPTIONS=”halt_on_error=0:log_path=xxx/asan.log:detect_stack_use_after_return=1”

注意

  • ASan版本程序在Linux环境下运行时会额外申请20TB的虚拟内存
    • 需要确保/proc/sys/vm/overcommit_memory的值不为2
    • 这也可以作为检验ASan是否工作的标志
  • ASan版本性能大幅受损,大约会下降2x左右
  • ASan工具不是万能的,他必须要跑到有问题的代码才可以暴漏出来