Widoczność symboli, wyjątki, błąd runtime

Staram się lepiej zrozumieć widoczność symboli. GCC Wiki ( http://gcc.gnu.org/wiki/Visibility ) zawiera sekcję " Problemy z wyjątkami w C++". Zgodnie z GCC Wiki możliwe jest wystąpienie błędu have runtime z powodu nieeksportowanych WYJĄTKÓW. Błędy uruchomieniowe bez błędu czasu kompilacji / ostrzeżenie jest dość niebezpieczne, więc starałem się lepiej zrozumieć problem. Zrobiłem kilka eksperymentów, ale nadal nie mogę tego odtworzyć. Wszelkie pomysły jak odtworzyć problem?

Wiki wspomina o trzech bibliotekach używających się nawzajem, więc zrobiłem trzy małe biblioteki.

Uruchamiam następujące komendy:

Klasa wyjątku bez vtable (działa zgodnie z oczekiwaniami):

make
./dsouser

Klasa wyjątku z vtable, ale nie eksportuje (nawet nie kompiluje):

make HAS_VIRTUAL=1

Exception class exported vtable (działa jako oczekiwany): {]}

make HAS_VIRTUAL=1 EXCEPTION_VISIBLE=1
./dsouser

Makefile:

CXX=g++-4.7.1
CFLAGS=-ggdb -O0 -fvisibility=hidden
ifdef EXCEPTION_VISIBLE
  CFLAGS+=-DEXCEPTION_VISIBLE
endif
ifdef HAS_VIRTUAL
  CFLAGS+=-DHAS_VIRTUAL
endif
all: dsouser

libmydso.so: mydso.cpp mydso.h
    $(CXX) $(CFLAGS) -fPIC -shared -Wl,-soname,$@ -o $@ $<

libmydso2.so: mydso2.cpp mydso.h mydso2.h libmydso.so
    $(CXX) $(CFLAGS) -L.  -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso

libmydso3.so: mydso3.cpp mydso.h mydso2.h mydso3.h libmydso2.so
    $(CXX) $(CFLAGS) -L.  -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso -lmydso2

dsouser: dsouser.cpp libmydso3.so
    $(CXX) $< $(CFLAGS) -L. -o $@ -lmydso -lmydso2 -lmydso3

clean:
    rm -f *.so *.o dsouser

.PHONY: all clean

Mydso.h:

#ifndef DSO_H_INCLUDED
#define DSO_H_INCLUDED
#include <exception>
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso
{
  class
#ifdef EXCEPTION_VISIBLE
    SYMBOL_VISIBLE
#endif
    MyException : public std::exception
  {
  public:
#ifdef HAS_VIRTUAL
    virtual void dump();
#endif
    void SYMBOL_VISIBLE foo();
  };
}
#endif

Mydso.cpp:

#include <iostream>
#include "mydso.h"
namespace dso
{

#ifdef HAS_VIRTUAL
void MyException::dump()
{
}
#endif

void MyException::foo()
{
#ifdef HAS_VIRTUAL
  dump();
#endif
}

}

Mydso2.h:

#ifndef DSO2_H_INCLUDED
#define DSO2_H_INCLUDED
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso2
{
  void SYMBOL_VISIBLE some_func();
}
#endif

Mydso2.cpp:

#include <iostream>
#include "mydso.h"
#include "mydso2.h"
namespace dso2
{
  void some_func()
  {
    throw dso::MyException();
  }
}

Mydso3.h:

#ifndef DSO3_H_INCLUDED
#define DSO3_H_INCLUDED
#define SYMBOL_VISIBLE __attribute__ ((visibility ("default")))
namespace dso3
{
  void SYMBOL_VISIBLE some_func();
}
#endif

Mydso3.cpp:

#include <iostream>

#include "mydso2.h"
#include "mydso3.h"

#include <iostream>

namespace dso3
{

  void some_func()
  {
    try
    {
      dso2::some_func();
    } catch (std::exception e)
    {
      std::cout << "Got exception\n";
    }
  }

}
Dsouser.cpp:
#include <iostream>
#include "mydso3.h"
int main()
{
  dso3::some_func();
  return 0;
}

Dzięki, Dani

Author: VargaD, 2013-01-11

1 answers

Jestem autorem oryginalnego patcha do GCC dodającego obsługę widoczności klas, a moje oryginalne howto, które jest sklonowane GCC jest w http://www.nedprod.com/programs/gccvisibility.html . Dziękuję Vargadowi za wysłanie mi osobiście e-maila, aby powiedzieć mi o tym pytaniu.

Zachowanie, które obserwujesz, jest ważne dla ostatnich GCC, jednak nie zawsze tak było. Kiedy pierwotnie łatałem GCC w 2004 roku, złożyłem prośbę do Bugzilli GCC o porównanie środowiska wykonawczego obsługi wyjątków GCC typy rzucane przez string porównanie ich zniekształconych symboli zamiast porównywania adresów tych łańcuchów-zostało to odrzucone w tym czasie przez opiekunów GCC jako niedopuszczalny koszt wykonania, mimo że takie zachowanie robi MSVC, i pomimo tego wydajność podczas rzucania WYJĄTKÓW nie jest ogólnie uważana za ważną, biorąc pod uwagę, że są one rzadkie. Dlatego musiałem dodać specjalny wyjątek do mojego przewodnika widoczności, aby powiedzieć, że każdy wyrzucony Typ nigdy nie może być ukryty, nie raz, w binarnym jako " hiddenness "przebija" default", więc tylko jedna deklaracja ukrytego symbolu gwarantuje nadpisanie wszystkich przypadków tego samego symbolu w danym binarnym.

