Зіставлення PDB та виконуваних файлів

Як би добре не працював відділ тестування, все ж таки часто продукт йде користувачеві з неабиякою кількістю помилок. Залежно від продукту, боротьба з цими помилками ведеться по-різному: ведення журналу операцій, взяття у користувача проблемного проекту, виїзд до користувача і т.д.

Для боротьби з критичними помилками, що призводять до вильоту, у моїй компанії використовується механізм автоматичного зняття дампи пам'яті та відправки нам на сервер. Кожен дамп піддається вивченню і основі інформації отриманої з дампа приймаються подальші рішення.

Однак для того, щоб проаналізувати дамп найменшими силами, необхідні файли налагоджувальної інформації, зокрема PDB-файли, що здійснюють зворотний зв'язок від виконуваного файлу до вихідників. У загальному вигляді PDB файли в моїй компанії зберігаються у відведеній і доступне всім місце.

Однак не так давно сталося так, що файли PDB від випущеної версії були випадково видалені. Про те, як я відновлював налагоджувальну інформацію, я і хочу розповісти.

Перше що необхідно було зробити це зібрати версію з тих же вихідників, що не стало проблемою (назвемо цю збірку PDB_сборкой). В результаті я отримав PDB файли приблизно того ж змісту. Приблизно тому, що бінарно вони не ідентичні. Гарантію того, що вони будуть коректні для складання у користувача так само немає, проте досвід налагодження підказує, що більшість проблем за допомогою отриманих PDB можна вирішити.

Далі мені потрібно було знайти спосіб обдурити відладчик Visual Studio і змусити завантажувати ці PDB (для чесності треба сказати що WinDbg може завантажувати практично будь-куди. Для цього треба виконати команду .symopt+0x40). Проблема тут полягає в тому, що для кожного складання генерується GUID, який зашивається вглибини всіх виконуваних та налагоджувальних файлів. Також студія при завантаженні перевіряє позначку часу останнього запису у файл, який має бути ідентичним бінарному файлу.

Пошук в інтернеті мене навів на утиліту ChkMach, яка дозволяє підмінити GUID.

Насправді виявилося, що утиліта не змогла впоратися з усіма моїми PDB файлами (якщо PDB файл розміром більше приблизно 70 метрів утиліта відмовляється його перепрошувати).

Вирішили написати щось своє, що дозволило б зробити потрібні махінації.

Алгоритм вийшов приблизно наступний: 1) Завантажити виконуваний файл зі складання користувачеві, що пішла, і отримати з нього GUID. Цей GUID треба буде записати в PDB з PDB_складання. 2) Завантажити PDB файл з PDB_складання. 3) Отримати GUID зашитий у PDB. 4) Знайти всі входження отриманого GUID у PDB та замінити їх на GUID отриманий у пункті 1.

Я знайшов два способи отримати GUID з файлу, що виконується. Перший спосіб – це використання інтерфейсів IDia. У Visual Studio (%VSINSTALLDIR%\DIA SDK\Samples\) є приклад використання. У цього способу є проблема. Якщо для файлу, що завантажується, не знайдено PDB-файл, то виконуваний файл завантажений не буде.

Що стосується пунктів 2 і 3, виявилося їх дуже легко вирішити, скориставшись інтерфейсами IDia.

bool LoadDataFromPdb(const wchar_t *pdbFileName, IDiaDataSource **ppSource, IDiaSession **ppSession, IDiaSymbol **ppGlobal, GUID * guid)

hr = CoCreateInstance(__uuidof(DiaSource), NULL, CLSCTX_INPROC_SERVER, __uuidof(IDiaDataSource), (void**) ppSource);

if (FAILED(hr)) wprintf(L"CoCreateInstance failed - HRESULT = %08X\n", hr);

if (FAILED(hr)) wprintf(L"loadDataFromPdb failed - HRESULT = %08X\n", hr); returnfalse; >

if (FAILED(hr)) wprintf(L"openSession failed - HRESULT = %08X\n", hr); return false; >

Пошук та заміна GUID у файлі, я думаю є, досить простим завданням, і описувати її я не буду.

Однак варто зупинитися на одній дрібниці.

Оголошення структури GUID виглядає так.

typedef struct _GUID unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[8]; > GUID;

З огляду на те, що у кінцевому вигляді цей GUID передбачалося писати як масив байтів, необхідно було врахувати порядок байт, і як наслідок розгорнути поля Data1, Data2 і Data3

char getByteFromShort(short l, int pos) _ASSERTE(pos = 0);

std::vector transformGuid(const GUID & guid) std::vector v; v.push_back(getByteFromLong(guid.Data1, 0)); v.push_back(getByteFromLong(guid.Data1, 1)); v.push_back(getByteFromLong(guid.Data1, 2)); v.push_back(getByteFromLong(guid.Data1, 3));

v.push_back(getByteFromShort(guid.Data2, 0)); v.push_back(getByteFromShort(guid.Data2, 1));

v.push_back(getByteFromShort(guid.Data3, 0)); v.push_back(getByteFromShort(guid.Data3, 1));

А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»

Написав гарний текст – отримав запрошення.

У «Пісочниці» діє премодерація: перед публікацією всі матеріали проходять через дбайливе проміння НЛО.

З великою ймовірністю не пройдуть премодерацію: