Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2007.04.08;
Скачать: [xml.tar.bz2];

Вниз

Трассировка 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;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.72 MB
Время: 0.041 c
15-1173686804
Vlad Oshin
2007-03-12 11:06
2007.04.08
Хорошо, очередная рац.идея :) Шляпа.


15-1173983756
Shmit
2007-03-15 21:35
2007.04.08
бизнес-планирование на Delphi


6-1161325036
YurkaT
2006-10-20 10:17
2007.04.08
IdHTTPServer - проблемма с выполнением JS


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


1-1171537578
Аноним
2007-02-15 14:06
2007.04.08
отдельное отображение формы в таскбар





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский