同样的业务代码,centos7 编译发布,运行正常,切换到 centos8 编译发布,程序发生崩溃。

问题仅发生在 Release 模式,Debug 模式编译发布,程序都是正常的。

第一次排查此类问题,组里断断续续,排查了三天,最终定位到问题。

结论

函数没有定义返回值,Release 模式下高版本gcc编译后有了更多的优化,导致了未知的执行逻辑,发生崩溃,GCC 的编译告警,记得处理,老项目不也建议全部屏蔽,屏蔽部分即可。

环境描述

centos7 gcc 版本

gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39)
Copyright © 2015 Free Software Foundation, Inc.
本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
包括没有适销性和某一专用目的下的适用性担保。

centos8 gcc 版本

gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

崩溃现场

此堆栈为问题定位以后,单独编写的测试代码,实际业务代码堆栈更加复杂。

[New LWP 1385902]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `./pstack_main'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007ffe894b4420 in ?? ()
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64 libgcc-8.5.0-21.el8.x86_64 libstdc++-8.5.0-21.el8.x86_64
(gdb) bt
#0  0x00007ffe894b4420 in ?? ()
#1  0x00000000004008e9 in main ()

看到这个堆栈,是不是没有什么头绪,崩溃的函数栈居然是个问号,下面是完整代码,你分析出来了问题吗?

#include <iostream>
#include <map>

int test()
{
    std::cout << "1" << std::endl;
}

int main() {
    test();
    return 0;
}

编译告警

组内项目的基础 Cmake 脚本,屏蔽了很多告警信息,问题恰恰就是下面这个告警: 函数没有定义返回值,Release 模式下高版本gcc编译后有了更多的优化,导致了未知的执行逻辑,发生崩溃

[build] /root/pstack/main.cpp: In function ‘int test()’:
[build] /root/pstack/main.cpp:7:1: warning: no return statement in function returning non-void [-Wreturn-type]
[build]  }
[build]  ^
[build] [100%] Linking CXX executable pstack_main
[build] [100%] Built target pstack_main
[driver] Build completed: 00:00:00.554
[build] Build finished with exit code 0

汇编代码

仅供参考,没有具体的分析,可以很明显看到,高版本的 GCC 做了更多的优化,大幅减少了汇编的代码量。

低版本 GCC-4.8.5 汇编代码:

.LC0:
        .string "1"
test():
        pushq   %rbx
        movl    $1, %edx
        movl    $.LC0, %esi
        movl    $std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movq    std::cout(%rip), %rax
        movq    -24(%rax), %rax
        movq    std::cout+240(%rax), %rbx
        testq   %rbx, %rbx
        je      .L7
        cmpb    $0, 56(%rbx)
        je      .L3
        movzbl  67(%rbx), %eax
.L4:
        movsbl  %al, %esi
        movl    $std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >::put(char)
        movq    %rax, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >::flush()
        popq    %rbx
        ret
.L3:
        movq    %rbx, %rdi
        call    std::ctype<char>::_M_widen_init() const
        movq    (%rbx), %rax
        movl    $10, %esi
        movq    %rbx, %rdi
        call    *48(%rax)
        jmp     .L4
.L7:
        call    std::__throw_bad_cast()
main:
        subq    $8, %rsp
        call    test()
        xorl    %eax, %eax
        addq    $8, %rsp
        ret
_GLOBAL__sub_I_test():
        subq    $8, %rsp
        movl    $_ZStL8__ioinit, %edi
        call    std::ios_base::Init::Init() [complete object constructor]
        movl    $__dso_handle, %edx
        movl    $_ZStL8__ioinit, %esi
        movl    $_ZNSt8ios_base4InitD1Ev, %edi
        addq    $8, %rsp
        jmp     __cxa_atexit

高版本 GCC-8.5.0 汇编代码:

.LC0:
        .string "1"
test():
        movl    $_ZSt4cout, %edi
        subq    $8, %rsp
        movl    $1, %edx
        movl    $.LC0, %esi
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movl    $_ZSt4cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
main:
        subq    $8, %rsp
        call    test()
_GLOBAL__sub_I_test():
        subq    $8, %rsp
        movl    $_ZStL8__ioinit, %edi
        call    std::ios_base::Init::Init() [complete object constructor]
        movl    $__dso_handle, %edx
        movl    $_ZStL8__ioinit, %esi
        movl    $_ZNSt8ios_base4InitD1Ev, %edi
        addq    $8, %rsp
        jmp     __cxa_atexit