1{ $Id: debugutils.pp 56692 2017-12-11 19:44:22Z juha $ }
2{                   -------------------------------------------
3                     dbgutils.pp  -  Debugger utility routines
4                    -------------------------------------------
5
6 @created(Sun Apr 28st WET 2002)
7 @lastmod($Date: 2017-12-11 20:44:22 +0100 (Mo, 11 Dez 2017) $)
8 @author(Marc Weustink <marc@@dommelstein.net>)
9
10 This unit contains a collection of debugger support routines.
11
12 ***************************************************************************
13 *                                                                         *
14 *   This source is free software; you can redistribute it and/or modify   *
15 *   it under the terms of the GNU General Public License as published by  *
16 *   the Free Software Foundation; either version 2 of the License, or     *
17 *   (at your option) any later version.                                   *
18 *                                                                         *
19 *   This code is distributed in the hope that it will be useful, but      *
20 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
21 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
22 *   General Public License for more details.                              *
23 *                                                                         *
24 *   A copy of the GNU General Public License is available on the World    *
25 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
26 *   obtain it by writing to the Free Software Foundation,                 *
27 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
28 *                                                                         *
29 ***************************************************************************
30}
31unit DebugUtils;
32
33{$mode objfpc}{$H+}
34
35interface
36
37uses
38  DbgIntfBaseTypes, Classes, LCLProc, LazUTF8;
39
40type
41
42  TPCharWithLen = record
43    Ptr: PChar;
44    Len: Integer;
45  end;
46
47  TGdbUnEscapeFlags = set of (uefOctal, uefTab, uefNewLine);
48
49function GetLine(var ABuffer: String): String;
50function ConvertToCString(const AText: String): String;
51function ConvertPathDelims(const AFileName: String): String;
52function DeleteEscapeChars(const AValue: String; const AEscapeChar: Char = '\'): String;
53function MakePrintable(const AString: String): String; // Make a pascal like string
54function UnEscapeBackslashed(const AValue: String; AFlags: TGdbUnEscapeFlags = [uefOctal]; ATabWidth: Integer = 0): String;
55function UnQuote(const AValue: String): String;
56function Quote(const AValue: String; AForce: Boolean=False): String;
57function ConvertGdbPathAndFile(const AValue: String): String; // fix path, delim, unescape, and to utf8
58function ParseGDBString(const AValue: String): String; // remove quotes(') and convert #dd chars: #9'ab'#9'x'
59function GetLeadingAddr(var AValue: String; out AnAddr: TDBGPtr; ARemoveFromValue: Boolean = False): Boolean;
60function UpperCaseSymbols(s: string): string;
61function ConvertPascalExpression(var AExpression: String): Boolean;
62
63procedure SmartWriteln(const s: string);
64
65function PCLenPartToString(const AVal: TPCharWithLen; AStartOffs, ALen: Integer): String;
66function PCLenToString(const AVal: TPCharWithLen; UnQuote: Boolean = False): String;
67function PCLenToInt(const AVal: TPCharWithLen; Def: Integer = 0): Integer;
68function PCLenToQWord(const AVal: TPCharWithLen; Def: QWord = 0): QWord;
69function DbgsPCLen(const AVal: TPCharWithLen): String;
70
71function DPtrMin(const a,b: TDBGPtr): TDBGPtr;
72function DPtrMax(const a,b: TDBGPtr): TDBGPtr;
73
74implementation
75
76uses
77  SysUtils;
78
79{ SmartWriteln: }
80var
81  LastSmartWritelnStr: string;
82  LastSmartWritelnCount: integer;
83  LastSmartWritelnTime: double;
84
85procedure SmartWriteln(const s: string);
86var
87  TimeDiff: TTimeStamp;
88  i: Integer;
89begin
90  if (LastSmartWritelnCount>0) and (s=LastSmartWritelnStr) then begin
91    TimeDiff:=DateTimeToTimeStamp(Now-LastSmartWritelnTime);
92    if TimeDiff.Time<1000 then begin
93      // repeating too fast
94      inc(LastSmartWritelnCount);
95      // write every 2nd, 4th, 8th, 16th, ... time
96      i:=LastSmartWritelnCount;
97      while (i>0) and ((i and 1)=0) do begin
98        i:=i shr 1;
99        if i=1 then begin
100          DebugLn('Last message repeated %d times: "%s"',
101            [LastSmartWritelnCount, LastSmartWritelnStr]);
102          break;
103        end;
104      end;
105      exit;
106    end;
107  end;
108  LastSmartWritelnTime:=Now;
109  LastSmartWritelnStr:=s;
110  LastSmartWritelnCount:=1;
111  DebugLn(LastSmartWritelnStr);
112end;
113
114function GetLine(var ABuffer: String): String;
115var
116  idx: Integer;
117begin
118  idx := Pos(#10, ABuffer);
119  if idx = 0
120  then Result := ''
121  else begin
122    Result := Copy(ABuffer, 1, idx);
123    Delete(ABuffer, 1, idx);
124  end;
125end;
126
127function ConvertToCString(const AText: String): String;
128var
129  srclen, dstlen, newlen: Integer;
130  src, dst: PChar;
131begin
132  srclen := Length(AText);
133  Setlength(Result, srclen);
134  dstlen := srclen;
135  src := @AText[1];
136  dst := @Result[1];
137  newlen := 0;
138  while srclen > 0 do
139  begin
140    if newlen >= dstlen
141    then begin
142      Inc(dstlen, 8);
143      SetLength(Result, dstlen);
144      dst := @Result[newlen+1];
145    end;
146    case Src[0] of
147      '''': begin
148        if (srclen > 2) and (Src[1] = '''')
149        then begin
150          Inc(src);
151          Dec(srclen);
152          Continue;
153        end;
154        dst^ := '"';
155      end;
156      '"': begin
157        if newlen+1 >= dstlen
158        then begin
159          Inc(dstlen, 8);
160          SetLength(Result, dstlen);
161          dst := @Result[newlen+1];
162        end;
163        dst^ := '"';
164        Inc(dst);
165        Inc(newlen);
166        dst^ := '"';
167      end;
168    else
169      dst^ := src^;
170    end;
171    Inc(src);
172    Inc(dst);
173    Inc(newlen);
174    Dec(srclen);
175  end;
176  SetLength(Result, newlen);
177end;
178
179function ConvertPathDelims(const AFileName: String): String;
180var
181  i: Integer;
182begin
183  Result := AFileName;
184  for i := 1 to length(Result) do
185    if Result[i] in ['/','\'] then
186      Result[i] := PathDelim;
187end;
188
189function MakePrintable(const AString: String): String; // Todo: Check invalid utf8
190// Astring should not have quotes
191var
192  n, l, u: Integer;
193  InString: Boolean;
194
195  procedure ToggleInString;
196  begin
197    InString := not InString;
198    Result := Result + '''';
199  end;
200
201begin
202  Result := '';
203  InString := False;
204  n := 1;
205  l := Length(AString);
206  while n <= l do
207  //for n := 1 to Length(AString) do
208  begin
209    case AString[n] of
210      ' '..#127: begin
211        if not InString then
212          ToggleInString;
213        Result := Result + AString[n];
214        if AString[n] = '''' then Result := Result + '''';
215      end;
216    #192..#255: begin // Maybe utf8
217        u := UTF8CodepointSize(@AString[n]);
218        if (u > 0) and (n+u-1 <= l) then begin
219          if not InString then
220            ToggleInString;
221          Result := Result + copy(AString, n, u);
222          n := n + u - 1;
223        end
224        else begin
225          if InString then
226            ToggleInString;
227          Result := Result + Format('#%d', [Ord(AString[n])]);
228        end;
229      end;
230    else
231      if InString then
232        ToggleInString;
233      Result := Result + Format('#%d', [Ord(AString[n])]);
234    end;
235    inc(n);
236  end;
237  if InString
238  then Result := Result + '''';
239end;
240
241function UnEscapeBackslashed(const AValue: String; AFlags: TGdbUnEscapeFlags = [uefOctal]; ATabWidth: Integer = 0): String;
242var
243  c, cnt, len: Integer;
244  Src, Dst: PChar;
245begin
246  len := Length(AValue);
247  if len = 0 then Exit('');
248
249  Src := @AValue[1];
250  cnt := len;
251  SetLength(Result, len); // allocate initial space
252
253  Dst := @Result[1];
254  while cnt > 0 do
255  begin
256    if (Src^ = '\') then begin
257      case (Src+1)^ of
258        '\' :
259          begin
260            inc(Src);
261            dec(cnt);
262          end;
263        '0'..'7' :
264          if uefOctal in AFlags then begin
265            inc(Src);
266            dec(cnt);
267            c := 0;
268            while (Src^ in ['0'..'7']) and (cnt > 0)
269            do begin
270              c := (c * 8) + ord(Src^) - ord('0');
271              Inc(Src);
272              Dec(cnt);
273            end;
274            //c := UnicodeToUTF8SkipErrors(c, Dst);
275            //inc(Dst, c);
276            Dst^ := chr(c and 255);
277            if (c and 255) <> 0
278            then Inc(Dst);
279            if cnt = 0 then Break;
280            continue;
281          end;
282        'n' :
283          if uefNewLine in AFlags then begin
284            inc(Src, 2);
285            dec(cnt, 2);
286            Dst^ := #10;
287            Inc(Dst);
288            continue;
289          end;
290        'r' :
291          if uefNewLine in AFlags then begin
292            inc(Src, 2);
293            dec(cnt, 2);
294            Dst^ := #13;
295            Inc(Dst);
296            continue;
297          end;
298        't' :
299          if uefTab in AFlags then begin
300            inc(Src, 2);
301            dec(cnt, 2);
302            if ATabWidth > 0 then begin;
303              c := Dst - @Result[1];
304              if Length(Result) < c + cnt + ATabWidth + 1 then begin
305                SetLength(Result, Length(Result) + ATabWidth);
306                Dst := @Result[1] + c;
307              end;
308              repeat
309                Dst^ := ' ';
310                Inc(Dst);
311              until ((Dst - @Result[1]) mod ATabWidth) = 0;
312            end
313            else begin
314              Dst^ := #9;
315              Inc(Dst);
316            end;
317            continue;
318          end;
319      end;
320    end;
321    Dst^ := Src^;
322    Inc(Dst);
323    Inc(Src);
324    Dec(cnt);
325  end;
326
327  SetLength(Result, Dst - @Result[1]); // adjust to actual length
328end;
329
330function UnQuote(const AValue: String): String;
331var
332  len: Integer;
333begin
334  len := Length(AValue);
335  if  len < 2 then Exit(AValue);
336
337  if (AValue[1] = '"') and (AValue[len] = '"')
338  then Result := Copy(AValue, 2, len - 2)
339  else Result := AValue;
340end;
341
342function Quote(const AValue: String; AForce: Boolean): String;
343begin
344  if (pos(' ', AValue) < 1) and (pos(#9, AValue) < 1) and (not AForce) then
345    exit(AValue);
346  Result := '"' + StringReplace(AValue, '"', '\"', [rfReplaceAll]) + '"';
347end;
348
349function ConvertGdbPathAndFile(const AValue: String): String;
350begin
351  Result := AnsiToUtf8(ConvertPathDelims(UnEscapeBackslashed(AValue, [uefOctal])));
352end;
353
354function ParseGDBString(const AValue: String): String;
355var
356  i, j, v: Integer;
357  InQuote: Boolean;
358begin
359  if AValue = '' then exit('');
360
361  SetLength(Result, length(AValue));
362  j := 0;
363  i := 0;
364  InQuote := False;
365
366  if copy(AValue,1,2) = '0x' then begin
367    // skip leading address: 0x010aa00 'abc'
368    i := 2;
369    while (i < length(AValue)) and (AValue[i+1] in ['0'..'9', 'a'..'f', 'A'..'F']) do inc(i);
370    while (i < length(AValue)) and (AValue[i+1] in [' ']) do inc(i);
371  end;
372
373  while i < length(AValue) do begin
374    inc(i);
375    If AValue[i] = '''' then begin
376      if InQuote and (i < length(AValue)) and (AValue[i+1] = '''') then begin
377        inc(i);
378        inc(j);
379        Result[j] := '''';
380      end
381      else begin
382        InQuote := not InQuote;
383      end;
384      continue;
385    end;
386    if (AValue[i] = '\' ) and (i < length(AValue)) then begin // gdb escapes some chars, even it not pascalish
387      inc(j);
388      inc(i); // copy next char
389      Result[j] := AValue[i];
390      continue;
391    end;
392    if InQuote or not(AValue[i] = '#' ) then begin
393      inc(j);
394      Result[j] := AValue[i];
395      continue;
396    end;
397    // must be #
398    v := 0;
399    inc(i);
400    while (i < length(AValue)) and (AValue[i] in ['0'..'9']) do begin
401      v:= v * 10 + ord(AValue[i]) - ord('0');
402      inc(i);
403    end;
404    inc(j);
405    Result[j] := chr(v and 255);
406  end;
407  SetLength(Result, j);
408end;
409
410function GetLeadingAddr(var AValue: String; out AnAddr: TDBGPtr;
411  ARemoveFromValue: Boolean): Boolean;
412var
413  i, e: Integer;
414begin
415  AnAddr := 0;
416  Result := (length(AValue) >= 2) and (AValue[1] = '0') and (AValue[2] = 'x');
417
418  if not Result then exit;
419
420  i := 2;
421  while (i < length(AValue)) and (AValue[i+1] in ['0'..'9', 'a'..'f', 'A'..'F']) do inc(i);
422  Result := i > 2;
423  if not Result then exit;
424
425  Val(copy(AValue,1 , i), AnAddr, e);
426  Result := e = 0;
427  if not Result then exit;
428
429  if ARemoveFromValue then begin
430    if (i < length(AValue)) and (AValue[i+1] in [' ']) then inc(i);
431    delete(AValue, 1, i);
432  end;
433end;
434
435function UpperCaseSymbols(s: string): string;
436var
437  i, l: Integer;
438begin
439  Result := s;
440  i := 1;
441  l := Length(Result);
442  while (i <= l) do begin
443    if Result[i] = '''' then begin
444      inc(i);
445      while (i <= l) and (Result[i] <> '''') do
446        inc(i);
447    end
448    else
449    if Result[i] = '"' then begin
450      inc(i);
451      while (i < l) and (Result[i] <> '"') do
452        inc(i);
453    end;
454    (* uppercase due to https://sourceware.org/bugzilla/show_bug.cgi?id=17835
455       gdb 7.7 and 7.8 fail to find members, if lowercased
456       Alternative prefix with "self." if gdb returns &"Type TCLASSXXXX has no component named EXPRESSION.\n"
457    *)
458    if (i<=l) and  (Result[i] in ['a'..'z']) then
459      Result[i] := UpperCase(Result[i])[1];
460    inc(i);
461  end;
462end;
463
464function ConvertPascalExpression(var AExpression: String): Boolean;
465var
466  QuoteChar, R: String;
467  P: PChar;
468  InString, WasString, IsText, ValIsChar: Boolean;
469  n: Integer;
470  ValMode: Char;
471  Value: QWord;
472
473  function AppendValue: Boolean;
474  var
475    S: String;
476  begin
477    if ValMode = #0 then Exit(True);
478    if not (ValMode in ['h', 'd', 'o', 'b']) then Exit(False);
479
480    if ValIsChar
481    then begin
482      if not IsText
483      then begin
484        R := R + '"';
485        IsText := True;
486      end;
487      R := R + '\' + OctStr(Value, 3);
488      ValIsChar := False;
489    end
490    else begin
491      if IsText
492      then begin
493        R := R + '"';
494        IsText := False;
495      end;
496      Str(Value, S);
497      R := R + S;
498    end;
499    Result := True;
500    ValMode := #0;
501  end;
502
503begin
504  R := '';
505  Instring := False;
506  WasString := False;
507  IsText := False;
508  QuoteChar := '"';
509  ValIsChar := False;
510  ValMode := #0;
511  Value := 0;
512
513  P := PChar(AExpression);
514  for n := 1 to Length(AExpression) do
515  begin
516    if InString
517    then begin
518      case P^ of
519        '''': begin
520          InString := False;
521          // delay setting terminating ", more characters defined through # may follow
522          WasString := True;
523        end;
524        #0..#31,
525        '\',
526        #128..#255: begin
527          R := R + '\' + OctStr(Ord(P^), 3);
528        end;
529      else begin
530          if p^ = QuoteChar then
531            R := R + '\' + OctStr(Ord(P^), 3)
532          else
533            R := R + P^;
534        end;
535      end;
536      Inc(P);
537      Continue;
538    end;
539
540    case P^ of
541      '''': begin
542        if WasString
543        then begin
544          R := R + '\' + OctStr(Ord(''''), 3)
545        end
546        else begin
547          if not AppendValue then Exit(False);
548          if not IsText
549          then begin
550            QuoteChar := '"';
551            // single CHAR ?
552            if ( ((p+1)^ <> '''') and ((p+2)^ = '''') and not((p+3)^ in ['#', '''']) ) or
553               ( ((p+1)^ = '''') and ((p+2)^ = '''') and ((p+3)^ = '''') and not((p+4)^ in ['#', '''']) )
554            then
555              QuoteChar := '''';
556            R := R + QuoteChar;
557          end
558        end;
559        IsText := True;
560        InString := True;
561      end;
562      '#': begin
563        if not AppendValue then Exit(False);
564        Value := 0;
565        ValMode := 'D';
566        ValIsChar := True;
567      end;
568      '$', '&', '%': begin
569        if not (ValMode in [#0, 'D']) then Exit(False);
570        ValMode := P^;
571      end;
572    else
573      case ValMode of
574        'D', 'd': begin
575          case P^ of
576            '0'..'9': Value := Value * 10 + Ord(P^) - Ord('0');
577          else
578            Exit(False);
579          end;
580          ValMode := 'd';
581        end;
582        '$', 'h': begin
583          case P^ of
584            '0'..'9': Value := Value * 16 + Ord(P^) - Ord('0');
585            'a'..'f': Value := Value * 16 + Ord(P^) - Ord('a');
586            'A'..'F': Value := Value * 16 + Ord(P^) - Ord('A');
587          else
588            Exit(False);
589          end;
590          ValMode := 'h';
591        end;
592        '&', 'o': begin
593          case P^ of
594            '0'..'7': Value := Value * 8 + Ord(P^) - Ord('0');
595          else
596            Exit(False);
597          end;
598          ValMode := 'o';
599        end;
600        '%', 'b': begin
601          case P^ of
602            '0': Value := Value shl 1;
603            '1': Value := Value shl 1 or 1;
604          else
605            Exit(False);
606          end;
607          ValMode := 'b';
608        end;
609      else
610        if IsText
611        then begin
612          R := R + QuoteChar;
613          IsText := False;
614        end;
615        R := R + P^;
616      end;
617    end;
618    WasString := False;
619    Inc(p);
620  end;
621
622  if not AppendValue then Exit(False);
623  if IsText then R := R + QuoteChar;
624  AExpression := R;
625  Result := True;
626end;
627
628function DeleteEscapeChars(const AValue: String; const AEscapeChar: Char): String;
629var
630  cnt, len: Integer;
631  Src, Dst: PChar;
632begin
633  len := Length(AValue);
634  if len = 0 then Exit('');
635
636  Src := @AValue[1];
637  cnt := len;
638  SetLength(Result, len); // allocate initial space
639
640  Dst := @Result[1];
641  while cnt > 0 do
642  begin
643    if Src^ = AEscapeChar
644    then begin
645      Dec(len);
646      Dec(cnt);
647      if cnt = 0 then Break;
648      Inc(Src);
649    end;
650    Dst^ := Src^;
651    Inc(Dst);
652    Inc(Src);
653    Dec(cnt);
654  end;
655
656  SetLength(Result, len); // adjust to actual length
657end;
658
659{ TPCharWithLen }
660
661function PCLenPartToString(const AVal: TPCharWithLen; AStartOffs, ALen: Integer): String;
662begin
663  if AStartOffs + ALen > AVal.Len
664  then ALen := AVal.Len - AStartOffs;
665  if ALen <= 0
666  then exit('');
667
668  SetLength(Result, ALen);
669  Move((AVal.Ptr+AStartOffs)^, Result[1], aLen)
670end;
671
672function PCLenToString(const AVal: TPCharWithLen; UnQuote: Boolean = False): String;
673begin
674  if UnQuote and (AVal.Len >= 2) and (AVal.Ptr[0] = '"') and (AVal.Ptr[AVal.Len-1] = '"')
675  then begin
676    SetLength(Result, AVal.Len - 2);
677    if AVal.Len > 2
678    then Move((AVal.Ptr+1)^, Result[1], AVal.Len - 2)
679  end
680  else begin
681    SetLength(Result, AVal.Len);
682    if AVal.Len > 0
683    then Move(AVal.Ptr^, Result[1], AVal.Len)
684  end;
685end;
686
687function PCLenToInt(const AVal: TPCharWithLen; Def: Integer = 0): Integer;
688begin
689  Result := StrToIntDef(PCLenToString(AVal, True), Def);
690end;
691
692function PCLenToQWord(const AVal: TPCharWithLen; Def: QWord = 0): QWord;
693begin
694  Result := StrToQWordDef(PCLenToString(AVal, True), Def);
695end;
696
697function DbgsPCLen(const AVal: TPCharWithLen): String;
698begin
699  Result := PCLenToString(AVal);
700end;
701
702function DPtrMin(const a, b: TDBGPtr): TDBGPtr;
703begin
704  if a < b then Result := a else Result := b;
705end;
706
707function DPtrMax(const a, b: TDBGPtr): TDBGPtr;
708begin
709  if a > b then Result := a else Result := b;
710end;
711
712
713initialization
714  LastSmartWritelnCount:=0;
715
716end.
717