Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2007.04.08;
Скачать: CL | DM;

Вниз

Трассировка exception в Delphi   Найти похожие ветки 

 
Alex Konshin ©   (2007-02-27 15:53) [0]

Я тут в одном (коммерческом) проекте реализовал печать стека вызовов при exception.
После Java отсутствие распечатки стека вызовов сильно раздражает и
Я знаю, что это уже есть в JCL, но там это сделано как-то слишком лохмато - код имеет кучу зависимостей от других модулей JCL, совершено ненужных мне вариантов отладочной информации, и т.п.. Причем это невозможно разделить на куски и использовать только то, что нужно.
Короче, не буду врать, что я не заглядывал в их код, но все-таки я написал свои модули, которые более обозримы, и, самое главное, работают. У меня сделан отдельно парсер *.map, отдельно структура и функции работы с картой памяти, отдельно модуль с хуком на exception и функции раскрутки фреймов стека.

Понятное дело, что так красиво, как в Java не получится. Нужно понимать где и когда Delphi создает фреймы. Если скомпилировано с включеным debug, то можно напечатать имена модулей и номера строк. Если же нет, то хотя бы смещения от начала модулей. Понятно, что в release версии уже номеров строк не будет. Но все равно эти модули уже оказались ислючительно полезны для бета-тестирования. Например, недавно в логах были обнаружены Access violation exception - всем знакомые ужасно противные исключения, и все известно, как тяжело искать причину в таком случае, особенно если учесть, что у меня многопоточное приложение. А тут я сразу нахожу конкретное место в коде, а также цепочку вызовов (ну не совсем так, но откуда пришли понять можно):
20070203 01:07:27.437 EAccessViolation: Access violation at address 00403EF8 in module "InsiderWinMonitor.exe". Read of address FFFFFFFC
20070203 01:07:27.437 00403EF8: System offset=2EF8
20070203 01:07:27.437 00423B3D: NetThreads.pas,255
20070203 01:07:27.437 00423BDA: NetThreads.pas,266
20070203 01:07:27.453 004048B0: System._ExceptionHandler

Короче, я тут так вкусно рассказываю, что у некоторых наверняка потекли слюньки :). Добавим ложку дегтя, чтоб жизнь медом не казалась :). Эти модули я писал для себя и потому не могу гарантировать, что это будет также замечательно работать для вас. Это также означает, что исходники поставляются как есть и ничего кроме них. Для человека дружного со своей головой этого достаточно, а другим этого и не надо :)

В самом простом случае, чтобы это все заработало, нужно просто в самом начале вызвать  
 DebugUtils.HookException(PrintToConsoleAndODS);
Или что-то в этом духе (там несколько вариантов).
Простой случай это когда у вас только один executable и нет своих DLL/BPL, которые хочется отслеживать. Иначе нужно для каждой DLL/BPL вызвать парсер *.map. На самом деле мне лично это было ненужно и потому я это не тестировал. Возможно, что нужно будет немного доработать напильником. У меня предполагается, что реальный адрес загрузки DLL/BPL совпадает с адресом загрузки установленым при компиляции, что может быть неправдой. Это должно быть несложно доделать, если это нужно.

Да, чуть не забыл самое главное.  Я положил это свой сайт
http://home.earthlink.net/~akonshin/files/DebugUtils.zip

Поддержку не гарантирую - я в последнее время сильно занят, а спать иногда хочется. Сейчас вот только выкроил время для души - отдыхаю :)
Но уже 8 утра, пора и спать.


 
oldman ©   (2007-02-27 16:22) [1]


> Alex Konshin ©   (27.02.07 15:53)  
> ...skipped...
> Но уже 8 утра, пора и спать.


Ни фига у тебя глюки!!!


 
Alex Konshin ©   (2007-02-27 16:25) [2]

> oldman ©   (27.02.07 16:22) [1]
> > Alex Konshin ©   (27.02.07 15:53)  
> > ...skipped...
> > Но уже 8 утра, пора и спать.
> Ни фига у тебя глюки!!!

В смысле? Сейчас у меня 8:19 утра.
Вот PartitionMagic завершится и я пойду спать.


 
alien1769 ©   (2007-02-27 16:29) [3]


> oldman ©   (27.02.07 16:22) [1]
>
> > Alex Konshin ©   (27.02.07 15:53)  
> > ...skipped...
> > Но уже 8 утра, пора и спать.
>
>
> Ни фига у тебя глюки!!!


По местному времени.


 
oldman ©   (2007-02-27 16:33) [4]


> alien1769 ©   (27.02.07 16:29) [3]


> Alex Konshin ©   (27.02.07 16:25) [2]

Да я не про время :)))


 
Ketmar ©   (2007-02-27 16:55) [5]

интересно. забрал. почитаю на досуге. %-)

tnx. а то из JCL я когда-то начал выковыривать, но понял, что задача по плечу лишь больным на голову титанам. %-)


 
oldman ©   (2007-02-27 16:56) [6]


> Ketmar ©   (27.02.07 16:55) [5]
> задача по плечу лишь больным на голову титанам.


Она, кстати, только им и нужна...


 
Ketmar ©   (2007-02-27 17:00) [7]

> oldman ©   (27.02.07 16:56) [6]
ни фига подобного. я, например, вменяемый способ отлаживать некий сервис так и не нашёл. только логами и дампами. это если ты про саму идею подобных дампов.


 
novill ©   (2007-02-27 17:07) [8]

Штука в общем занятная, но вот много ли дадут адреса вызовов?


 
Alex Konshin ©   (2007-02-27 17:10) [9]

Ну у меня, собственно, тоже сервис.
Вообще я очень редко отлаживаюсь в Delphi IDE, да и чаще всего это просто невозможно. А многопоточные приложения так вообще отлаживать тяжко. Кроме как ODS практически никаких вариантов.


 
Alex Konshin ©   (2007-02-27 17:12) [10]

> novill ©   (27.02.07 17:07) [8]
> Штука в общем занятная, но вот много ли дадут адреса вызовов?

А ты приглядись. Там не только адреса, а имена исходных файлов и номера строк в них.


 
Alex Konshin ©   (2007-02-27 17:22) [11]

Вот еще пример из лога:
20070209 08:28:19.359 EStructuredStreamClosed: Conection has been closed
20070209 08:28:19.375 0041D76C: StructuredStreams.pas,568
20070209 08:28:19.375 0041DA25: StructuredStreams.pas,701
20070209 08:28:19.375 0041DAAC: StructuredStreams.pas,713
20070209 08:28:19.390 0041E2AE: StructuredStreams.pas,919
20070209 08:28:19.390 0041E2CA: StructuredStreams.pas,921
20070209 08:28:19.390 004243AB: NetThreads.pas,437
20070209 08:28:19.390 00424435: NetThreads.pas,461
20070209 08:28:19.390 0042447B: NetThreads.pas,464
20070209 08:28:19.390 00423A9E: NetThreads.pas,243
20070209 08:28:19.390 00423BDA: NetThreads.pas,266
20070209 08:28:19.406 004048B0: System._ExceptionHandler


 
atruhin ©   (2007-02-27 17:24) [12]

Хорошая вещь! Давно хотел найти, но ставить JCL только из за глупо.


 
novill ©   (2007-02-27 17:28) [13]

> А ты приглядись.

У меня вот такую штуку выдает:

EInvalidOp: Invalid floating point operation
00457117: Unknown module
00433724: TestProject1.exe+032724
00433745: TestProject1.exe+032745
00433724: TestProject1.exe+032724
00433745: TestProject1.exe+032745
0044F516: TestProject1.exe+04E516
0044F53F: TestProject1.exe+04E53F
00403EEC: TestProject1.exe+2EEC


Можешь подсказать, что там еще доработать надо?


 
Alex Konshin ©   (2007-02-27 17:30) [14]

Ты компилил с генерацией *.map файла?


 
Джо ©   (2007-02-27 17:30) [15]

> [13] novill ©   (27.02.07 17:28)
> > А ты приглядись.
>
> У меня вот такую штуку выдает:
>
> EInvalidOp: Invalid floating point operation
> 00457117: Unknown module
> 00433724: TestProject1.exe+032724
> 00433745: TestProject1.exe+032745
> 00433724: TestProject1.exe+032724
> 00433745: TestProject1.exe+032745
> 0044F516: TestProject1.exe+04E516
> 0044F53F: TestProject1.exe+04E53F
> 00403EEC: TestProject1.exe+2EEC
>
>
> Можешь подсказать, что там еще доработать надо?

Думаю, что необходимо включить генерацию map-файлов.


 
Alex Konshin ©   (2007-02-27 17:31) [16]

Сразу видно, что голубой значок :)


 
Piter ©   (2007-02-27 17:33) [17]

а можно для ламеров поподробнее, что это такое означает "распечатка стека вызовов"?

Какие функции в каком порядке вызывались? Дык этих вызовов тысячи будут!

Или объясните на примере, я не догоняю :(

И вот например:

> 20070209 08:28:19.390 0042447B: NetThreads.pas,464

что это такое? Ну дата понятна... Что за адрес 0042447B? Что произошло в строчке 464 модуля NetThreads.pas?


 
oldman ©   (2007-02-27 17:34) [18]


> Alex Konshin ©   (27.02.07 15:53)  


Извини, не допер сначала.

Вещь хорошая, но мне (пока) не нужна...


 
Джо ©   (2007-02-27 17:35) [19]

> [16] Alex Konshin ©   (27.02.07 17:31)
> Сразу видно, что голубой значок :)

Ну дык, остро отточенный телепатор вручили вместе со значком :)


 
Alex Konshin ©   (2007-02-27 17:50) [20]

> Piter ©   (27.02.07 17:33) [17]
> а можно для ламеров поподробнее, что это такое означает
> "распечатка стека вызовов"?
> Какие функции в каком порядке вызывались? Дык этих вызовов
> тысячи будут!

Что такое стек? Что значит вызов функции/метода из другого метода?
Могут быть и тысячи, но маловероятно. Тысячи могут быть, если у тебя рекурсия, а так - обычно не больше десятка.

Но на самом деле тут печатаются строки, где создавались стек фреймы. А Delphi их создает не во всех функциях/методах, но зато на всех try ... finally/except. Создание фреймов тоже регулируется опциями компиляции.

Короче, этой информации обычно достаточно для понимания где произшло exception и откуда мы туда пришли.


 
novill ©   (2007-02-27 17:51) [21]

> [16] Alex Konshin ©   (27.02.07 17:31)


EMapParserException: Table of publics by value is not found
0040A8EA: TestProject2.exe+98EA
0040AB16: TestProject2.exe+9B16
0040A55A: TestProject2.exe+955A
0040A587: TestProject2.exe+9587
00403DB0: TestProject2.exe+2DB0
Exception EMapParserException in module TestProject2.exe at 0000A8EA.
Table of publics by value is not found.


Похоже просто парсилку придется "доработать напильником"


 
Суслик ©   (2007-02-27 18:19) [22]


>  [20] Alex Konshin ©   (27.02.07 17:50)

Скажи, у тебя по функционалу также сделано как в JEDI или все-таки нет?

Вопрос вот в чем: когда я пользуюсь JEDI я иногда в стеке замечаню методы, в которые вообще вход быть не доолжен. Типа такого:

function C(): ...
begin
end;
proceure B(...)
begin
  raise ...
end;
procedure A()
begin
  B(C());
end;


В этом (или каком-то очень похожем - закономерности я все-таки не понял до конца) случае в стеке быдет типа
A
C
B
т.е. как будто в ходе работы был вызов С. Он то был, но и возврат из него уже был.

Собственно вопрос - эта особенность нестранима, или я что-то не понимаю.


 
GrayFace ©   (2007-02-27 18:41) [23]

Спасибо, посмотрю. Сам хотел такое делать и даже чуть-чуть сделал.


 
Ketmar ©   (2007-02-27 18:43) [24]

> oldman ©   (27.02.07 17:34) [18]
счастливый...


 
Сало ©   (2007-02-27 18:46) [25]

Не знаю, втянул из JCL 6 модулей, да и работает себе
JclDebug, JclHookExcept, JclPeImage, JclTD32, JclBase и JclFileUtils.


 
Piter ©   (2007-02-27 19:10) [26]

Alex Konshin ©   (27.02.07 17:50) [20]
Что такое стек? Что значит вызов функции/метода из другого метода?
Могут быть и тысячи, но маловероятно. Тысячи могут быть, если у тебя рекурсия, а так - обычно не больше десятка.


а, все, понял. В общем, программка печатает стек. Но ведь далеко не все функции помещают аргументы в стек, верно? Если у функции, например, один параметр integer, так он вроде сразу в регистр будет запихнут?

Да и вообще... В стеке ведь хранятся аргументы для функций, как можно определить к какой функции что относится... :)


 
Ketmar ©   (2007-02-27 19:33) [27]

> Piter ©   (27.02.07 19:10) [26]
эх. ну не качай ты эту муть. явно она тебе ещё не нужна...


 
Piter ©   (2007-02-27 20:28) [28]

Ketmar ©   (27.02.07 19:33) [27]

бла бла бла :)


 
Ketmar ©   (2007-02-27 20:46) [29]

> Piter ©   (27.02.07 20:28) [28]
и я о том же. не понимашешь, что и зачем -- не бери. не стоят у тебя, значит, такие задачи, где подобная штучка сильно облегчит жизнь.


 
Piter ©   (2007-02-27 21:06) [30]

ну как можно узнать нужна ли тебе вещь, если ты вещью никогда не пользовался?

По твоей логике, если вещь ты никогда не использовал - значит, она тебе не нужна. Оригинально, но немного бредово ;)


 
Ketmar ©   (2007-02-27 21:12) [31]

> Piter ©   (27.02.07 21:06) [30]
ты без этого жил? жил. значит -- не нужна. была бы нужна -- ты бы искал нечто подобное и сразу понял бы, о чём речь. ничего бредового не вижу.


 
Kerk ©   (2007-02-27 22:25) [32]

> [31] Ketmar ©   (27.02.07 21:12)

Не спорь ты с ним. Он ведь правда похоже считает, что суть в передаваемых в функции аргументах :)


 
Игорь Шевченко ©   (2007-02-27 22:56) [33]

Пару копеек - не получилось вывести трассировку в VCL Forms (пробовал через DebugUtils.HookException(ODS) и запуском DbgView от SysInternals;
В консольном приложении все прекрасно вышло.


 
Суслик ©   (2007-02-27 23:25) [34]


> Игорь Шевченко ©   (27.02.07 22:56) [33]

моя копейка (может не в тему).
борланд не предоставляет возможность полного мэпа для vcl и rtl - сам не знаю о чем говорю, но это говорит JEDI.


 
ZeroDivide ©   (2007-02-28 00:16) [35]

Alex Konshin,

20070209 08:28:19.375 0041D76C: StructuredStreams.pas,568
20070209 08:28:19.375 0041DA25: StructuredStreams.pas,701
20070209 08:28:19.375 0041DAAC: StructuredStreams.pas,713
20070209 08:28:19.390 0041E2AE: StructuredStreams.pas,919
20070209 08:28:19.390 0041E2CA: StructuredStreams.pas,921
20070209 08:28:19.390 004243AB: NetThreads.pas,437
20070209 08:28:19.390 00424435: NetThreads.pas,461
20070209 08:28:19.390 0042447B: NetThreads.pas,464
20070209 08:28:19.390 00423A9E: NetThreads.pas,243
20070209 08:28:19.390 00423BDA: NetThreads.pas,266

Лучше еще сразу имя функции выводить. А то так не очень красиво. Было бы имя функции, тогда в некоторых случаях можно было бы определить ошибку еще до заглядывания в код.

У меня написана полностью своя трассировка стека и багрепорт, который автоматом отсылается мне на мыло, в случае unhandled exception содержит: место ошибки в виде модуль, метод, строка. Затем трассировку вызовов + если ошибка была в при открытии датасета, то его SQL + Скриншот экрана юзера + еще некоторую системную информацию.

Вообще-то, рулез фарева.... без такой фигни как без рук, только вот безопасность несколько хромает (в смысле при включении map-файла в дистрибутив, в каком-либо виде), но если для приложения это не так важно, то вполне юзабельно.


 
Alex Konshin ©   (2007-02-28 02:34) [36]

> ZeroDivide ©   (28.02.07 00:16) [35]

Проблема в том, что в *.map попадают далеко не все имена функций. Поэтому в распечатку могут попасть функции, которые в памяти перед нужной функции,
что еще более некрасиво.
Но если есть желание, то можешь писать имена функций. Необходимая функциональность реализована. В тех примерах из лога это даже видно - в *.map нет информации о строках из System, но зато есть адреса функций оттуда, поэтому они и попадают в распечатку.
Я уже сказал, что на мой взгляд мой код более обозримый и человек с головой сможет подстроить его под свой вкус. C JCL кодом это было нереально - я безуспешно пробовал несколько раз, слишком много лишнего.

Еще одно замечание.
У меня реализован парсер *.map файлов, причем отдельно и без левых зависимостей. С таким же успехом можно довольно просто реализовать парсер TD32 информации или подключение карты из ресурсов/файлов и т.п.. Обратите внимание, что формат карты специально сделан таким, что он допускает загрузку-выгрузку ее в бинарном виде одним куском. То есть карта не содержит абсолютных ссылок, только смещения. Также не должно быть проблемой загрузить несколько карт. Мне это не нужно было, но я писал свой код имея в виду такую возможность.

То есть, по сути можно сделать любую функциональность, которая есть в JCL, при этом не захламляя лишним кодом. Считайте это набором "Сделай сам". На мой взгляд вполне простой в использовании набор :)


 
Alex Konshin ©   (2007-02-28 02:42) [37]