Co się stało potem chyba nikt z nas się nie spodziewał - KDE bardzo publicznie przyjęło moją funkcję. To kaskadowe w prawie każdym dużym projekcie wykorzystującym GCC w zadziwiająco krótkim czasie. Nagle ukrywanie symboli było normą, a nie wyjątkiem.

Niestety, niewielka liczba osób nie zastosowała mojego poradnika poprawnie dla typów wyrzuconych wyjątków, a także ciągłe raporty o błędnej obsłudze WYJĄTKÓW obiektów współdzielonych w GCC ostatecznie spowodowały, że opiekunowie GCC zrezygnowali i wiele lat później wprowadzili poprawki w porównywaniu łańcuchów dla dopasowania typu wyrzuconego, tak jak pierwotnie prosiłem. Stąd w nowszych GCCs sytuacja jest nieco lepsza. Nie zmieniłem przewodnika ani instrukcji, ponieważ takie podejście jest nadal najbezpieczniejsze na każdym GCC od v4. 0, a nowsze GCC są bardziej niezawodne w obsłudze wyjątek rzuca się z powodu używania porównywania łańcuchów, przestrzeganie zasad przewodnika tego nie zaszkodzi.

To sprowadza nas do problemu typeinfo. Dużym problemem jest to, że najlepsza praktyka C++ wymaga, aby zawsze dziedziczyć praktycznie w typach throwable, ponieważ jeśli skomponujesz dwa typy WYJĄTKÓW, oba dziedziczące (powiedzmy) z STD::exception, posiadanie dwóch równoodległych klas bazowych STD::exception spowoduje catch (STD:: exception&) do auto call terminate (), ponieważ nie może określ, która klasa bazowa ma być dopasowana, więc zawsze musisz mieć tylko jedną klasę bazową STD::exception i to samo uzasadnienie dotyczy każdej możliwej kompozycji typu throwable. Ta najlepsza praktyka jest szczególnie wymagana w każdej bibliotece C++, ponieważ nie możesz wiedzieć, co użytkownicy zewnętrzni zrobią z Twoimi typami WYJĄTKÓW.

Innymi słowy, oznacza to, że wszystkie wyrzucane typy WYJĄTKÓW w najlepszych praktykach zawsze będą miały łańcuch kolejnych RTTI dla każdej klasy bazowej, a ten wyjątek dopasowanie jest teraz przypadkiem wewnętrznego wykonania udanej operacji dynamic_cast do dopasowanego typu, operacji o (liczba klas bazowych). A żeby dynamic_cast pracował nad łańcuchem wirtualnie dziedziczonych typów, zgadliście, trzeba każdego tego łańcucha mieć domyślną widoczność. Jeśli nawet jeden jest ukryty przed kodem wykonującym catch (), cały caboodle idzie w górę i dostajesz terminate (). Byłbym bardzo zainteresowany, gdybyś przerobił powyższy przykładowy kod na wirtualnie i zobacz co się stanie - jeden z twoich komentarzy mówi, że odmawia linkowania, co jest świetne. Ale załóżmy, że DLL A definiuje typ A, podklasy DLL B Typ A na B, podklasy DLL C Typ B na C, a program D próbuje złapać wyjątek typu A, gdy Typ C został wyrzucony. Program D będzie miał dostępną informację o typie a, ale powinien być błędny podczas próby pobrania RTTI dla typów B i C. Może jednak ostatnie GCC też to naprawiły? Nie wiem, moja uwaga w ostatnich latach jest na clang jako to przyszłość dla wszystkich kompilatorów C++.

Oczywiście jest to bałagan, ale jest to bałagan specyficzny dla elfów - nic z tego nie wpływa na PE lub MachO, z których oba uzyskują wszystkie powyższe prawa, nie używając globalnych tabel symboli procesów w pierwszej kolejności. Jednak grupa analityczna WG21 SG2 Modules pracująca nad C++17 musi skutecznie zaimplementować wyeksportowane szablony modułów do pracy w celu rozwiązania naruszeń ODR, A C++17 jest pierwszym proponowanym standardem, który widziałem, aby być napisany z myślą o LLVM. Innymi słowy, kompilatory C++17 będą musiały zrzucić złożony AST na dysk, tak jak robi to clang. A to oznacza ogromny wzrost gwarancji tego, co jest dostępne RTTI-rzeczywiście, dlatego mamy SG7 Reflection study group, ponieważ Ast z modułów C++ umożliwia ogromny wzrost możliwych możliwości autorefleksji. Innymi słowy, spodziewaj się, że powyższe problemy szybko znikną wraz z adopcją C++17.

Więc, w skrócie, trzymaj się mojego oryginalnego przewodnika na razie. And things will mam nadzieję, że w następnej dekadzie będzie znacznie lepiej. I podziękuj Apple za sfinansowanie tego rozwiązania, minęło bardzo dużo czasu, ze względu na to, jak bardzo trudne jest.

Niall

 23
Author: Niall Douglas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-16 17:19:54