Написание устойчивых расширений браузера

На устойчивость работы Windows Internet Explorer неблагоприятно влияют плохо реализованные расширения, такие как вспомогательные объекты BHO (Browser Helper Object), панели инструментов и элементы управления Microsoft ActiveX. В этой статье подытожены важные указания, которым разработчики должны следовать при создании расширений COM для настройки браузера, и предоставлены советы и рекомендации по созданию правильно работающих расширений браузера, которые не приводят к сбою или зависанию (отсутствию реакции на действия пользователя) Internet Explorer.

Эта статья содержит следующие разделы:

  • Вопросы COM 
    • Совет 1. Правильно используйте AddRef и Release. 
    • Совет 2. Правильно и эффективно реализуйте DllCanUnloadNow. 
  • Безопасность кода 
    • Совет 3. Всегда проверяйте внешний вход для предотвращения переполнения буфера. 
    • Совет 4. Используйте переключатель компилятора /GS для добавления дополнительной защиты от переполнения буфера. 
    • Совет 5. Перед продолжением проверяйте, нет ли ошибок в значениях, возвращаемых каждой функцией. 
  • Потоки 
    • Совет 6. Соответствующим образом обрабатывайте выбранную модель потоков. 
    • Совет 7. Избегайте использовать однопоточные элементы управления. 
    • Совет 8. Создайте рабочий поток для выполнения в фоновом режиме задач, выполнение которых занимает много времени. 
    • Совет 9. Предоставьте возможность отмены длительных операций. 
    • Совет 10. Используйте встроенные механизмы синхронизации Windows. 
    • Совет 11. Никогда не используйте TerminateThread. 
  • Времена ожидания 
    • Совет 12. Не используйте слишком длительные времена ожидания или INFINITE в качестве значения времени ожидания. 
    • Совет 13. Используйте флаг SMTO_ABORTIFHUNG. 
  • DLLMain 
    • Совет 14. Чтобы избежать новых уведомлений о потоках, используйте DisableThreadLibraryCalls. 
    • Совет 15. Создайте отдельную процедуру для сложной инициализации. 
  • Управление памятью 
    • Совет 16. Изолируйте память, используемую расширением, в одной куче. 
    • Совет 17. Включите защиту памяти, чтобы помочь снизить риск атак из Интернета. 
  • Обработка исключений 
    • Совет 18. Всегда указывайте исключения, которые собираетесь перехватить. 
  • Доставка 
    • Совет 19. Предоставляйте хорошо разработанную процедуру установки. 
    • Совет 20. Предоставляйте символьные файлы (PDB) для каждого внешнего выпуска. 
    • Совет 21. Подпишитесь на систему отчетов об ошибках Windows. 

Вопросы COM

Совет 1. Правильно используйте AddRef и Release.

AddRef и Release управляют жизненным циклом COM-объекта с помощью подсчета ссылок, AddRef сообщает COM-объекту, что один фрагмент кода использует объект в данный момент, а Release сообщает COM-объекту, что один фрагмент кода закончил использовать объект. Пока счетчик ссылок больше нуля, объект остается активным. Когда счетчик ссылок падает до нуля, объект уничтожается, чтобы освободить память.

Release необходимо вызывать один и только один раз для каждого вызова AddRef. Если AddRef и Release используются неаккуратно, COM-объекты могут разрушиться слишком рано и вызвать сбой (слишком много вызовов Release) или отрицательно повлиять на быстродействие, занимая память до закрытия браузера (слишком много вызовов AddRef). Дополнительные сведения см. в статье Управление временем жизни объектов с помощью счетчиков ссылок.

Совет 2. Правильно и эффективно реализуйте DllCanUnloadNow.

В каждой библиотеке DLL, содержащей COM-объекты, должна быть реализована функция DllCanUnloadNow, вызываемая COM-объектом, чтобы проверить, можно ли выгрузить DLL. Неправильная или неэффективная реализация DllCanUnloadNow может привести к сбоям, зависаниям или падению быстродействия.