> Игорь Шевченко ©   (27.02.07 22:56) [33]
> Пару копеек - не получилось вывести трассировку в VCL Forms
> (пробовал через DebugUtils.HookException(ODS) и запуском
> DbgView от SysInternals;
> В консольном приложении все прекрасно вышло.

А что хоть происходит?
Ну у меня это сделано в сервисе, так что я с формами не тестировал, всякое может быть. Но по идее должно работать.
Ты включи отладку самого стектрейсера (в defines.inc), возможно поймешь в чем дело. Я на вскидку, конечно, не знаю в чем дело, но возможно, что трейсер не может найти конец место, где надо остановится раскручивать. Хотя, черт его знает что там у тебя случилось - ты же ничего конкретного не сообщил.


 
Alex Konshin ©   (2007-02-28 02:53) [38]

> Сало ©   (27.02.07 18:46) [25]
> Не знаю, втянул из JCL 6 модулей, да и работает себе
> JclDebug, JclHookExcept, JclPeImage, JclTD32, JclBase и
> JclFileUtils.

Работать-то может и работает, охотно верю. Только мне в сервисе столько хлама абсолютно незачем. Вот зачем мне формат PE executable, несколько парсеров карт и debug info и какие-то утилиты работы с файлами? У меня своих достаточно. Тем более, что это не основная, а вспомогательная функциональность, которая к основной задаче не имеет никакого отношения. И переделывать под себя эти модули из JCL очень затруднительно. На мой взгляд они перемудрили и накидали в кучу много лишнего. Нужно было более продумано распределить функциональность по модулям. Короче, проще было переписать все нафиг (типичный русский подход), что я и сделал :).


 
Alex Konshin ©   (2007-02-28 03:16) [39]

> novill ©   (27.02.07 17:51) [21]
> > [16] Alex Konshin ©   (27.02.07 17:31)
> EMapParserException: Table of publics by value is not found
> 0040A8EA: TestProject2.exe+98EA
> 0040AB16: TestProject2.exe+9B16
> 0040A55A: TestProject2.exe+955A
> 0040A587: TestProject2.exe+9587
> 00403DB0: TestProject2.exe+2DB0
> Exception EMapParserException in module TestProject2.exe
> at 0000A8EA.
> Table of publics by value is not found.
>
> Похоже просто парсилку придется "доработать напильником"

А кто говорил, что будет легко? :)
Наверно у тебя просто слишком маленький проектик и нет public методов. В реальных проектах эта таблица не будет пустой. Так как я это делал для конкретного реального проекта, то я и не стал заморачиваться с нереальными случаями.
Также возможно, что у тебя не включена отладочная информация, хотя вроде в таком случае в *.map все равно что-то все-таки попадает (хотя номеров строк уже не будет). Вот это я проверял, у меня работало и на урезаной карте.


 
Ketmar ©   (2007-02-28 04:00) [40]

> Kerk ©   (27.02.07 22:25) [32]
так я ж и пытался пояснить, что "нэ трэба!" (ц) %-)

> Alex Konshin ©   (28.02.07 02:34) [36]
> реализовать парсер TD32

где ж ты был неделю назад?! когда у меня ещё был жив раздел, на котором жили мои исходники? в том числе и выдраный из JCL (после чего переписаный почти заново) парзер TD32 debug info?! всё как всегда... а опять "втыкать" в формат (хоть он и простой) да писать нет желания. и времени тоже.


 
ZeroDivide ©   (2007-02-28 08:43) [41]

Проблема в том, что в *.map попадают далеко не все имена функций. Поэтому в распечатку могут попасть функции, которые в памяти перед нужной функции,
что еще более некрасиво.


Какие туда не попадают?


 
Alex Konshin ©   (2007-02-28 08:59) [42]

Я точно не понял какие. Но есть не все. Ну точно попадают имена public методов. Причем дело похоже не только в том что компилятор не включает неиспользуемые методы и функции. Я нарывался на ситуацию, когда адрес не принадлежал ни одной из функции.


 
Piter ©   (2007-02-28 09:16) [43]

Ketmar ©   (27.02.07 21:12) [31]
ты без этого жил? жил. значит -- не нужна


уверяю тебя, ты когда-то без Delphi жил, и ничего. Получается, она тебе не нужна.

Kerk ©   (27.02.07 22:25) [32]
Не спорь ты с ним. Он ведь правда похоже считает, что суть в передаваемых в функции аргументах :)


ценю твое великое желание подколоть, но я спрашивал.


 
novill ©   (2007-02-28 09:46) [44]

> [39] Alex Konshin ©   (28.02.07 03:16)

