gcc版本切换引发的“血案”

51 阅读5分钟

一、代码准备

1 score.h

#ifndef SCORE_H
#define SCORE_H

#ifdef __cplusplus
extern "C" {
#endif

void AddStudent(const char *name, int score);
int GetScore(const char *name);
void PrintStudentsScores();

#ifdef __cplusplus
}
#endif

#endif

2 score.cpp

#include "score.h"
#include <iostream>
#include <string>
#include <unordered_map>

static std::unordered_map<std::string, int> g_scores;

void AddStudent(const char *name, int score)
{
    if (name) {
        g_scores[std::string(name)] = score;
    }
}

int GetScore(const char *name)
{
    if (!name) {
        return -1;
    }

    std::string key(name);
    auto it = g_scores.find(key);
    return (it != g_scores.end()) ? it->second : -1;
}

void PrintStudentsScores()
{
    std::cout << "Students scores:" << std::endl;
    for (const auto &pair : g_scores) {
        std::cout << pair.first << " -> " << pair.second << std::endl;
    }
}

3 main.cpp

#include <iostream>
#include <unordered_map>
#include "score.h"

int main()
{
    // 随便构造1个unordered_map
    std::unordered_map<std::string, int> wordCount;
    wordCount["apple"] = 10;
    wordCount["banana"] = 5;
    wordCount["orange"] = 8;

    // 调用libscore.so中的函数
    AddStudent("A", 95);
    AddStudent("B", 87);
    AddStudent("C", 92);

    std::cout << "B score: " << GetScore("B") << std::endl;
    std::cout << "D score: " << GetScore("D") << std::endl;
    PrintStudentsScores();
}

二、问题构造

1 在5.10内核(gcc 10.3)编译libscore.so

g++ -fPIC -c score.cpp -g -o score.o
g++ -shared score.o -o libscore.so

2 在6.6内核(gcc 12.3)编译main

g++ -o main main.cpp -L. -lscore -Wl,-rpath,.

3 执行main

运行进程会异常。

4 部署asan

g++ -fsanitize=address -o main main.cpp -L. -lscore -lasan -Wl,-rpath,.

5 执行main,在std::unordered_map发生异常

[root@3558d81212aa /]# ./main
B score: 87
D score: -1
Students scores:
``▒``C▒▒▒▒▒▒▒▒▒▒▒▒▒▒\▒▒▒▒4      4M▒▒▒=================================================================
==172==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6060000002f8 at pc 0x7f185d173572 bp 0x7ffc1162f7d0 sp 0x7ffc1162ef78
READ of size 1024 at 0x6060000002f8 thread T0
    #0 0x7f185d173571 in __interceptor_fwrite ../../../../libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1157
    #1 0x7f185d016d04 in std::basic_streambuf<char, std::char_traits<char> >::sputn(char const*, long) /usr/src/debug/gcc-12.3.1-98.oe2403sp2.x86_64/obj-x86_64-openEuler-linux/x86_64-openEuler-linux/libstdc++-v3/include/streambuf:456
    #2 0x7f185d016d04 in void std::__ostream_write<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) /usr/src/debug/gcc-12.3.1-98.oe2403sp2.x86_64/obj-x86_64-openEuler-linux/x86_64-openEuler-linux/libstdc++-v3/include/bits/ostream_insert.h:51
    #3 0x7f185d016d04 in 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) /usr/src/debug/gcc-12.3.1-98.oe2403sp2.x86_64/obj-x86_64-openEuler-linux/x86_64-openEuler-linux/libstdc++-v3/include/bits/ostream_insert.h:102
    #4 0x7f185d11dcfc in PrintStudentsScores /root/work/leetcode/cpp/score.cpp:30
    #5 0x4078cc in main (/main+0x4078cc)
    #6 0x7f185cc24a0f  (/lib64/libc.so.6+0x23a0f)
    #7 0x7f185cc24ac8 in __libc_start_main (/lib64/libc.so.6+0x23ac8)
    #8 0x4073f4 in _start (/main+0x4073f4)

0x6060000002f8 is located 0 bytes to the right of 56-byte region [0x6060000002c0,0x6060000002f8)
allocated by thread T0 here:
    #0 0x7f185d1e51f8 in operator new(unsigned long) ../../../../libsanitizer/asan/asan_new_delete.cpp:95
    #1 0x40a6a3 in std::__new_allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true> >::allocate(unsigned long, void const*) (/main+0x40a6a3)
    #2 0x409eb2 in std::allocator_traits<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true> > >::allocate(std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true> >&, unsigned long) (/main+0x409eb2)
    #3 0x4097ef in std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true>* std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true> > >::_M_allocate_node<std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>&&, std::tuple<>&&) (/main+0x4097ef)
    #4 0x408ef5 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, int>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int> >, 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> >::_Scoped_node::_Scoped_node<std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>, std::tuple<> >(std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int>, true> > >*, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>&&, std::tuple<>&&) (/main+0x408ef5)
    #5 0x408a29 in 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, int>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, int> >, 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> >&&) (/main+0x408a29)
    #6 0x408345 in std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, 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, int> > >::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&) (/main+0x408345)
    #7 0x7f185d11db25 in AddStudent /root/work/leetcode/cpp/score.cpp:11
    #8 0x407861 in main (/main+0x407861)
    #9 0x7f185cc24a0f  (/lib64/libc.so.6+0x23a0f)

SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1157 in __interceptor_fwrite
Shadow bytes around the buggy address:
  0x0c0c7fff8000: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
  0x0c0c7fff8010: 00 00 00 00 00 00 06 fa fa fa fa fa 00 00 00 00
  0x0c0c7fff8020: 00 00 00 fa fa fa fa fa 00 00 00 00 00 00 00 fa
  0x0c0c7fff8030: fa fa fa fa 00 00 00 00 00 00 00 fa fa fa fa fa
  0x0c0c7fff8040: 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 00 00
=>0x0c0c7fff8050: 00 00 00 fa fa fa fa fa 00 00 00 00 00 00 00[fa]
  0x0c0c7fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff8090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff80a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==172==ABORTING

三、问题分析

libscore.so是基于gcc 10编译的,main是基于gcc 12编译的,两者在std::unordered_map的内部实现上差异较大。main链接libscore.so运行后,std::unordered_map的逻辑混乱导致异常。

如何识别到是哪个gcc版本编译的,可以通过strings main | grep ^GLIBCXX 查看GLIBCXX的版本,进而分析出gcc版本。

image.png