Если DLL не может быть выгружена, функция DllCanUnloadNow должна немедленно вернуть S_FALSE. Если DLL может быть выгружена, убедитесь, что все активные потоки, созданные DLL, были завершены, вся память, выделенная DLL, была освобождена, а все зацепки интерфейсов (зацепки для окна, мыши и клавиатуры) — удалены. Вызовите CoUninitialize один раз для каждого вызова CoInitialize, выполненного этой DLL. Наконец, так как COM не дает гарантий того, что DLL можно выгрузить, DLL должна быть полностью готова к выгрузке до того, как DllCanUnloadNow вернет S_OK. Дополнительные сведения см. в документации DllCanUnloadNow.

Безопасность кода

Совет 3. Всегда проверяйте внешний вход для предотвращения переполнения буфера.

Одной из самых распространенных ошибок с точки зрения безопасности и устойчивости является переполнение буфера, происходящее, когда код записывает в буфер памяти больше данных, чем буфер может вместить. Большей частью переполнение буфера вызвано плохим вводом — записью слишком большого объема данных, так что они записываются в соседнюю память. Хотя эта ошибка часто приводит к сбою программы, взломщики-злоумышленники могут использовать эту уязвимость для захвата управления системой. Разработчики расширений Internet Explorer должны принять дополнительные меры предосторожности, чтобы избежать переполнения буфера. Дополнительные сведения см. в статье Предотвращение переполнения буфера.

Совет 4. Используйте переключатель компилятора /GS для добавления дополнительной защиты от переполнения буфера.

Чтобы обнаружить переполнение буфера, разработчики Microsoft Visual C++ могут использовать параметр компилятора /GS. Этот переключатель вводит новую схему стека, содержащую рассчитанный файл "cookie" безопасности между буфером и возвращенным адресом функции. Если это значение переписывается, приложение сообщит об ошибке и завершит работу. Дополнительные сведения см. в статье Тщательные проверки безопасности компьютера.

Совет 5. Перед продолжением проверяйте, нет ли ошибок в значениях, возвращаемых каждой функцией.

Вызывая функции, разработчики должны позаботиться о проверке допустимости передаваемых параметров. Параметры должны быть правильно размещены, относиться к правильному типу и находиться в диапазоне значений, ожидаемых функцией. Это может предотвратить ошибки функции до их появления, но не все ошибки можно предвидеть. Если ошибка может повлиять на выполнение оставшейся части процедуры, ее следует обнаружить и обработать. Будьте особенно осторожны, добавляя новый код проверки ошибки. Часто разработчик забывает освободить ресурсы во вновь введенных путях кода ошибки. См. ниже в этой статье раздел Обработка исключений.

Потоки

Совет 6. Соответствующим образом обрабатывайте выбранную модель потоков.

Для расширений рекомендуется использовать контейнерную модель потоков, но независимо от того, использует ли расширение контейнерную модель или свободные потоки, оно должно следовать правилам, соответствующим выбранной модели. Internet Explorer является многопотоковым приложением и создаст несколько потоков для расширение со свободными потоками, поэтому будьте очень осторожны, разрабатывая такое расширение. Дополнительные сведения см. в статье Процессы, потоки и контейнеры.

Совет 7. Избегайте использовать однопоточные элементы управления.

Начиная с версии 4.0, Internet Explorer требует, чтобы для всех размещаемых в браузере элементах управления ActiveX использовались хотя бы потоки в контейнерах. При работе с однопоточными объектами у браузера возникают проблемы с быстродействием и устойчивостью (зависанием). Дополнительные сведения см. в статье Устранение неполадок при сбоях элементов управления ActiveX в Internet Explorer.

Совет 8. Создайте рабочий поток для выполнения в фоновом режиме задач, выполнение которых занимает много времени.

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

Совет 9. Предоставьте возможность отмены длительных операций.

Для однопотоковых приложений разработчикам следует создать кнопку "Отмена" любого продолжительного действия (или любого действия, которое может стать продолжительным). Приложения с отдельным пользовательским интерфейсом и рабочими потоками должно предоставить пользователю возможность отменить любые продолжительные операции для потока пользовательского интерфейса.

Совет 10. Используйте встроенные механизмы синхронизации Windows.

Microsoft Win32 предоставляет богатый набор конструкций синхронизации (например критические разделы или семафоры), чтобы предотвратить одновременное обращение потоков к данным. Разработчикам не следует создавать собственные объекты синхронизации и не следует отказываться от синхронизации ради улучшения быстродействия. Дополнительные сведения см. в статье О синхронизации.