Оттрассировал... оказалось этот блок парсилки "пережевывает" весь map файл до конца, что в общем не удивительно.

 // Parse detailed map of segments
 repeat
   pEOL := NextEOL;
   ptr := FTextPtr;
   linelen := pEOL-ptr;
   if linelen<60 then
   begin
     if (linelen=PUBLICS_BY_NAME_TITLE_LEN) and CompareMem(ptr,PChar(PUBLICS_BY_NAME_TITLE),PUBLICS_BY_NAME_TITLE_LEN) then Break;
     Continue;
   end;
   if not CompareMem(ptr,SEGMENT_CODE_PREFIX,6) then Continue;

   Inc(ptr,6);
   uOffset := GetHexNumber(ptr);
   Inc(ptr,9);
   uSize := GetHexNumber(ptr);

   pUnitName := FTextPtr+59;
   ptr := NextBlank(pUnitName);
   AddUnit( FStringPool.CopyString(pUnitName,ptr-pUnitName), uOffset, uSize );
 until NextLine=nil;


 
novill ©   (2007-02-28 10:46) [45]

Alex Konshin ты в какой версии Дельфи это всё тестил?

В .map файле созданном в Д6
строки
 Address         Publics by Name
 Address         Publics by Value

имеют отличие от ожидаемого парсером написание.


 
Игорь Шевченко ©   (2007-02-28 10:48) [46]

Alex Konshin ©   (28.02.07 02:42) [37]


> А что хоть происходит?
> Ну у меня это сделано в сервисе, так что я с формами не
> тестировал, всякое может быть. Но по идее должно работать.
>
> Ты включи отладку самого стектрейсера (в defines.inc), возможно
> поймешь в чем дело. Я на вскидку, конечно, не знаю в чем
> дело, но возможно, что трейсер не может найти конец место,
>  где надо остановится раскручивать. Хотя, черт его знает
> что там у тебя случилось - ты же ничего конкретного не сообщил.
>


Происходит, как и требуется, Access Violation, но DbgView ничего не показывает, а writeln в VCL-приложении мне даже вставлять не хочется :)
На FormCreate вызывается HookException (ODS), по ButtonClick вызывается метод, гарантирующий Access Violation

В консольном все хорошо, показывает и в окне консоли и в dbgview.


 
Alex Konshin ©   (2007-02-28 10:58) [47]


> novill ©   (28.02.07 10:46) [45]
>
> Alex Konshin ты в какой версии Дельфи это всё тестил?
>
> В .map файле созданном в Д6
> строки
>  Address         Publics by Name
>  Address         Publics by Value
> имеют отличие от ожидаемого парсером написание.

Вот и хорошо, что ты это нашел :) Нужно добавить условную компиляцию.
Я свой проект под BDS 4.0 компилю. Я же сказал, что я это делал для себя и для конкретного проекта. Но можно довести до ума совместными усилиями :)


 
Игорь Шевченко ©   (2007-02-28 11:00) [48]

Alex Konshin ©   (28.02.07 10:58) [47]


> Я свой проект под BDS 4.0 компилю


Кстати, описанное мной поведение наблюдается тоже в D2006 (другой версии просто нету)


 
Alex Konshin ©   (2007-02-28 11:02) [49]

> Игорь Шевченко ©   (28.02.07 10:48) [46]
> Происходит, как и требуется, Access Violation, но DbgView
> ничего не показывает, а writeln в VCL-приложении мне даже
> вставлять не хочется :)
> На FormCreate вызывается HookException (ODS), по ButtonClick
> вызывается метод, гарантирующий Access Violation
>
> В консольном все хорошо, показывает и в окне консоли и в  dbgview.

У меня такая мысль: а сам Delphi наверно тоже в какой-то момент устанавливает свой обработчик прерываний. Наверно в этом и дело. Он затирает мой хук.
Вообще-то в оригинале в JCL делается организуется цепочка хуков, а так мне это было нафиг не нужно, то я это и не стал делать. Короче понятно, в каком месте и куда копать.


 
Alex Konshin ©   (2007-02-28 11:12) [50]

А, так ты по FormCreate хук ставишь? Ставь его в *.dpr в самом начале.


 
Игорь Шевченко ©   (2007-02-28 11:21) [51]

Alex Konshin ©   (28.02.07 11:12) [50]

Это я сейчас не могу попробовать, вечером :)


 
ZeroDivide ©   (2007-02-28 12:10) [52]

>Я точно не понял какие. Но есть не все.

Думаю, все же, есть имена ВСЕХ методов вызываемых в проекте.

>Я нарывался на ситуацию, когда адрес не принадлежал ни одной из функции.
Возможно Exception возникал внутри одной из стандартных виндовских dll, тогда да, адрес exception"а в map-файле не найдешь. В данном случае важен список параметров вызова, который тоже достаточно трудно вытащить из стека. Но история вызовов может дать некоторую информацию.


> FormCreate хук ставишь? Ставь его в *.dpr в самом начале.

Я код не смотрел... зачем вообще для этой задачи хуки?


 
Rouse_ ©   (2007-02-28 12:28) [53]


>  я, например, вменяемый способ отлаживать некий сервис так
> и не нашёл.

Эээ, Attach to Process не пробовал? :)


 
novill ©   (2007-02-28 12:33) [54]

теперь вываливается AV  на строчке
AddPublicName( sName, FStringPool.CopyString(pName,pEOL-pName), uOffset );
при вызове CopyString

Строчки
     Result := PChar(Allocate(Len+1));
     System.Move(Ptr^,Result^,Len);


при Len=4294962803 не срабатывают,  на (Result+Len)^ := #0; валится AV.


 
ZeroDivide ©   (2007-02-28 12:35) [55]

Вот как делаю я:

Для того чтобы оттрассировать стек, я просто получаю его адрес при старте проекта и при возникновении ошибки, а затем пробегаюсь по нему на предмет разделения его содержимого на данные и адреса возврата и затем строю историю вызовов, проверяя по map-файлу каким функциям принадлежат найденные адреса возврата.
И ни каких хуков не надо: код работатет только при возникновении unhandled exception!

Самое сложное в этой ситуации, отделить данные от адресов возврата. Это возможно:

Кусок из моего TYanosExceptionHandler (с):

procedure TYanosExceptionHandler.GetCalls(var St: TstringList);
var
 MapFileAddress, PrevProcStart, NextProcStart, ProcStart: DWORD;
 StackPtr: DWORD;
 n: integer;
 m: LongInt;
 sModule, sProc, sLine, ComStr: String;
 RepeatCnt: Integer;
begin
 St.Add(" ");
 MapFileAddress := GetMapAddressFromAddress(DWORD(ExceptAddr));
 St.Add("Module= "+ GetModuleNameFromAddress(MapFileAddress));
 St.Add("Proc= "+ GetProcNameFromAddress(MapFileAddress, ProcStart));
 St.Add("Line= "+ GetLineNumberFromAddress(MapFileAddress));
 St.Add(" ");

 asm
   mov StackPtr, ESP
 end;

 St.Add(" ИСТОРИЯ ВЫЗОВОВ ПРОЦЕДУР");
 m := DWORD(FStack);
 m := m - DWORD(StackPtr);
 RepeatCnt := 1;
 PrevProcStart := 0;
 if m > 0 then
   for n := 0 to m do
   begin
     if IsCall(StackPtr, ComStr, NextProcStart) then
     begin
       MapFileAddress := GetMapAddressFromAddress(DWORD(Ptr(StackPtr)^)-5);
       sModule := GetModuleNameFromAddress(MapFileAddress);
       sProc := GetProcNameFromAddress(MapFileAddress, ProcStart);
       sLine := GetLineNumberFromAddress(MapFileAddress);
       if (sModule <> "") and (sProc <> "") and (sLine <> "") and (sModule <> "YanosExceptionHandler") then
       begin
         if ehoCallHistoryExt in Options then
         begin
           if PrevProcStart = ProcStart then
             Inc(RepeatCnt)
           else
           begin
             if RepeatCnt > 1 then
               St.Add("Recursive, count =" + IntToStr(RepeatCnt));
             RepeatCnt := 1;
           end;
           if RepeatCnt = 1 then
           begin
             St.Add("  ");
             St.Add("Module= "+ sModule);
             St.Add("Proc= "+ sProc);
             St.Add("Start at= $"+ IntToHex(ProcStart, 8));
             St.Add("Line= "+ sLine);
             St.Add("Comment= "+ComStr);
             if NextProcStart <> 0 then
               St.Add("Call to $" + IntToHex(NextProcStart, 8));
             PrevProcStart := ProcStart;
           end;
         end
         else
         begin
           if St[St.Count-1] <> sProc then
             St.Add(sProc);
         end;
       end;
     end;
     Inc(StackPtr);
   end;
end;

function TYanosExceptionHandler.IsCall(StackPtr: DWORD; var ComStr: String; var NextProcStartAddr: DWORD): boolean;
var
 SAddr: DWORD;
 CallPtr: ^DWORD;
 CallData: DWORD;
//  Found: boolean;
 function GetRegisterName(b: Byte): string;
 begin
   case b of
     0: Result := "EAX";
     1: Result := "ECX";
     2: Result := "EDX";
     3: Result := "EBX";
     4: Result := "ESP";
     5: Result := "EBP";
     6: Result := "ESI";
     7: Result := "EDI";
   end;
 end;

 function GetRegisterValue(b: Byte): DWORD;
 begin
   case b of
     0: asm mov Result, EAX end;
     1: asm mov Result, ECX end;
     2: asm mov Result, EDX end;
     3: asm mov Result, EBX end;
     4: asm mov Result, ESP end;
     5: asm mov Result, EBP end;
     6: asm mov Result, ESI end;
     7: asm mov Result, EDI end;
   end;
 end;

begin
 Result := False;

   //Near Address Call (Normal)
   SAddr := DWORD(Ptr(StackPtr)^);
   Dec(SAddr, 5);
   CallPtr := Ptr(SAddr);
   if IsBadHugeReadPtr(CallPtr,5) = False then
   begin
     CallData := BYTE(CallPtr^);
     if CallData = $E8 then
     begin
       ComStr := "Call Address";
       Result := True;
       Inc(SAddr);
       CallPtr := Ptr(SAddr);
       NextProcStartAddr := DWORD(CallPtr^) + DWORD(Ptr(StackPtr)^);
     end;
   end;

   // Register addresing call with offset (call [EAX+X] - call [EDI+X])
   SAddr := DWORD(Ptr(StackPtr)^);
   Dec(SAddr, 3);
   CallPtr := Ptr(SAddr);
   if IsBadHugeReadPtr(CallPtr,3) = False then
   begin
     CallData := WORD(CallPtr^);
     if (Lo(WORD(CallData)) = $FF) and (Hi(WORD(CallData)) >= $50) and (Hi(WORD(CallData)) <= $57) then
     begin
       Result := True;
       Inc(SAddr,2);
       CallPtr := Ptr(SAddr);
       if ShortInt(CallPtr^) < 0 then
         ComStr := "CALL ["+ GetRegisterName(Byte(StrToInt("$50") xor Hi(WORD(CallData))))+ IntToStr(ShortInt(CallPtr^)) +"]"
       else
         ComStr := "CALL ["+ GetRegisterName(Byte(StrToInt("$50") xor Hi(WORD(CallData))))+"+"+ IntToStr(ShortInt(CallPtr^)) +"]";
       NextProcStartAddr := 0; //DWORD(Ptr( GetRegisterValue(Byte(StrToInt("$50") xor Hi(WORD(CallData)))) + ShortInt(CallPtr^) )) ;
     end;
   end;

end;


 
Ketmar ©   (2007-02-28 12:49) [56]

> Rouse_ ©   (28.02.07 12:28) [53]
неа, не канает. сервис занимается в том числе перехватом/подменой API. и сильно многопоточный. с attach не будет ничего, кроме геморроя. %-)


 
Alex Konshin ©   (2007-02-28 12:49) [57]

Удалено модератором
Примечание: Выражения выбираем


 
Alex Konshin ©   (2007-02-28 13:15) [58]

> ZeroDivide ©   (28.02.07 12:35) [55]

Я не понимаю, каким образом в общем случае можно сделать раскрутку стека полагаясь только на call и ret. Да, зачастую это будет работать. Но достаточно где-то в ассемблере в одном месте сбить стек(положить лишнеее и достатать) и все, работать не будет. Самое неприятное, что это может не сработать как раз в сложных случая, когда трудно понять причину. А ведь еще можно в ассемблере и специально со стеком работать. Вот я не очень давно показал, как безопасно на стеке выделять память переменной длины для временых данных. В этом случае облом будет полный. При раскрутке стек фреймов информации может и меньше, но она более надежна.

Но вообще-то никто не мешает скомбинировать эти два способа раскрутки для взаимной коррекции/контроля и извлечения большей информации. Теоретически это можно сделать.


 
pasha_golub ©   (2007-02-28 13:20) [59]

Извините, ребята, если повторю кого, но читать все сил нету.

http://eurekalog.com/

Вот эта вещь просто иногда спасает.


 
Alex Konshin ©   (2007-02-28 13:37) [60]


> Alex Konshin ©   (28.02.07 12:49) [57]
>
> Удалено модератором
> Примечание: Выражения выбираем
>

Очень интересно, что же я там такого сказал? Прямо теряюсь в догадках


 
Ketmar ©   (2007-02-28 13:39) [61]

> pasha_golub ©   (28.02.07 13:20) [59]
Prices begin at $99(US).


 
novill ©   (2007-02-28 13:56) [62]

> [60] Alex Konshin ©   (28.02.07 13:37)

Да вроде довольно культурно всё, кроме одного слова. Типа "ерунда", только эмомоинальнее

Цитировать не буду...


 
novill ©   (2007-02-28 13:57) [63]

процтитирую все же

Я тоже думал, что все. Ан нет. Говорю же, уже сталкивался. Я сначала тоже сделал печать имени метода/функции и смещения от ее начала, но увидел, что иногда <censored> получается. Почему - не разбирался, мне оп-большому счету это пофиг было, я и тому, что номера строк стало выдавать, был безумно рад.

> Я код не смотрел... зачем вообще для этой задачи хуки?

Это не те хуки, что ты думаешь. Там действительно подменяется RaiseException в   kernel32 для перехвата железных исключений. А для перехвата дельфийских  достаточно установить Sustem.ExceptObjProc.
Перехват делается я так понимаю для того, чтобы первыми получить информацию о прерывании и о его месте, когда стек еще не испорчен самими обработчиками.
Этот эффект кстати хорошо видно и в Delphi IDE когда происходит прерывание. После прерывания зачастую уже и не поймешь что произошло.
Но с другой стороны тебя никто и не заставляет использовать этот хук. Если тебе достаточно места самого прерывания, то можно достать его адрес, а по адресу уже найти в карте место. Но обычно этого недостаточно.


 
ZeroDivide ©   (2007-02-28 14:37) [64]


Я не понимаю, каким образом в общем случае можно сделать раскрутку стека полагаясь только на call и ret. Да, зачастую это будет работать.


Доставайте и ложите что хотите, если не трогаете адреса возврата. Если трогаете и при exception не возвращаете их назад, то лучше отказаться от такого стиля программирования. Если возвращаете их назад, то это уже обрабатываемое исключение.
В общем случае МОЖНО сделать раскрутку стека полагаясь только на call и ret... именно так это делает процессор :)


 
ZeroDivide ©   (2007-02-28 14:46) [65]

Одно исключение: если вы случайно, в качестве каких-либо данных, запихаете в стек значение совпадающее с адресом возврата. Шанс такой ситуации крайне мал. (Во всяком случае, на ранних стадиях нового проекта, я получаю штук по 10 багрепортов в день, но такой ситуации пока в моей практике не пока возникло.)
IsBadCodePtr еще больше сокращает этот шанс.


 
ZeroDivide ©   (2007-02-28 14:47) [66]

не пока возникло
:)
В смысле: пока не возникло.


 
Ketmar ©   (2007-02-28 15:46) [67]

> ZeroDivide ©   (28.02.07 14:37) [64]
ты ещё посоветуй анализатор кода для этого привинтить. или вообще эмулятор. %-)


 
Игорь Шевченко ©   (2007-02-28 21:37) [68]

Как и было обещано, вечером попробовал ситуацию:

Стек трассируется из GUI-приложения, если оно запускается не из-под среды. Прошу прощения, вчера из-под нее запускал, поэтому в DbgView ничего не попадало.


 
Alex Konshin ©   (2007-02-28 23:06) [69]

> ZeroDivide ©   (28.02.07 14:37) [64]
> Я не понимаю, каким образом в общем случае можно сделать
> раскрутку стека полагаясь только на call и ret. Да, зачастую
> это будет работать.
>
>
> Доставайте и ложите что хотите, если не трогаете адреса
> возврата. Если трогаете и при exception не возвращаете их
> назад, то лучше отказаться от такого стиля программирования.
>  Если возвращаете их назад, то это уже обрабатываемое исключение.
>
> В общем случае МОЖНО сделать раскрутку стека полагаясь только
> на call и ret... именно так это делает процессор :)

Неправду говоришь. В частном случа можно. В общем нельзя. Я предполагаю, что ты знаешь, как обрабатываются исключения. ret и текущий указатель стека там абсолютно ни при делах и это понятно почему. Между входом в функцию и выходом из нее по ret стек вполне можно менять. Это обычная практика для C/C++. В Pascal такое редкость, потому что нет языковых средств разместить нечто на стеке уже после объявленых переменных или разместить нечто с переменным размером, но даже в VCL есть код который это делает (вроде в Grids.pas). Я в одном проекте, в котором частично портировал сишный код тоже использую этот прием - выделяю память для временных данных переменной длины на стеке, иначе просто получается неэффективно, а там это очень критично. Потом никто не запрещает вызывать сишные DLL и исключение  вполне может возникнуть там, а уж там твой подход просто не сработает, достаточно хотя бы массив с переменными границами определить. И это только для штатных ситуаций. А сколько всего может произойти при нештатных?

Кстати, а куда ты этот обработчик вешаешь? От этого ведь тоже зависит в каком состоянии ты получишь стек и что именно ты раскручиваешь.

Я повторюсь, что твой подход может быть безусловно полезен для получения дополнительной информации. То есть, в идеале нужно их объединить.


 
GrayFace ©   (2007-03-03 11:22) [70]

Piter ©   (27.02.07 19:10) [26]
а, все, понял. В общем, программка печатает стек. Но ведь далеко не все функции помещают аргументы в стек, верно? Если у функции, например, один параметр integer, так он вроде сразу в регистр будет запихнут?

Нет, при кадом call-е в стек пишется адрес возврата, по нему можно определить процедуру. Еще, при создании Stack Frame, есть такой код: push ebp; mov ebp, esp. Он дает возможность скакать по Stack Frame"ам.

novill ©   (28.02.07 13:57) [63]
Перехват делается я так понимаю для того, чтобы первыми получить информацию о прерывании и о его месте, когда стек еще не испорчен самими обработчиками.

Тут главное получить Context прерывания или хотябы epb.

Alex Konshin ©   (28.02.07 23:06) [69]
В частном случа можно. В общем нельзя.

В общем вообще нельзя. Только при чем тут динамическое выделение памяти в стеке? Я не смотрел код ZeroDevide, но, если правильно понял идею, это не должно влиять.


 
Ketmar ©   (2007-03-03 13:06) [71]

> GrayFace ©   (03.03.07 11:22) [70]
mov  eax,esp
push 0x4768d0
push .....
push .....
sub  esp,256

определяй.


 
Piter ©   (2007-03-03 15:05) [72]

GrayFace ©   (03.03.07 11:22) [70]
Нет, при кадом call-е в стек пишется адрес возврата


а как можно понять - адресс возврата это или например аргумент функции?


 
GrayFace ©   (2007-03-04 15:46) [73]

Ketmar ©   (03.03.07 13:06) [71]
Не понял. Речь о том, что по методу ZeroDevide 0x4768d0 будет принят за адрес возврата или о том, что мой способ его проигнорирует, т.к. не создан Stack Frame?

Piter ©   (03.03.07 15:05) [72]
а как можно понять - адресс возврата это или например аргумент функции?

Stack Frame типично создается в начале функции и если отступить от epb на 4 байта попадаем на адрес возврата. Для функций Дельфи (не асмовых) это действует безотказно, но со многими (или с одной, но часто встречающейся) функциями WinAPI этот вокус не проходит.


 
GrayFace ©   (2007-03-04 15:55) [74]

Уточню: для функций Дельфи, в которых создается Stack Frame, это действует безотказно. А для упомянутых функций WinAPI толи epb указывает не на прошлое значение ebp, толи между ebp и адресом возврата что-то есть, в общем, натыкаемя на недопустимый адрес, а почему я не смотрел.


 
ДжекиМайер   (2007-03-11 23:46) [75]

попробывал D6 WIN200

procedure TForm1.FormCreate(Sender: TObject);
begin
//Application.OnException:=AppException;
DebugUtils.HookException(PrintToConsoleAndODS);


procedure TForm1.AppException(Sender: TObject; E: Exception);

begin
 Application.ShowException(E);
 Application.Terminate;
end;


procedure TForm1.Button1Click(Sender: TObject);
var
a:^dword;
begin
a^:=222;


никаких окон - при нажатии на кнопку - приложение как будно сделало halt;

впрочем и юзание стандартного обработчика Application.OnException:=AppException; дает тот же результат!

требуется только перехватить VA, неможет быть реад врите
и выдавать сообщение (стек и коды ошибок ненужны!!!!!!)
незнаю что из исходника выкинуть чтоб остался сам хук
и процедура куда идет сработка при тяжелой ошибке
(не try except )



Страницы: 1 2 вся ветка

Текущий архив: 2007.04.08;
Скачать: CL | DM;

Наверх




Память: 0.73 MB
Время: 0.037 c
2-1174038418
Двоишник
2007-03-16 12:46
2007.04.08
Определить название Button


1-1171273611
hex1287
2007-02-12 12:46
2007.04.08
Как перекрыть конструктор, если он не виртуальный?


2-1174396361
nit_ro
2007-03-20 16:12
2007.04.08
как сделать базу данных в Borland Developer Studio 2006?


15-1173637687
wacom
2007-03-11 21:28
2007.04.08
HTTP прокси


15-1173753189
Slider007
2007-03-13 05:33
2007.04.08
С днем рождения ! 13 марта