Обновить
8K+
320
Николай Шлей@CodeRush

Firmware Security Engineer

28,1
Рейтинг
692
Подписчики
Отправить сообщение
Мне он не нравится тем, что я вынужден проходить цепочку из макросов и typedef'ов, чтобы понять, что это за зверь вообще и как он будет выглядеть из окна аппаратного отладчика. Т.е. одно дело, когда на тебя из этого окна смотрит целая переменная со значением 17, и совсем другое — когда это не целая переменная, а указатель на целую, или на функцию, что еще хуже.
Сам по себе typedef — это благо, а проблемы такого рода решаются грамотными IDE, но они от этого не перестают быть проблемами.
По мне тоже, но случается и вот такое, причем у всех, даже у самых гурушных гуру. У нас там все проще в UEFI — прошивка отрабатывает быстро и умирает быстро, а всю память, выделенную как EfiBootServicesCode или EfiBootServicesData освободит менеджер BDS, просто пометив ее свободной в UEFI MemMap, и ее потом перепишет ОС, когда ей понадобится. Т.к. мало кто в прошивке пытается выделить себе с кучи гигабайт-другой, то вся система довольно спокойно отностится к утечкам — в конце все равно останутся только пара рантайм-сервисов, для которых управление памятью уже написано и работает нормально.
Я тут рассматриваю не только язык сам по себе, но и все, что вокруг, поэтому директивы компилятора, разные #pragma и прочие __attribute__((declspec)) я тоже отношу к C, просто не к чему больше. Не спорю, опять же, что стандартизировать такое сложно, однако #pragma STDC ... как-то пробрались в C99, значит не все еще потеряно в этом смысле.

По поводу отношения соглашения о вызовах к указателям на функцию, простой пример: все компоненты UEFI находятся в одном адресном пространстве и общаются путем прямого вызова функций через таблицы указателей на них (такие таблицы называются «протоколами»). При этом, для архитектуры amd64 существует два распространенных соглашения о вызовах: Microsoft x64 (которое и используется в 64-битном UEFI и которое использует по умолчанию компилятор MSVC) и System V amd64 ABI, которое используют
*nix-системы и которое по умолчанию использует GCC. При этом компоненты UEFI не собираются одним и тем же компилятором, и зачастую поставляются в бинарном виде, поэтому очень важно, чтобы соглашение о вызовах было одинаковое, а это достигается добавлением EFIAPI ко всем typedef'ам всех указателей на функции, являющиеся частью протоколов, к прототипам и реализациям этих функций.
Пример кода
#include "Uefi.h"

// Define SIO DXE protocol GUID
#define EFI_CRSIO_DXE_PROTOCOL_GUID \
{ 0xc38bdbd, 0x6f6b, 0x49ae, 0x99, 0x5a, 0x1d, 0x6c, 0xc2, 0x9d, 0x95, 0xa1 }

// Extern the GUID for protocol users.
extern EFI_GUID gEfiCrSioDxeProtocolGuid;

// Forward reference for ANSI C compatibility
typedef struct _EFI_CRSIO_DXE_PROTOCOL EFI_CRSIO_DXE_PROTOCOL;

// Define each protocol interface function and the respective function pointer type 
typedef
UINT16
(EFIAPI *CRSIO_DXE_GET_INDEX) ( //Если вот здесь забыть EFIAPI, ничего не сломается, т.к. функция не имеет параметров. 
    VOID                        //А вот если бы они были, а компилятором оказался GCC, то он бы их положил в RDI, RSI...                         
);                              //а функция их ждет в RCX, RDX..., т.е. на вид все хорошо, а по факту в регистрах мусор вместо параметров

UINT16
EFIAPI // А если забыть его вот здесь, не забыв выше, будет почти то же самое, только упадет все в другом месте
CrSioGetIndex(
    VOID
);

// Define protocol data structure
typedef struct _EFI_CRSIO_DXE_PROTOCOL 
{
    CRSIO_DXE_GET_INDEX CrSioGetIndex;
} EFI_CRSIO_DXE_PROTOCOL;

Проблема то решена, но какой-то осадок неприятный все равно остается, т.к. четверть кода, который я пишу — макросы, обернутые в макросы, обернутые в typedef'ы, но альтернативы пока нет, спасибо, что не на ассемблере.
А что делать с обработкой ошибок, копировать все вызовы free() в каждый if (EFI_ERROR(Status)) или использовать goto fail, или может быть do { if (error_condition) break; } while(0), если у вас на goto аллергия?
Если бы простыми правилами можно было бы обойтись, память не текла бы у практически всех программ на С сложнее hello.c. Язык не виноват, но люди устроены именно так.
Все хорошо, да. А теперь то же самое, но для функции, которая сама указатель на другую функцию принимает, как qsort из стандартной библиотеки. Необходимость в typedef сразу же указывает на проблему, а если его не использовать, получится дикая смесь из скобок, запятых и имен типов, которую невозможно ни толком прочитать, ни толком понять.
Плюс еще нужно о соглашении о вызовах не забыть, если предполагается, что программа будет использоваться на голом железе, причем ключевое слово для него не стандартизировано и потому выглядит как макрос EFIAPI, который раскрывается либо в "", либо в __cdecl, либо в __attribute__((cdecl)), либо еще в какой-нибудь __кошмар.
Честно сказать, я не знаю, как нужно было сделать лучше. Но тот факт, что указатели на функции в их нынешнем виде мало кто понимает — вижу на работе стабильно раз в месяц.
Ну что сказать, это личный кактус отдельного человека, и он готов его кушать.
Я пишу на C потому, что именно на нем весь UEFI и написан, но я с удовольствием перешёл бы на Rust прямо завтра, если бы такая возможность была, т.к. от некоторых особенностей C уже мутит.
Синтаксис указателей на функции понимают два с половиной специалиста, молчаливое приведение типов приводит к очень хитрым ошибкам, от которых не спасает даже статический анализатор, практически все функции для работы с форматной строкой — одна большая дыра в безопасности, забытый volatile моежт обрушить цивилизацию в самом неожиданном месте, а макросами можно отстрелить себе не то, что ногу, а вообще все и несколько раз. Добавим сюда ручное управление памятью, которое почти никто не умеет делать правильно, буфера постоянного размера на стеке, и великий и ужасный NULL — изобретение на минус миллиард долларов. Несмотря на все это, С — прекрасный кроссплатформенный ЯП низкого уровня, и на низком уровне ему альтернатив практически нет. Подходит ли он для игр — надо спросить у автора, если он когда-нибудь вылезет из отладки.
У всех современных процессоров очень длинные списки Errata, т.к. прцоессоры имеют запредельную сложность, и я не без оснований подозреваю, что современный микропроцессор — вообще самая сложная вещь, созданная человеком.
На некоторых чипах до трети всех возможностей процессора не используется, т.к. их реализации нашлись ошибки, которые нецелесообразно (т.е. слишком дорого) исправлять в текущем степпинге или текущей линейке, поэтому все больше вещей стараются делать программно — не только через микрокод, но и через код ME, прошивки Sensor Hub'а, PSP, SMU и тому подобных.
В данный момент для всех систем с поддержкой FIT (Haswell и более новые) микрокод загружается процессором еще до передачи управления на ResetVector, т.е. нельзя сказать, что его загружает firmware, она на тот момент даже не запущена еще. Затем можно загрузить другую версию микрокода, но у некоторых версий установлен флаг, запрещающий загрузку предыдущих версий на более поздних этапах.
Чтобы лучше понимать, как все работает на самом деле, во что компилируется switch и почему именно в это. Эти знания помогут потом в отладке сложных проблем, за решением которых понадобиться опуститься до уровня машинного кода и прямого взаимодействия с железом. Если вы думате, что никогда не столкнетесь с такими проблемами — это только потому, что с вашими системами до вас работала куча людей, столкнулась с ними, и сделала все, чтобы вам это было не нужно. Ну и прочтите вот эту статью, станет немного понятнее.
//@xvilka, где моя Apply structure offset? Еще пару месяцев подожду и пойду хакать её сам. :)
По теме — отличная статья и ссылки, большое спасибо.
Есть такая информация неподтвержденная, что нормальные IPS-экраны 16:10 просто выкуплены компанией Apple, и все остальные либо выдумывают другие отношения сторон, либо бреются.
Это если он просто карту отключает, а не встает с сообщением «Incompatible mPCIe device found. Replace it and try again» и отказывается проходить POST. Мой старенький Lenovo x121e вел себя именно так.
BIOS без WhiteList можно попросить на bios-mods.com или forums.mydigitallife.com, чаще всего помогают.
Запускать WinPhlash из Wine лучше даже не пробовать — может произойти все, что угодно. Скорее всего, ничего страшного не будет, просто драйвер режима ядра не загрузится, но лучше не рисковать.
Скорее всего, его там нет аппаратно, просто не припаян. Самый простой мод в таком случае — найти и припаять, плюс нпйти стреп-резистор, который говорит прошивке не показывать TPM в меню (в случае, если прошивки для ноутов с TPM и без него не разные).
А вы описываете какую-то «страну эльфов». В 21 веке файлы как были реальными, так и не перестали, сколько их за фасадом интерфейса не прячь. Нет никаких «документов», есть поток байт, который каждая программа интерпретирует по своему, и есть файловая система, на которой этот поток байт хранится отдельно от других. Именно от того, что Apple пытается спрятать от меня тот факт, что мой смартфон или планшет — это такой же точно компьютер с архитектурой ARM, как и те, с которыми я работаю постоянно, я и не смог пользоваться первым iPad (вернул в магазин через 10 дней) и до сих пор не могу пользоваться iPhone.
Терпеть не могу, когда пользователя держат за идиота, и когда производитель думает, что он лучше знает, как пользователю жить, как ему работать, как слушать музыку и как обмениваться файлами. Стойло какое-то, право-слово.
Будем посмотреть. Телевиденье — это не совсем то, о чем я тут пытаюсь сказать, я про промышленность и системы управления, вот там в 2018 году будет то же самое, что и сейчас — QNX, VxWorks, Windows Embedded Compact и прочее вот такое, загружающееся в legacy mode через CSM, который не перестанут поддерживать еще минимум лет 10.
Тут проверяют по VID/DID, и потому нужно либо выпатчить саму проверку, либо добавить еще родну пару ID в таблицу.
Как только там сделают нормальную прошивку — почему нет, железо то далеко не самое плохое, а для своего общего уровня качества — еще и очень дешевое. ОСХ меня не пугает, поставлю рядом такую ОС, какая понадобится, клавиатура проблемная, но на бегу я не программирую, а дома или на работе у меня все равно 3 монитора и KVM с нормальной мышью и клавиатурой.
Меня не интересуют причины, только конечный результат — я не могу купить ПК компании Apple и зашифровать на нем жесткий диск так, чтобы его нельзя было расшифорвать путем кражи пароля вредоносным агентом в прошивке, т.к. таких ПК нет. Т.е. заниматься чем-то минимально секретным на машинах Apple — невозможно, о чем, кстати, говорил Мэтью Гаррет на недавно прошедшем 32с3.
Видя, что ситуация патовая, и надо что-то менять, Apple пару месяцев назад купила security-стартап LegbaCore, и теперь у них есть два специалиста по безопасности прошивок, т.е. ситуация должна измениться в ближайшем будущем, но сейчас все так как сейчас — на уровне 2007 года.
Только вот для 9/10 России первый канал по прежнему аналоговый, и так будет еще лет 20, приблизительно. Плюс какой-нибудь ткацкий или токарно-фрезерный станок с ЧПУ неожиданно цифровым не станет никогда — он цифровым и был, и заниматься апгрейдом ОС на нем никто не будет, а вот встроенный в него компьютер надо иногда менять — и замена должна пройти незаметно для всего стального станка, даже если на самом деле Pentium 2 в итоге заменили на Skylake.
Сочувствовать не надо, посочувствуйте, пожалуйта, владельцам кучи дорогого железа с интерфейсом FireWire, которые к новым изделиям Apple, даже к iMac и Mac Pro, уже без переходников не подключить. А нет там этих интерфейсов не потому, что некуда поставить копеечный контролер, а потому, что левая пятка менеджмента компании так решила. Feel the difference.

Информация

В рейтинге
301-й
Дата рождения
Зарегистрирован
Активность

Специализация

Инженер встраиваемых систем, Системный инженер
Ведущий