Подтвердить что ты не робот

Видимость символа, исключения, ошибка времени выполнения

Я пытаюсь лучше понять видимость символов. В GCC Wiki (http://gcc.gnu.org/wiki/Visibility) содержится раздел "Проблемы с исключениями С++". В соответствии с GCC Wiki возможно наличие времени выполнения из-за не экспортируемых исключений. Ошибки времени выполнения без ошибки компиляции/предупреждения довольно опасны, поэтому я попытался лучше понять проблему. Я сделал несколько экспериментов, но я до сих пор не могу воспроизвести его. Любые идеи о том, как воспроизвести проблему?

В Wiki упоминаются три библиотеки, которые используют друг друга, поэтому я создал три небольшие библиотеки.

Я запускаю следующие команды:

Класс исключений без vtable (работает как ожидалось):

make
./dsouser

Класс исключений с vtable, но он не экспортируется (даже не компилируется):

make HAS_VIRTUAL=1

Класс исключений экспортирован vtable (работает как ожидалось):

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,[email protected] -o [email protected] $<

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

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

dsouser: dsouser.cpp libmydso3.so
    $(CXX) $< $(CFLAGS) -L. -o [email protected] -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;
}

Спасибо, Dani

4b9b3361

Ответ 1

Я автор оригинального патча для GCC, добавляющего поддержку видимости класса, и мой оригинальный способ, который клонировал GCC, находится в http://www.nedprod.com/programs/gccvisibility.html. Я благодарю VargaD за то, что вы лично отправили мне письмо, чтобы рассказать мне об этом вопросе.

Поведение, которое вы наблюдаете, действительно для недавних GCC, однако это было не всегда так. Когда я впервые исправил GCC еще в 2004 году, я отправил запрос в GCC bugzilla для среды выполнения обработки исключений GCC для сравнения брошенных типов путем сравнения строк с их искаженными символами вместо сравнения адресов этих строк - это было отклонено в то время GCC как неприемлемая стоимость исполнения, несмотря на то, что это поведение - это то, что делает MSVC, и, несмотря на это, производительность во время исключения бросает, как правило, не считается важной, поскольку они должны быть редкими. Поэтому я должен был добавить конкретное исключение из моего руководства по видимости, чтобы сказать, что любой брошенный тип никогда не должен быть скрыт, а не один раз, в двоичном виде, поскольку "скрытые" козыри "по умолчанию", поэтому только одно скрытое объявление символа гарантирует отмену всех случаев тот же символ в данном двоичном формате.

Что произошло дальше, я полагаю, никто из нас не ожидал - KDE очень публично поддержал мою добавленную функцию. Это каскадировалось почти в каждом крупном проекте, использующем GCC, в удивительно короткие сроки. Внезапно скрытие символов было нормой, а не исключением.

К сожалению, небольшое число людей не применило мой путеводитель правильно для исключенных типов, а постоянные сообщения об ошибках о некорректной обработке исключений между общими объектами в GCC в конечном итоге заставили разработчиков GCC отказаться и много лет спустя патч в сопоставлении строк для сопоставления типов, как я изначально запрашивал. Следовательно, в новых GCC ситуация несколько лучше. Я не изменил ни руководство, ни инструкции, поскольку этот подход по-прежнему безопасен для каждого GCC с версии 4.0, а в то время как более новые GCC более надежны в обработке исключений, из-за того, что теперь используют сравнение строк, следуя правилам руководства, это не повредит что.

Это приводит нас к проблеме typeinfo. Большая проблема заключается в том, что для наилучшей практики на С++ требуется, чтобы всегда наследовался практически в типах сбрасываемых типов, потому что если вы создаете два типа исключений, наследующих (скажем), из std:: exception, имеющих два равноудаленных std:: базовые классы исключений вызовут catch (std:: exception &) для автоматического вызова terminate(), потому что он не может решить, какой базовый класс должен соответствовать, поэтому вы должны иметь только один базовый класс std:: exception, и тот же Обоснование применимо к любому возможному составу бросаемого типа. Эта передовая практика особенно необходима в любой библиотеке С++, потому что вы не можете знать, что сторонние пользователи будут делать с вашими типами исключений.

Другими словами, это означает, что все типы исключенных исключений в наилучшей практике всегда будут иметь цепочку последовательных RTTI для каждого базового класса, и это совпадение исключений теперь является случаем, когда внутреннее выполнение успешного dynamic_cast < > к типу согласовывается, операция O (число базовых классов). И для dynamic_cast < > для работы над цепочкой фактически унаследованных типов, вы догадались, вам нужно каждый этой цепочки, чтобы иметь видимость по умолчанию. Если даже один из них скрыт от кода, выполняющего catch(), весь caboodle идет вверх, и вы получаете terminate(). Мне было бы очень интересно, если вы переработали свой примерный код выше, чтобы фактически наследовать и посмотреть, что произойдет - один из ваших комментариев говорит, что он отказывается связывать, что здорово. Но предположим, что DLL A определяет подклассы типа A, DLL B типа A в B, подклассы DLL C типа B на C, а программа D пытается поймать исключение типа A при броске типа C. Программа D будет иметь доступную информацию типа A, но должна быть неисправна при попытке получить RTTI для типов B и C. Возможно, последние GCC также исправили это? Я не знаю, мое внимание в последние годы связано с тем, что будущее для всех компиляторов С++.

Очевидно, что это беспорядок, но это специфический для ELF беспорядок - ничто из этого не влияет на PE или MachO, оба из которых получают все вышеперечисленные права, не используя, в первую очередь, глобальные таблицы процессов. Однако исследовательская группа WG21 SG2 Modules, работающая в направлении С++ 17, должна эффективно внедрять экспортированные шаблоны для работы модулей, чтобы разрешать нарушения ODR, а С++ 17 - это первый предложенный стандарт, который я видел в LLVM в разум. Другими словами, компиляторы С++ 17 должны будут сбрасывать сложный АСТ на диск, как это делает clang. И это подразумевает огромный рост гарантий того, что RTTI доступно - действительно, почему у нас есть исследовательская группа SG7 Reflection, потому что AST из С++ Modules позволяет значительно увеличить возможности самоанализа. Другими словами, ожидайте, что вышеупомянутые проблемы скоро исчезнут с принятием С++ 17.

Итак, короче, продолжайте следовать моему оригинальному руководству. И в следующем десятилетии все будет намного лучше. И поблагодарить Apple за то, что она финансировала это решение, прошло очень много времени из-за того, насколько жестоко это происходит.

Найл