Совет 11. Никогда не используйте TerminateThread.

Функция Win32 TerminateThread принудительно прерывает потоки, что помимо других проблем ведет к опасности появления незавершенных блокировок и утечки выделения стека потока. Если точно неизвестно, что делает поток, есть опасность перевода системы в противоречивое состояние. (Дополнительные сведения см. в статье TerminateThread.) Вместо этого используйте событие синхронизации "shutdown" (завершение), чтобы сигнализировать о необходимости завершить рабочие потоки.

По этой же причине будьте очень осторожны и при использовании функции ExitThread. Когда поток завершается, он должен освободить все блокировки и любые ресурсы, задействованные для выполнения логики программы.

Времена ожидания

Совет 12. Не используйте слишком длительные времена ожидания или INFINITE в качестве значения времени ожидания.

При каждом вызове функции, задающей значение времени ожидания, не поддавайтесь искушению ждать в течение бесконечного времени. Бесконечное время ожидания может привести к тому, что расширение (и сам браузер) будет казаться зависшим, так как пользовательский ввод не сможет быть обработан до возвращения из функции. Также следует избежать использования функций, вызывающие длительное ожидание без задания времени ожидания.

Совет 13. Используйте флаг SMTO_ABORTIFHUNG.

Вместо SendMessage используйте PostMessage или SendMessageTimeout с флагом SMTO_ABORTIFHUNG.

Функция SendMessage не возвращает управление, пока отправленное сообщение не будет полностью обработано. Это может привести к длительному ожиданию, если приложение еще обрабатывает предыдущее сообщение или если приложение занято и вообще не обрабатывает сообщение. SendMessageTimeout с флагом SMTO_ABORTIFHUNG возвращает управление, не ожидая истечения периода времени ожидания, если принимающий поток не отвечает.

DLLMain

Совет 14. Чтобы избежать новых уведомлений о потоках, используйте DisableThreadLibraryCalls.

Все расширения браузера реализуются как библиотеки DLL и, следовательно, экспортируют функцию DllMain, которая вызывается из Windows при подключении к процессу браузера или отключении от него. Так как Internet Explorer интенсивно использует многопотоковость, частые уведомления DLL_THREAD_ATTACH и DLL_THREAD_DETACH, поступающие в DllMain, могут замедлить общее быстродействие расширения и процесса браузера. Если расширению не требуется отслеживание на уровне потока, вызовите DisableThreadLibraryCalls во время уведомления DLL_PROCESS_ATTACH. Дополнительные сведения см. в статье DllMain.

Совет 15. Создайте отдельную процедуру для сложной инициализации.

Существуют ограничения на действия, возможные в точке входа DLL. Вызов сложных интерфейсов API, таких как интерфейсы в Shell32.dll, может вызвать взаимоблокировки в DllMain. Это происходит, когда функция DllMain запускается одновременно с блокировкой загрузчика ОС, который задействуется при каждой загрузке DLL. Теоретически следует избегать использования в функции DllMain любого интерфейса API, который может включить загрузку DLL в фоновом режиме. (Дополнительные сведения см. в разделе "Ограничения DllMain" статьи Проблема смешанной загрузки DLL.) Если нужно сложная инициализация, создайте глобальную функцию инициализации для DLL и потребуйте, чтобы приложения вызывали процедуру инициализации перед вызовом любых других процедур в DLL.

Управление памятью

Совет 16. Изолируйте память, используемую расширением, в одной куче.

По возможности расширения Internet Explorer должны использовать для выделения памяти функцию HeapCreate Win32 вместо использования выделения памяти по умолчанию, предоставляемого компилятором (например malloc). Не используйте GetProcessHeap вместе с HeapAlloc, вместо этого используйте HeapCreate для выделения кучи специально для нужд памяти расширения.

Совет 17. Включите защиту памяти, чтобы помочь снизить риск атак из Интернета.

