Семен (sim0nsays) wrote,
Семен
sim0nsays

На что был похож код в 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-зеалотам - пламенные приветы.
Crossposted to blog.gamedeff.com
Tags: graphics, tech
Subscribe
  • Post a new comment

    Error

    default userpic
    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 114 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →