Работа со стеком

Warstone
Дата: 25.11.2007 14:28:00
Хочу написать свой обработчик ошибок. Что-бы при ошибке о не только говорил тип, но и говорил в каком юните, на какой строчке(это частично реализовано/знаю как) и что в стеке. Как получить содержимое стека - я знаю, но как понять что конкретное значение стека - это адрес процедуры, а не что-то ещё. В принципе есть адреса процедур... всех... из map файла, но если идет что-то типа
RegisterHandler(@HandlerProc);
То в стеке и будет каша мала... То есть... Там будет адрес возврата внутри процедуры, в которой делается вызов процедуры RegisterHandler и адрес 1-й команды (обычно это pop, но не суть важно) процедуры HandlerProc. В результате, если мы будет просматривать адреса на принадлежность процедурам, то мы увидим тут 2 адреса и посчитаем что у нас 2 вызова процедур. Следовательно, не подходит.

Как вариант - дебагить самого себя и ловить инструкции call (по ней определять процедуру и писать ее в свой стек) и ret (по которой убирать последнюю процедуру из своего стека). Но вопрос в том, как это сделать? Парсинг кода? Долго и неэффективно. Можно-ли поставить прерывание или ещё что на появление инструкции call и ret?

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

Убедительная просьба: Воздержаться от комментариев. Тема сложная и разбираться в ней сложно. Пишите, пожалуйста, только что-то конструктивное и по теме.
Warstone
Дата: 25.11.2007 14:32:13
Да, кстати, в коде, допущена ошибка. В описанном случае
RegisterHandler(@HandlerProc);
Адрес процедуры будет передан через EAX (если мне не изменяет память), тогда надо сделать что-то типа:
RegisterHandler(@HandlerProc, @StdErrProc);
Тогда 1 параметр(не помню какой из) пойдет через EAX, а второй - через стек. Или сделать так:
TMyObject.RegisterHandler(@HandlerProc);
Тогда через EAX будет передана ссылка на объект, а адрес процедуры - через стек.
newbb
Дата: 26.11.2007 14:37:03
Интересовался этим вопросом, т.к. для отладки дополнительная информация не помешает. Нашел немало реализаций (платных и безплатных)...MadExcept, EurekaLog итд, есть апи функция StackWalk от Микрософта, можно написать и свой вариант, например: получаеш границы стэка и побайтно проходя сверху вниз (или наооборот) интерпретируеш 4-байта стэка как адрес возврата, потом проверяеш смотрит ли этот адрес в код, если да - смотриш на инструкцию по этому адресу, если это например call (возможны несколько вариантов вызова ПП) - определяеш адрес вызова или просто сохраняеш адрес инструкции, и т.д.
Warstone
Дата: 26.11.2007 15:02:05
newbb
смотриш на инструкцию по этому адресу, если это например call (возможны несколько вариантов вызова ПП) - определяеш адрес вызова или просто сохраняеш адрес инструкции, и т.д.
Хм... А что, call пихает в стек свой адрес? А не свой + 4(что вызываем) + размер своего опкода(1 или 2 байта)? Хотя... не суть важно... Про переход по адресу и просмотр - это конечно интересно, но: А если я в стек запихнул байт, то вершина стека изменится на 1 байт... или на 4? Если на 1, то твой алгоритм не работает, так как все последующее будет идти со смещением и вряд-ли попадет в код.
newbb
Дата: 26.11.2007 15:51:41
ну нет..в стеке может быть что попало по определению, поэтому "как адрес" анализируется весь стэк - смысл такого разбора как раз в универсальности... правда есть вероятность ложного обнаружения вызова...но она не слишком велика )
newbb
Дата: 26.11.2007 15:57:13
вот кусок наковырял...
TopOfStack := GetStackTop;
BotOfStack := GetESP;
PrevCaller := 0;
StackPtr := PDWORD(BotOfStack);
  while DWORD(StackPtr) < TopOfStack do
  begin
    // get possible ret addr from stack(4b)
    if  (StackPtr^ <> PrevCaller) and ValidCall(StackPtr^,adr,ml)then
    begin
       Result := Result + Format(#9'%p %s'#13#10,[Ptr(adr),ml]);
      PrevCaller := StackPtr^;
    end;
    Inc(StackPtr);
  end;
newbb
Дата: 26.11.2007 16:14:13
В оригинале это HVYAST32.pas...правда рекомендуется доработать напильником. Или использовать StackWalk, т.к. она возможно универсальнее.
Warstone
Дата: 26.11.2007 17:00:56
newbb
В оригинале это HVYAST32.pas...правда рекомендуется доработать напильником. Или использовать StackWalk, т.к. она возможно универсальнее.
Ну пока я тоже склоняюсь к StackWalk'у... Но мб это тож не выход. Вопрос: А как-нибудь перехватить инструкции call и ret? Прерываниями какими или ещё чем... Как вариант - зарегистрировать обработчик на каждую инструкцию (я так понимаю - так делает дельфи, когда идем в каком-то страшном режиме... Щас не помню как он ставится)
newbb
Дата: 26.11.2007 17:09:26
А зачем писать еще один дебагер? Смысл раскрутки стэка - получение инф-ции для локализации ошибки...не более. А то вылезет у кого нибудь АВ, а отчего и в каком методе и не понятно )
Warstone
Дата: 26.11.2007 17:12:51
newbb
А зачем писать еще один дебагер? Смысл раскрутки стэка - получение инф-ции для локализации ошибки...не более. А то вылезет у кого нибудь АВ, а отчего и в каком методе и не понятно )
Так вот что-б было понятно надо разобрать где (это уже известно как... на основе map файла (только он ессно немного переработан будет)), а ещё... хорошо-бы восстановить стек, конечно до уровня C# наверно не дотяну, но рядом, при разборе стека - вполне вероятно... Если все дойдет до логической запятой - сюда выложу код =))