Принудительный выход из функции
Запускаем тестовую программу, набиваем в одном или нескольких окнах какой-нибудь текст, затем в меню "Help"
выбираем пункт "About TestCEdit"
и в появившемся диалоговом окне щелкаем по кнопке "make error". Опля! Программа выбрасывает критическую ошибку и, если мы нажмем на "ОК", все не сохраненные данные необратимо погибнут, что никак не входит в наши планы. Однако при наличии предварительно установленного отладчика мы еще можем кое-что предпринять. Пусть для определенности это будет Microsoft Visual Studio Debugger.
Нажимаем кнопку "Отменау" и отладчик немедленно дизассемблирует функцию, возбудившую исключение (см. листинг 3.5, приведенный ниже).[Y90] [n2k91]
Листинг 3.5. Отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение
0040135C push esi
0040135D mov esi,dword ptr [esp+8]
00401361 push edi
00401362 movsx edi,byte ptr [ecx+esi]
00401366 add eax,edi
00401368 inc ecx
00401369 cmp ecx,edx
0040136B jl 00401362
0040136D pop edi
0040136E pop esi
0040136F ret 8
Листинг 5 отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение
Проанализировав причину возникновения исключения (функции передан указатель на невыделенную память), мы приходим к выводу, что заставить функцию продолжить свою работу невозможно, поскольку структура передаваемых данных нам неизвестна. Приходится прибегать к принудительному возврату в материнскую функцию, не забыв при этом установить флаг ошибки, сигнализируя программе, что текущая операция не была выполнена. К сожалению, никаких общепринятых флагов ошибок не существует, и различные функции используют различные соглашения.
Чтобы выяснить, как обстоят дела в данном конкретном случае, мы должны дизассемблировать материнскую функцию и определить какой именно код ошибки она ожидает.
Переместив курсор в окно дампа, набьем в строке адреса название регистра указателя вершины стека –— "ESP" и нажмем на клавишу <Enter>. Содержимое стека тут же предстанет перед нашими глазами (листинг 3.6).:
Листинг 3.6. Поиск адреса возврата из текущей функции (выделен полужирным шрифтом)
0012F488 0012FA64 0012FA64 004012FF
0012F494 00000000 00000064 00403458
0012F4A0 FFFFFFFF 0012F4C4 6C291CEA
0012F4AC 00000019 00000000 6C32FAF0
0012F4B8 0012F4C0 0012FA64 01100059
0012F4C4 006403C2 002F5788 00000000
0012F4D0 00640301 77E16383 004C1E20
Листинг 6 поиск адреса возврата из текущей функции (выделен жирным шрифтом)
Первые два двойных слова соответствуют машинным командам POP EDI/POP ESI и не представляют для нас совершенно никакого интереса. А вот следующее двойное слово содержит адрес выхода в материнскую процедуру (в приведенном выше листинге 3.6 оно выделено полужирным шрифтом). Как раз его-то нам и надо!
Нажимаем <Ctrl>+<-D> и затем 0x4012FF, отладчик послушно отображает следующий дизассемблерный текст (листинг 3.7).:
Листинг 3.7. Дизассемблерный листинг материнской функции
004012FA call 00401350
004012FF cmp eax,0FFh
00401302 je 0040132D
00401304 push eax
00401305 lea eax, [esp+8]
00401309 push 405054h
0040130E push eax
0040130F call dword ptr ds:[4033B4h]
00401315 add esp, 0Ch
00401318 lea ecx, [esp+4]
0040131C push 0
0040131E push 0
00401320 push ecx
00401321 mov ecx, esi
00401323 call 00401BC4
00401328 pop esi
00401329 add esp,64h
0040132C ret
0040132C
0040132D push 0
0040132D ; эта ветка получает управление, если Функция 401350h вернет FFh
0040132F push 0
00401331 push 405048h
00401336 mov ecx,esi
00401338 call 00401BC4
0040133D pop esi
0040133E add esp,64h
00401341 ret
Листинг 7 дизассемблерный листинг материнской функции
Смотрите: если регистр EAX равен FFh, то материнская функция передает управление на ветку 40132Dh и спустя несколько машинных команд завершает свою работу, передавая бразды правления функции более высокого уровня. Напротив, если EAX != FFh, то его значение передается функции 4033B4h. Следовательно, мы можем предположить, что FFh –— это флаг ошибки и есть. Возвращаемся в подопытную функцию, нажав <Ctrl>+<-G> и "EIP", переходим в окно "Registers" и меняем значение регистра EAX на FFh.
Теперь необходимо найти подходящую точку возврата из функции. Просто перейти к машинной команде "RET" нельзя, поскольку перед выходом из функции следует в обязательном порядке сбалансировать стек или нас "выбросит" неизвестно куда и программа "обрушится" окончательно.
В общем случае число PUSH-команд должно в точности соответствовать количеству POP (также учитывайте, что PUSH DWORD X эквивалентен SUB ESP, 4, а POP DWORD X –—
ADD ESP, 4). Проанализировав дизассемблерный листинг функции, мы приходим к выводу, что для достижения "гармонии добра и зла" мы должны "стащить" с вершины стека два двойных слова, соответствующие машинным командам 40135С:PUSH ESI и 401361:PUSH EDI. Это достигается передачей управления по адресу 40136Dh, где "живут" дваа "добродушныхех" команды POP'a'а, приводящие стек в равновесное состояние.Подводим сюда курсор и уверенным щелчком правой клавиши мыши вызываем контекстное меню, среди пунктов которого выбираем "Set Next Statement". Как вариант можно перейти в окно регистров и изменить значение регистра EIP с 401362h на 40136Dh.
Нажатием клавиши <F5> мы заставляем процессор продолжить выполнение программы и… о чудо! Она действительно продолжает свою работу ("незлобное ругательство" на ошибку последней операции –— не в счет!). Несохраненные данные спасены!