Форум: "Прочее";
Текущий архив: 2007.04.08;
Скачать: [xml.tar.bz2];
ВнизТрассировка exception в Delphi Найти похожие ветки
← →
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.65 MB
Время: 0.047 c