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