На что был похож код в Imaging
Чтобы год с лишним в Imaging не прошли даром, я попробую записать если не “what did we learn from it?” (the obvious answer is not to do it again), то хотя бы уж “what the fuck it is we did”. (это если что цитаты).
Забавно, что почти год спустя про WIC все еще спрашивают и даже за дизайн приходится чуть спорить. Кто бы со мной столько про DX разговаривал.
Итак, WIC (Windows Imaging Component), компонент для работы с картинками в разных форматах, декодинг, энкодинг, фильтры, конвертеры, регистрируемые кодеки, вся фигня. Не очень продвинутый COM, без ATL.
О чем больше всего заботятся при декодинге картинок - о security, то есть чтобы с помощью картинки нельзя было выполнить произвольный код (через buffer overrun, integer overflow и прочее). Заботиться надо не только при декодинге как таковом, но и в любом фильтре/конвертере, работающем с внешними данными.
В общем-то WIC хороший пример того, как пишутся библиотеки в Windows внизу user mode, с упором на security. Кернел, конечно, пишут не так, user mode-компоненты уровнем выше (скажем, shell), тоже совсем не такие, а вот базовые графические библиотеки (WIC, MIL, DX в XP итд) - это где-то там.
Так вот, как такое кодают в Windows.
Пишут фактически на C с классами. Разумеется, наследуются от COM-интерфейсов, кроме них есть несколько базовых классов BaseEncoder, BaseDecoder, BaseBitmap да и все. Иерархии больше двух предков фактически нет.
Из темплейтов есть только один суровый джедайский контейнер DynArray, аналог std::vector, и все. Он настолько суров, что наверное следует сказать о нем пару слов. Он принципиально не зовет никаких конструкторов и никаких деструкторов, хочешь иницалироваться - зови inplace new или делай Init. Копирует, разумеется, тупо память, без всякой пидарасни с copy constructor. Большая часть реализации DynArray - не темплейтная, сам DynArray - ее темплейтный потомок, который только подставляет sizeof(T) базовому классу, практически все инлайнится. Есть вариант темплейта с начальным размером массива, он тогда выделится на стеке. Отличный контейнер, ничего лишнего.
Никаких смартпойнтеров (по мне, кстати, стоило бы).
Нет exceptions, вся обработка ошибок через return и HRESULT. Разумеется, библиотека готова к тому, что может не выполниться любая операция - любая аллокация памяти, любая операция с файлом или потоком итд. К счастью, чаще всего достаточно передавать ошибку наверх и перевести объект в невалидный стейт. И для этого мы используем goto (буахаха).
Есть магическое макро IFC, условно, вот такое (spare me the bad macro bullshit):
#define IFC(x) if (FAILED(hr=x)) { goto cleanup; }
|
| _Winnie C++ Colorizer |
Код большинства функций выглядит примерно так:
HRESULT Foo(...)
{
HRESULT hr;
IFC(Foo1(...));
...
IFC(Foo2(...));
...
cleanup:
// Do the cleanup
return hr;
}
|
| _Winnie C++ Colorizer |
Когда привыкаешь, удобно.
Так вот, о security.
Почти нигде в коде не найти простого сложения или умножения двух целочисленных переменных, самое сложное - i++ в цикле. Потому что будет integer overflow и сразу жопа-кеды, запись с неправильным смещением, привет новому эксплойту.
Везде вместо
a = b*c |
| _Winnie C++ Colorizer |
написан
IFC(UIntMult(b,c,&a)); |
| _Winnie C++ Colorizer |
И также для сложения и прочего. То есть, функция умножения возвращает HRESULT и может всегда зафейлиться. Представьте себе, как весело с такой арифметикой писать сложные преобразования.
(Петя, расскажи им еще раз про RSX?)
Дальше, про самое веселое - buffer overrun.
Весь Imaging про вычисление цвета пикселя и его запись в память, и поэтому проверять каждую запись в массив нельзя по соображениям производительности.
Для этого (кроме здравого смысла и security review) есть аццкие тулы PREfix/PREfast, статические анализаторы кода. Чтобы они работали, все параметры функций надо аннотировать - вход это или выход, и если массив - то какой размерности.
Например:
HRESULT Foo(
__in UINT inparam,
__in_ecount(inparam) UINT* inarray,
__out UINT* outparam,
__deref_out_ecount(outparam) UINT** outarray)
|
| _Winnie C++ Colorizer |
То есть, функция принимает inparam в качестве входного параметра, массив inarray из inparam элементов, и возвращает размер массива и указатель на массив именно этого размера. Причем возвращаемый указатель не может быть NULL.
И значит по таким декларациям тул анализирует код функции и смотрит, возможна ли запись по индексу больше заданного. Отслеживает все арифметические действия внутри кода, проверяет соответствие декларациям вызываемых функций, выдает диагностику - “а вот если передали такое-то, то в ту переменную запишется такое, в ту такое, потом это вот так сложится в третью, потом вычтется вот здесь, потом будет использовано в качестве индекса, и ЖОПА”. Ты сидишь и медитируешь, что же он такое пытается тебе сказать.
Иногда не может догадаться, ты пишешь ему хинты вроде “а считай пожалуйста, что такое-то условие верно”.
Но обычно - натурально находит баги.
Разумеется, необходимая часть всех тестов - запуск под Application Verifier, он в числе прочего ловит buffer overrun. И не надо никаких умных контейнеров.
Вот на таких state of art-технологиях делается некая часть Windows. С с классами, goto, HRESULT, функции для умножений и сложений. Дедовскими методами, без темплейтов, прогресса и прочего rocket science. Люди аккуратно пишут код на языке C, как писали 10 и 20 лет назад.
С++ и ATL-зеалотам - пламенные приветы.
IronPeter:
Отличный пост.
>Петя, расскажи им еще раз про RSX?
Вероятно, это был риторический вопрос, но все же. В RSX все регионы памяти защищены, в аннотации DMA объекта пишется begin & end. Причем end - это последняя доступная ячейка памяти, size - 1. Если передать в гипервизор в качестве size 0, то end становился равным 0xffffffff. Пользователю на DMA отдавалась вся доступная DDR память. Ковырять дальше и мапить 3D было делом техники.
17 October 2008, 2:37 pmdDIMA:
Отличный пост. С++ и ATL-зеалотам - также пламенный привет )))).
17 October 2008, 3:09 pmНа выходных напишу про страшную багу в обработке изображений, которая ловилась долго только потому, что мы были молодые и глупые. Интересно, встречается ли схожая бага в WIC…. :)
volodya:
Ну, не то, чтобы драйвера уж сильно иначе писали. Там тоже активно юзаются всякие новомодные приблуды:
17 October 2008, 6:07 pm1. Тот же префаст - обязательно: http://www.microsoft.com/whdc/devtools/tools/PREfast.mspx
2. ntstrsafe - http://www.microsoft.com/whdc/devtools/tools/PREfast.mspx
3. Для дров, которые соблюдают драйверные модели майкрософта юзается sdv (к сожалению, для других случаев он практически бесполезен) - http://www.microsoft.com/whdc/DevTools/tools/SDV.mspx
4. В обязательном порядке юзается верификатор драйверов - http://www.microsoft.com/whdc/DevTools/tools/vistaverifier.mspx - считай, аналог appverifier, только для r0.
5. Вместо ExAllocatePool используется ExAllocatePoolWithTag + тулза pooltags для отлова ликов памяти.
CEMEH:
Не, ну кернел - это святое, над кернелом все кудахчут и бегают кружочками.
17 October 2008, 7:45 pmМой пойнт, что в обычном таком user mode (по большому счету, обычное приложение) - пишут со схожей строгостью.
volodya:
Да, согласен. Строго написан код. Уже дал своим почитать статью почитать :)
Вероятно, можно припомнить еще Ховарда с его блогом и всякие NX-биты и ASLR.
Я бы, дополнительно, еще через mingw прогнал. Правда, mingw не понимает аннотаций префаста и (nt)strafe. Это надо дописывать. Работаем над этим.
17 October 2008, 10:45 pmvolodya:
Да, кстати. Все эти prefast и иже с ним - бледнеют перед clang (http://clang.llvm.org/). prefast разбирает код on per-function basis и это накладывает достаточно сильные ограничения.
Был, помнится, еще у нас и случай, когда игрались с sdv (а этот парень разбирает весь код) и он не находил простейших багов.
Поэтому, я бы ставил на то, что когда-нибудь clang превратиться во что-нибудь достойное. А до той поры - увы, префаст.
17 October 2008, 10:50 pmCEMEH:
PREfix (серверная версия PREfast) уже смотрит не только на функцию, а на весь код. Разумеется, во время чекина не запустишь, но в процессе разработки он полным анализом находит всякое.
18 October 2008, 1:38 amIronPeter:
clang вроде совсем неживой пока. С++ не умеет. Static Analyzer в зародыше.
Ходили слухи про новую платформу для компиляторов от MS, аналоге llvm. С промежуточным представлением кода, мега-оптимизациями и прочими радостями. Что-то затихло. Или не затихло?
18 October 2008, 8:35 amCEMEH:
Называлось Phoenix. Как назвали, так и поплыло. Утверждается, что возрождается где-то там в процессе. Хер знает, что получится.
18 October 2008, 10:51 amDmitry Tyurev:
> PREfix (серверная версия PREfast) уже смотрит не только на функцию, а на весь код.
А Coverity лучше или хуже? :)
18 October 2008, 4:50 pmvolodya:
>clang вроде совсем неживой пока. С++ не умеет. Static Analyzer в зародыше.
Что значит, не умеет? http://llvm.org/devmtg/2007-05/09-Naroff-CFE.pdf
>PREfix (серверная версия PREfast) уже смотрит не только на функцию, а на весь код
Погуглил я, погуглил и сдался. Упоминания есть, самой тулзы - нет. Хде достать этого зверя?
18 October 2008, 6:17 pmCEMEH:
Видимо, internal only пока.
18 October 2008, 7:37 pmIronPeter:
Вроде не умеет
http://clang.llvm.org/cxx_status.html цитата Currently most of the C++ features are missing; here you can find features that are at least partially supported in Clang
Презентация рассказывает про “C” фронтэнд. C++ и C - разные языки вообще-то.
18 October 2008, 7:57 pmJoes:
Одно непонятно - скажем чего с JPEGLib делали? Переписывали с нуля?
19 October 2008, 10:45 amhighly professional scums » Blog Archive » Рецепты отладки. Падение на ровном месте.:
[...] Subscribe in a reader « На что был похож код в Imaging [...]
19 October 2008, 11:44 amfree(wasker);:
[...] Познавательно. [...]
19 October 2008, 6:37 pmCEMEH:
Jpeglib много дописывали. Расставляли аннотации, добавляли SSE код в критические места, добавляли transcoding без изменения данных.
20 October 2008, 12:06 amМестами и safe int функции писали, да.
TSS:
2CEMEH:
21 October 2008, 10:17 amОтличная статейка.
highly professional scums » Blog Archive » Best tools noone uses: PREfast:
[...] про правильные практики, наконец заставил себя ознакомиться со словом PREfast и [...]
22 October 2008, 3:09 pmgauri:
спасибо, прочитал. с выколупыванием prefast в отдельный пакетик думаю его использовать в работе.
24 October 2008, 12:22 pmодно не радует — это синтаксис аннотирования параметров…