Did libstdc++'s layout for make_shared change between gcc 4.x and gcc 6.x?
Asked Answered
G

1

6

Consider the following minimal example, consisting of three files:

foo.h:

#pragma once
#include <memory>

struct X {
    uint64_t i = 0xdeadbeefdeadbeefULL;
};

void foo();

foo.cxx:

#include "foo.h"

void foo() {
    std::make_shared<X>();
}

main.cxx:

#include <memory>
#include "foo.h"

template std::shared_ptr<X> std::make_shared();

int main() {
    foo();
}

And then compile the two translation units with different versions of gcc:

$ g++-4.8.2 -g -std=c++11 -O0 -c foo.cxx -o foo.o
$ g++-6.2.0 -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -O0 -g main.cxx foo.o -fsanitize=address -fno-omit-frame-pointer

Note that I'm specifically compiling with the old ABI.

Running the resulting executable dies (it would not if both TUs were compiled with the same version of gcc):

==33535==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000eff8 at pc 0x000000401dcf bp 0x7fffffffd7f0 sp 0x7fffffffd7e8
WRITE of size 8 at 0x60300000eff8 thread T0
    #0 0x401dce in X::X() (.../a.out+0x401dce)
    #1 0x402758 in _ZN9__gnu_cxx13new_allocatorI1XE9constructIS1_IEEEvPT_DpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/ext/new_allocator.h:120
    #2 0x402721 in _ZNSt16allocator_traitsISaI1XEE12_S_constructIS0_IEEENSt9enable_ifIXsrNS2_18__construct_helperIT_IDpT0_EEE5valueEvE4typeERS1_PS6_DpOS7_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:254
    #3 0x4026fc in _ZNSt16allocator_traitsISaI1XEE9constructIS0_IEEEDTcl12_S_constructfp_fp0_spcl7forwardIT0_Efp1_EEERS1_PT_DpOS4_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:393
    #4 0x4026a6 in std::_Sp_counted_ptr_inplace<X, std::allocator<X>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<>(std::allocator<X>) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:399
    #5 0x4025d4 in _ZN9__gnu_cxx13new_allocatorISt23_Sp_counted_ptr_inplaceI1XSaIS2_ELNS_12_Lock_policyE2EEE9constructIS5_IKS3_EEEvPT_DpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/ext/new_allocator.h:120
    #6 0x402572 in _ZNSt16allocator_traitsISaISt23_Sp_counted_ptr_inplaceI1XSaIS1_ELN9__gnu_cxx12_Lock_policyE2EEEE12_S_constructIS5_IKS2_EEENSt9enable_ifIXsrNS7_18__construct_helperIT_IDpT0_EEE5valueEvE4typeERS6_PSC_DpOSD_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:254
    #7 0x40253a in _ZNSt16allocator_traitsISaISt23_Sp_counted_ptr_inplaceI1XSaIS1_ELN9__gnu_cxx12_Lock_policyE2EEEE9constructIS5_IKS2_EEEDTcl12_S_constructfp_fp0_spcl7forwardIT0_Efp1_EEERS6_PT_DpOSA_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:393
    #8 0x40249b in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<X, std::allocator<X>>(std::_Sp_make_shared_tag, X*, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:502
    #9 0x4023b1 in std::__shared_ptr<X, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<X>>(std::_Sp_make_shared_tag, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:957
    #10 0x402375 in std::shared_ptr<X>::shared_ptr<std::allocator<X>>(std::_Sp_make_shared_tag, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:316
    #11 0x4022c4 in std::shared_ptr<X> std::allocate_shared<X, std::allocator<X>>(std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:598
    #12 0x402241 in _ZSt11make_sharedI1XIEESt10shared_ptrIT_EDpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:614
    #13 0x4021cf in foo() /.../foo.cxx:4
    #14 0x400fd0 in main /.../main.cxx:7
    #15 0x7ffff6208b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
    #16 0x400ef8  (/.../a.out+0x400ef8)

This is true for gcc 7 and gcc 8 as well, but not true for gcc 5.4. This is specific to std::make_shared. What is going on here? I can't find any information about an ABI break, nor do I understand what kind of change could break this example.

Guib answered 22/5, 2018 at 20:53 Comment(9)
As a general principle, I would assume ABI breaks occur between any arbitrary major compiler version release, and try not to build any functionality that assumes otherwise.Affirm
I do not think gcc folks feel obliged to document ABI breaks between major version, the implicit assumption is always that there is a break.Infusion
Given how seriously they take ABI breaks and strive to avoid them, and argue against any library changes that could incur them, I would not make assumptions that breaks silently happen.Guib
Also gcc.gnu.org/onlinedocs/libstdc++/manual/abi.htmlFarland
@Guib I think you're misapplying Occam's Razor: assuming they won't let ABI breaks go undocumented is a more speculative assumption than assuming they would. They certainly take ABI breaks within versions (like 4.1.1 to 4.1.2) quite seriously, and go to great lengths to avoid them, but I cannot find any evidence they are so concerned when it comes to major version changes.Affirm
@Affirm Here and here. That is going great lengths to avoid an ABI break.Guib
It doesn't look like this is an issue if you use "-fabi-version=2" on gcc-6. GCC 5+ changed the default ABI version, but I'm not sure what changed between the versions to break this case.Corydon
Blaming reveals this candidate commit: github.com/gcc-mirror/gcc/commit/… (2013, included in 4.9.0)Johnsten
@IlyaPopov That's it. In particular, it removed a pointer from _Sp_counted_ptr_inplace::_Impl (this is STL's "we know where you live" optimization).Issue
H
4

C++11 support in GCC 4.8 was still experimental, so two objects compiled with -std=c++11 can only be linked together if they are both compiled by GCC 4.8.x

See https://mcmap.net/q/58114/-is-it-safe-to-link-c-17-c-14-and-c-11-objects for a more complete explanation.

Hypogenous answered 26/5, 2018 at 23:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.