DWORD nCode = ::GetLastError();
char buf[256];
sprintf("LastError=%lu", nCode);
::OutputDebugStringA(buf);
Делалось это с целью поймать ошибку, появляющуюся, когда функция API выдает ошибку, а код ее игнорирует и продолжает дальше работать с невалидными данными.
Я тут же дал совет не портить нервы и код, а использовать псевдорегистры и условные точки останова.
Выяснилось, что человек этого понятия не знает, а после разговора с другими людьми я понял, что эта техника популярностью почему-то не пользуется.
Итак, что такое псевдорегистры?
На самом деле, это не более чем удобная для программиста метафора - "они" выглядят как регистры, но на самом деле это просто средство (внутри реализуемое как подпрограммы) получить дополнительную информацию (коды ошибок, адрес TIB и т.д.) в отладчике.
Обладая тем же синтаксисом, что и обычные аппаратные регистры, они могут участвовать в любых арифметических операциях в окне Watch.
Например, если добавить @ERR,hr в окно Watch, то в любой момент можно увидеть расшифрованное значение GetLastError().
Псевдоренистры также могут участвовать в задании условия для точек останова, что серьезно облегчает отладку.
@ERR
Наиболее часто используемый при отладке псевдорегистр, содержит значение результата вызова GetLastError() в контексте активного потока.
@TIB
Кроме @ERR существует не менее важный псевдорегистр @TIB. Это адрес thread information block, который крайне удобно использовать при многопоточной отладке.
Например, если какая-то функция вызывается из разных потоков, то очень легко привязать точку останова к нужному потоку, добавив на нее условие, например @TIB==0x5ffa3000. После этого выполнение программы начнет прерываться только для заданного потока.
Значение, с которым сравнивается @TIB можно посмотреть в окне Watch, введя @TIB во время первого прерывания.
Извращенные применения псевдорегистров
Так случилось, что трассироваться было некогда, а подозрение на то, что проблема получается из-за непроверенного значения GetLastError() уже было устойчивым.
В этом случае помогла достаточно простая техника (благо - ошибка могла возникать только в главном потоке приложения).
Смотрим на код GetLastError() (MSVC 2003):
_RtlGetLastWin32Error@0:
7C90FE21 mov eax,dword ptr fs:[00000018h]
7C90FE27 mov eax,dword ptr [eax+34h]
7C90FE2A ret
В окне Watch выводим два значения - @ERR,x и *(unsigned long*)(@TIB+0x34),x.
Убеждаемся, что они совпадают (это правильно ;-) ), далее (@TIB + 0x34) заменяем на число, которое можно получить в том же окне Watch, введя туда эту строку (мы в данный момент как раз и находимся в контексте главного потока).
Добавляем новую точку останова через Ctrl-B, переключаемся в появившемся диалоге на вкладку Data и вводим условие останова - *((unsigned long*)(0x7ffdf000 + 0x34)), поле Context очищаем.
Запускаем программу... и... через пять не имеющих отношения к делу ошибок.... Вуаля!
@__security_check_cookie@4:
7C8097AA cmp ecx,dword ptr [___security_cookie (7C8856CCh)]
7C8097B0 jne ___report_gsfailure (7C870E1Ch)
7C8097B6 test ecx,0FFFF0000h
7C8097BC jne ___report_gsfailure (7C870E1Ch)
7C8097C2 ret
7C8097C3 mov dword ptr [edi+34h],esi
7C8097C6 jmp _SetLastError@4+2Ch (7C809386h)
Stack:
> kernel32.dll!_SetLastError@4() + 0x468
kernel32.dll!_BaseSetLastNTError@4() + 0x17
kernel32.dll!_CreateFileW@28() + 0x93a
kernel32.dll!_CreateFileA@28() + 0x2b
DataTool_sec.exe!_sopen(const char * path=0x004250c8, int oflag=32768, int shflag=64, ...) Line 387 + 0x20 C
DataTool_sec.exe!_openfile(const char * filename=0x004250c8, const char * mode=0x004250ce, int shflag=64, _iobuf * str=0x00428da8) Line 190 + 0x16 C
DataTool_sec.exe!_fsopen(const char * file=0x004250c8, const char * mode=0x004250cc, int shflag=64) Line 75 + 0x15 C
DataTool_sec.exe!fopen(const char * file=0x004250c8, const char * mode=0x004250cc) Line 116 + 0xf C
И ниже появилась точка сбоя.
Время на поиск ошибки - полторы минуты, этот пост я набираю существенно дольше...
Впрочем, убирали ненужные контрольные печати в Araxis'e еще дольше (простой revert отбросил бы и сделанные осмысленные изменения).
Далее - просто для справки, основные псевдорегистры, доступные разработчику.
Список основных псевдорегистров
Pseudoregister | Description |
@ERR | Last error value; the same value returned by the GetLastError() API function |
@TIB | Thread information block for the current thread; necessary because the debugger doesn't handle the "FS:0" format |
@CLK | Undocumented clock register; usable only in the Watch window |
@EAX, @EBX, @ECX, @EDX, @ESI, @EDI, @EIP, @ESP, @EBP, @EFL | Intel CPU registers |
@CS, @DS, @ES, @SS, @FS, @GS | Intel CPU segment registers |
@ST0, @ST1, @ST2, @ST3, @ST4, @ST5, @ST6, @ST7 | Intel CPU floating-point registers |
Врочем, сознаюсь честно, регистры сопроцессора мне как-то при отладке применять еще не приходилось....
Резюме - чтение Робинсона "Отладка Windows приложений" очень помогает, хотя книжка уже и старая...
Комментариев нет:
Отправить комментарий