Как правило нуля влияет на разделяемые библиотеки со скрытой видимостью?

ПРИМЕЧАНИЕ: вопрос находится внизу.

Я пытаюсь понять проблемы, которые могут возникнуть при использовании правила нуля с разделяемыми библиотеками и производными типами.

В демонстрации ниже компилируется с правилом нуля или без него, в зависимости от определения препроцессора. Затем сценарий демонстрирует возникающие различия, а именно то, что если DerivedType имеет ключевой метод, определенный в файле cpp, vtable генерируется только в разделяемой библиотеке, тогда как, если ключевого метода нет, vtable генерируется в каждом потребляющем TU.

baselib.h:

      #ifndef BASELIB_H
#define BASELIB_H

#ifdef BUILD_BASE_LIB
#define BASELIB_EXPORTS __attribute__((visibility("default")))
#else
#define BASELIB_EXPORTS
#endif

#include <memory>

class BASELIB_EXPORTS BaseType
{
public:
    BaseType() = default;
    virtual ~BaseType();
    BaseType(BaseType const&) = default;
    BaseType(BaseType &&) = default;
    BaseType& operator=(BaseType const&) = default;
    BaseType& operator=(BaseType &&) = default;
};

class BASELIB_EXPORTS DerivedType : public BaseType
{
public:
#ifdef DERIVED_RULE_OF_FIVE
    DerivedType() = default;
    ~DerivedType() override;
    DerivedType(DerivedType const&) = default;
    DerivedType(DerivedType &&) = default;
    DerivedType& operator=(DerivedType const&) = default;
    DerivedType& operator=(DerivedType &&) = default;
#endif

#ifdef DERIVED_TYPE_EXPLICIT_KEY
    virtual void key();
#endif
};

BASELIB_EXPORTS std::unique_ptr<BaseType> makeBaseType();

#endif

baselib.cpp

      #include "baselib.h"

#include <iostream>

BaseType::~BaseType() = default;

#ifdef DERIVED_RULE_OF_FIVE
DerivedType::~DerivedType() = default;
#endif

#ifdef DERIVED_TYPE_EXPLICIT_KEY
void DerivedType::key() {}
#endif

std::unique_ptr<BaseType> makeBaseType()
{
    std::cout << "BASE LIB  " << &typeid(DerivedType) << "\n";
    return std::make_unique<DerivedType>();
}

otherlib.h:

      
#ifndef OTHERLIB_H
#define OTHERLIB_H

#ifdef BUILD_OTHER_LIB
#define OTHERLIB_EXPORTS __attribute__((visibility("default")))
#else
#define OTHERLIB_EXPORTS
#endif

#include "baselib.h"

class OTHERLIB_EXPORTS OtherDerivedType : public DerivedType
{
public:
    OtherDerivedType() = default;
    virtual ~OtherDerivedType();
    OtherDerivedType(OtherDerivedType const&) = default;
    OtherDerivedType(OtherDerivedType &&) = default;
    OtherDerivedType& operator=(OtherDerivedType const&) = default;
    OtherDerivedType& operator=(OtherDerivedType &&) = default;

    std::unique_ptr<DerivedType> getDerivedType();
};

#endif

otherlib.cpp

      #include "otherlib.h"

#include "baselib.h"

#include <iostream>

OtherDerivedType::~OtherDerivedType() = default;

std::unique_ptr<DerivedType> OtherDerivedType::getDerivedType()
{
    std::cout << "OTHER LIB " << &typeid(DerivedType) << "\n";
    auto bt = makeBaseType().release();
    return std::unique_ptr<DerivedType>(dynamic_cast<DerivedType*>(bt));
}

main.cpp

      #include "otherlib.h"
#include "baselib.h"

#include <iostream>

int main(int argc, char** argv)
{
    std::cout << "MAIN      " << &typeid(DerivedType) << "\n";

    OtherDerivedType odt;
    auto dt = odt.getDerivedType();
    std::cout << "DT " << dt.get() << "\n";

    return 0;
}
      #!/bin/bash

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -o baselib1.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib1.so baselib1.o

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -o otherlib1.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib1.so otherlib1.o baselib1.so

$COMPILER_DRIVER -o main1.o -c main.cpp
$COMPILER_DRIVER -o def_rule_zero_without_key main1.o otherlib1.so baselib1.so

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -DDERIVED_TYPE_EXPLICIT_KEY -o baselib2.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib2.so baselib2.o

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -DDERIVED_TYPE_EXPLICIT_KEY -o otherlib2.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib2.so otherlib2.o baselib2.so

$COMPILER_DRIVER -o main2.o -c main.cpp
$COMPILER_DRIVER -o def_rule_zero_with_explicit_key main2.o otherlib2.so baselib2.so

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -DDERIVED_RULE_OF_FIVE -DDERIVED_TYPE_EXPLICIT_KEY -o baselib3.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib3.so baselib3.o

$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -DDERIVED_RULE_OF_FIVE -DDERIVED_TYPE_EXPLICIT_KEY -o otherlib3.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib3.so otherlib3.o baselib3.so

$COMPILER_DRIVER -o main3.o -c main.cpp
$COMPILER_DRIVER -o def_rule_five_explicit_key main3.o otherlib3.so baselib3.so

echo
echo "Runtime demonstration of difference:"

echo
echo "The typeid is different when using rule of zero without a key method"
LD_LIBRARY_PATH=. ./def_rule_zero_without_key

echo
echo "The typeid is the same with a non-dtor explicit key and a defaulted inline dtor"
LD_LIBRARY_PATH=. ./def_rule_zero_with_explicit_key

echo
echo "The typeid is the same when using rule of FIVE/SIX and an explicit key"
LD_LIBRARY_PATH=. ./def_rule_five_explicit_key

echo
echo "Static demonstration of difference (nm -o):"

echo
echo "DerivedType vtable is emitted in consumer when using rule of zero"
nm -o otherlib1.o  | c++filt | grep vtable
echo
echo "DerivedType IS STILL visible (but externally defined) when using rule of zero with an explicit key"
nm -o otherlib2.o  | c++filt | grep vtable
echo
echo "DerivedType not visible with an explicit dtor and another virtual"
nm -o otherlib3.o  | c++filt | grep vtable

echo
echo "Static demonstration of difference (readelf -a):"

echo
echo "DerivedType vtable is emitted in consumer when using rule of zero"
readelf -a otherlib1.o  | c++filt | grep vtable
echo
echo "DerivedType vtable still emitted when using a defaulted destructor and an explicit key, but is NOTYPE GLOBAL DEFAULT and UND instead of OBJECT WEAK HIDDEN"
readelf -a otherlib2.o  | c++filt | grep vtable
echo
echo "DerivedType vtable is not emitted if the destructor is out of line"
readelf -a otherlib3.o  | c++filt | grep vtable

echo
echo
echo "In otherlib1, the vtable is present but HIDDEN (Is this STV_HIDDEN?)"

вывод с g ++:

      Runtime demonstration of difference:

The typeid is different when using rule of zero without a key method
MAIN      0x5580b2787d30
OTHER LIB 0x7f26b67f4dc8
BASE LIB  0x5580b2787d30
DT 0x559e26cc52c0

The typeid is the same with a non-dtor explicit key and a defaulted inline dtor
MAIN      0x55696d0f4d30
OTHER LIB 0x55696d0f4d30
BASE LIB  0x55696d0f4d30
DT 0x559e26cc52c0

The typeid is the same when using rule of FIVE/SIX and an explicit key
MAIN      0x5619fc118d30
OTHER LIB 0x5619fc118d30
BASE LIB  0x5619fc118d30
DT 0x559e26cc52c0

Static demonstration of difference (nm -o):

DerivedType vtable is emitted in consumer when using rule of zero
otherlib1.o:0000000000000000 V vtable for DerivedType
otherlib1.o:0000000000000000 V vtable for OtherDerivedType
otherlib1.o:                 U vtable for __cxxabiv1::__si_class_type_info

DerivedType IS STILL visible (but externally defined) when using rule of zero with an explicit key
otherlib2.o:                 U vtable for DerivedType
otherlib2.o:0000000000000000 V vtable for OtherDerivedType
otherlib2.o:                 U vtable for __cxxabiv1::__si_class_type_info

DerivedType not visible with an explicit dtor and another virtual
otherlib3.o:0000000000000000 V vtable for OtherDerivedType
otherlib3.o:                 U vtable for __cxxabiv1::__si_class_type_info

Static demonstration of difference (readelf -a):

DerivedType vtable is emitted in consumer when using rule of zero
COMDAT group section [   35] `.group' [vtable for OtherDerivedType] contains 2 sections:
COMDAT group section [   36] `.group' [vtable for DerivedType] contains 2 sections:
000000000013  00770000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
000000000013  007000000002 R_X86_64_PC32     0000000000000000 vtable for DerivedType + c
   112: 0000000000000000    32 OBJECT  WEAK   HIDDEN   112 vtable for DerivedType
   119: 0000000000000000    32 OBJECT  WEAK   DEFAULT  110 vtable for OtherDerivedType

DerivedType vtable still emitted when using a defaulted destructor and an explicit key, but is NOTYPE GLOBAL DEFAULT and UND instead of OBJECT WEAK HIDDEN
COMDAT group section [   35] `.group' [vtable for OtherDerivedType] contains 2 sections:
000000000013  00710000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
000000000013  006b0000002a R_X86_64_REX_GOTP 0000000000000000 vtable for DerivedType - 4
   107: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND vtable for DerivedType
   113: 0000000000000000    40 OBJECT  WEAK   DEFAULT  107 vtable for OtherDerivedType

DerivedType vtable is not emitted if the destructor is out of line
COMDAT group section [   34] `.group' [vtable for OtherDerivedType] contains 2 sections:
000000000013  00670000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
   103: 0000000000000000    40 OBJECT  WEAK   DEFAULT  102 vtable for OtherDerivedType


In otherlib1, the vtable is present but HIDDEN (Is this STV_HIDDEN?)

ПРИМЕЧАНИЕ: ВОПРОС:

Есть ли какой-то режим, с помощью которого dynamic_cast не удастся или какой-либо другой режим сбоя, который может быть трудно отладить, если vtables генерируются в нескольких TU и общих библиотеках?

0 ответов

Другие вопросы по тегам