Для Internet Explorer 7 в Windows Vista введен отключенный по умолчанию параметр панели управления Интернета Включить защиту памяти для снижения риска атаки из Интернета. Этот параметр также называется предотвращением выполнения данных или невыполнением (Data Execution Prevention or No-Execute, DEP/NX). В Internet Explorer 8 в Windows Server 2008 и Windows Vista с пакетом обновления 1 (SP1) этот параметр по умолчанию включен.

DEP/NX помогает помешать атакам, отказывая в выполнении кода, помеченного в памяти как неисполняемый. DEP/NX, в сочетании с технологией Address Space Layout Randomization (ASLR), затрудняет для злоумышленников использование уязвимостей, связанных с памятью, таких как переполнение буфера. Самое лучшее, что защита применяется и к Internet Explorer, и к загружаемым им надстройкам.

Для Internet Explorer 7 использование DEP/NX по умолчанию отключено с целью обеспечения совместимости, так как в ряд популярных надстроек встроена более старая версия библиотеки активных шаблонов (ATL). До версии 7.1 SP1 ATL использовала динамически создаваемый код несовместимым с DEP/NX способом. К счастью, в Windows Server 2008 и последние пакеты обновлений Windows были добавлены новые интерфейсы API DEP/NX, позволяющие использовать DEP/NX, одновременно сохраняя совместимость с более старыми версиями ATL.

При создании надстроек Internet Explorer можно помочь гарантировать для пользователей плавное обновление до Internet Explorer 8 с помощью следующих действий.

  • Если код зависит от более старых версий ATL, перестройте его с ATL v7.1 SP1 или более поздней (Microsoft Visual Studio 2005 содержит ATL 8).
  • Задайте параметр /NXCompat компоновщика, чтобы указать, что расширение совместимо с DEP/NX.
  • Проверьте свой код с включенным параметром DEP/NX, используя Internet Explorer 8 в Windows Vista с SP1 или с Internet Explorer 7 после включения параметра DEP/NX. (Чтобы включить параметр DEP/NX, запустите Internet Explorer от имени администратора, а затем установите соответствующий флажок на вкладке Дополнительно окна Свойства обозревателя.)
  • Используйте другие доступные параметры компилятора, такие как защита стека (/GS), безопасная обработка исключений (/SafeSEH) и ASLR (/DynamicBase).

Обработка исключений

Совет 18. Всегда указывайте исключения, которые собираетесь перехватить.

Расширения Internet Explorer, использующие преимущество структурированной обработки исключений, чтобы проверить тип исключения перед выполнением обработчика, должны применять GetExceptionCode. Будет ошибкой перехватывать нарушения прав доступа (AV) и ошибки переполнения стека, так как это скрывает противоречивое состояние процесса. Перехват AV не делает расширение более устойчивым — при этом сбой только преобразуется в повреждение данных.

После оператора __try убедитесь, что выражение __except возвращает значение TRUE только для ограниченного количества исключений, которое может обработать расширение. Никогда не выполняйте обработчик исключений для всех исключений и не используйте функции UnhandledExceptionFilter или SetUnhandledExceptionFilter. Дополнительные сведения см. в статье Структурированная обработка исключений.

Доставка

Совет 19. Предоставляйте хорошо разработанную процедуру установки.

Если расширение Internet Explorer собрано в пакет для доставки пользователям, программа установки должна устанавливать расширение только для проверенных версий операционной системы и отказываться устанавливать его для неподдерживаемых версий. Кроме того, программа установки расширения также должна обеспечивать полное удаление файлов расширения.

Совет 20. Предоставляйте символьные файлы (PDB) для каждого внешнего выпуска.

Если неудобно предоставлять доступ ко всем закрытым данным для отладки, можно подумать о предложении вместо нее публичных ("опущенных") символов.

Совет 21. Подпишитесь на систему отчетов об ошибках Windows.

Отчеты об ошибках Windows (WER) — это набор технологий Windows, захватывающих данные о сбое программного обеспечения и и поддерживающих создание отчетов конечного пользователя с данными о сбое. С помощью служб Winqual поставщики программного обеспечения и оборудования могут получить доступ к отчетам для анализа проблем и принятия соответствующих мер. Технологии WER реализованы в операционных системах Windows XP и Windows Server 2003. Дополнительные сведения см. в статье Отчеты об ошибках Windows. Приступая к работе.