Recent

Author Topic: Is there any way to name my threads for debugging?  (Read 16042 times)

Thaddy

  • Hero Member
  • *****
  • Posts: 10099
Re: Is there any way to name my threads for debugging?
« Reply #45 on: January 18, 2018, 08:35:27 am »
Not yet. I couldn't build lazarus from trunk for  a day or two (37962 issue). I'll have a look.
I am more like donkey than shrek

Pascal

  • Hero Member
  • *****
  • Posts: 879
Re: Is there any way to name my threads for debugging?
« Reply #46 on: January 18, 2018, 10:11:07 am »
Not yet. I couldn't build lazarus from trunk for  a day or two (37962 issue). I'll have a look.

This is fixed in the meantime.

But you do not need latest fpc. Just GetMem's patches to Lazars. Forget about my fpc integration, it
doesn't work atm.
laz trunk - fpc trunk 32bit - Windows 10 Pro x64 (1909)

Bi0T1N

  • New Member
  • *
  • Posts: 29
Re: Is there any way to name my threads for debugging?
« Reply #47 on: April 08, 2020, 08:23:53 pm »
I've had a look into the ThreadManager mechanism because I thought about implementing thread naming as TThread.NameThreadForDebugging doesn't do anything yet.
It's quite easy to implement if you write an own function (see below) but TThread.NameThreadForDebugging is defined as class procedure which makes it much more difficult as you cannot access the needed class variables.
The new SetThreadDescription needs a HANDLE to the thread (also works without debugger, see screenshot) instead of the ThreadID which could be circumvented by using GetCurrentThread if it's -1 (default value). But if a ThreadID is given this won't work so that you probably have to use OpenThread. And then another fallback is needed if SetThreadDescription is not available (older Windows) which checks if IsDebuggerPresent and raises an exception but therefore Lazarus has to update its gdb version to at least 8.0 or newer as engkin pointed out.

There it almost seems easier to implement it for most unix platforms via pthread_setname_np.

Code: Pascal  [Select][+][-]
  1. program threads;
  2.  
  3. {$mode Delphi}
  4.  
  5. uses
  6.   SysUtils, Classes;
  7.  
  8.  
  9. function SetThreadDescription(threadHandle: THandle; pcwstr: PWideChar): HResult; stdcall; external KernelDLL name 'SetThreadDescription';
  10.  
  11. type
  12.   TMyThread = class(TThread)
  13.   protected
  14.     procedure Execute; override;
  15.     procedure NameThreadForDebugging1(aThreadName: UnicodeString; aThreadID: TThreadID = TThreadID(-1));
  16.     procedure NameThreadForDebugging1(aThreadName: AnsiString; aThreadID: TThreadID = TThreadID(-1));
  17.   end;
  18.  
  19. procedure TMyThread.Execute;
  20. begin
  21.   writeln('hello from thread '+ThreadID.ToString);
  22.   sleep(30000);
  23. end;
  24.  
  25. procedure TMyThread.NameThreadForDebugging1(aThreadName: AnsiString; aThreadID: TThreadID);
  26. begin
  27.   writeln('ansi '+ThreadID.ToString);
  28.   NameThreadForDebugging1(UnicodeString(aThreadName), aThreadID);
  29. end;
  30.  
  31. procedure TMyThread.NameThreadForDebugging1(aThreadName: UnicodeString; aThreadID: TThreadID);
  32. begin
  33.   writeln('uni threadid '+ThreadID.ToString);
  34.   writeln('uni threadhandle '+Handle.ToString);
  35.   SetThreadDescription(Handle, @aThreadName[1]);
  36. end;
  37.  
  38. var
  39.   threads: TArray<TMyThread>;
  40.   i: Integer;
  41.   thread: TMyThread;
  42.  
  43. begin
  44.   writeln('start');
  45.   SetLength(threads, 5);
  46.   for i := Low(threads) to High(threads) do
  47.   begin
  48.     threads[i] := TMyThread.Create(True);
  49.     threads[i].NameThreadForDebugging1(IntToStr(i));
  50.   end;
  51.  
  52.   for thread in threads do
  53.   begin
  54.     thread.Start;
  55.   end;
  56.  
  57.   for thread in threads do
  58.   begin
  59.     thread.WaitFor;
  60.   end;
  61.  
  62.   writeln('done');
  63.   readln;
  64. end.

EDIT: My Lazarus 2.0.6 installation via fpcupdeluxe comes with GNU gdb (GDB) 7.7.1 (win32) and GNU gdb (GDB) 7.3.50.20110510-cvs (win64).
« Last Edit: April 08, 2020, 08:29:18 pm by Bi0T1N »

ASerge

  • Hero Member
  • *****
  • Posts: 1558
Re: Is there any way to name my threads for debugging?
« Reply #48 on: April 08, 2020, 08:28:15 pm »
The new SetThreadDescription needs a HANDLE to the thread...
As a result, the code should be more complex. Because in this form the program will not even start on Windows 7.

Bi0T1N

  • New Member
  • *
  • Posts: 29
Re: Is there any way to name my threads for debugging?
« Reply #49 on: April 08, 2020, 08:35:41 pm »
The new SetThreadDescription needs a HANDLE to the thread...
As a result, the code should be more complex. Because in this form the program will not even start on Windows 7.
That's why I wrote:
And then another fallback is needed if SetThreadDescription is not available (older Windows) which checks if IsDebuggerPresent and raises an exception...

Use GetProcAddress for trying to load the SetThreadDescription function and if the result is null, do what I've mentioned before.
« Last Edit: April 08, 2020, 08:40:35 pm by Bi0T1N »

ASerge

  • Hero Member
  • *****
  • Posts: 1558
Re: Is there any way to name my threads for debugging?
« Reply #50 on: April 08, 2020, 09:37:58 pm »
Use GetProcAddress for trying to load the SetThreadDescription function and if the result is null, do what I've mentioned before.
This will help avoid loading error, but it will not change the situation in principle for WIndows 7.

Bi0T1N

  • New Member
  • *
  • Posts: 29
Re: Is there any way to name my threads for debugging?
« Reply #51 on: April 09, 2020, 12:05:54 pm »
Use GetProcAddress for trying to load the SetThreadDescription function and if the result is null, do what I've mentioned before.
This will help avoid loading error, but it will not change the situation in principle for WIndows 7.
Could you please explain what exactly you mean?

PascalDragon

  • Hero Member
  • *****
  • Posts: 1488
  • Compiler Developer
Re: Is there any way to name my threads for debugging?
« Reply #52 on: April 09, 2020, 01:22:04 pm »
Use GetProcAddress for trying to load the SetThreadDescription function and if the result is null, do what I've mentioned before.
This will help avoid loading error, but it will not change the situation in principle for WIndows 7.

Yes, it will, because raising a (specific) exception is the way to name the thread (see here).

Bi0T1N

  • New Member
  • *
  • Posts: 29
Re: Is there any way to name my threads for debugging?
« Reply #53 on: April 09, 2020, 10:06:57 pm »
This versions works with both (old & new) variants on a recent Windows 10 :)

Code: Pascal  [Select][+][-]
  1. program threads;
  2.  
  3. {$mode Delphi}
  4.  
  5. uses
  6.   SysUtils, Classes;
  7.  
  8. type
  9.   TSetThreadDescription = function(threadHandle: THandle; pcwstr: PWideChar): HResult; stdcall;
  10.  
  11. //function SetThreadDescription(threadHandle: THandle; pcwstr: PWideChar): HResult; stdcall; external KernelDLL name 'SetThreadDescription';
  12. function GetCurrentThread(): THandle; stdcall; external KernelDLL name 'GetCurrentThread';
  13. function OpenThread(dwDesiredAccess: DWord; bInheritHandle: Boolean; dwThreadId: DWord): THandle; stdcall; external KernelDLL name 'OpenThread';
  14. function IsDebuggerPresent(): Boolean; stdcall; external KernelDLL name 'IsDebuggerPresent';
  15. procedure RaiseException(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWord; const lpArguments: PUInt64); stdcall; external KernelDLL name 'RaiseException';
  16. function GetModuleHandle(lpModuleName: PAnsiChar): THandle; stdcall; external KernelDLL name 'GetModuleHandleA';
  17.  
  18. var
  19.   KernelHandle : THandle;
  20.   SetThreadDescription: TSetThreadDescription;
  21.  
  22. type
  23.   TMyThread = class(TThread)
  24.   private
  25.     i: Integer;
  26.   protected
  27.     procedure Execute; override;
  28.     procedure SetNumber(const aNum: Integer);
  29.     procedure NameThreadForDebugging1(aThreadName: UnicodeString; aThreadID: TThreadID = TThreadID(-1));
  30.     procedure NameThreadForDebugging1(aThreadName: AnsiString; aThreadID: TThreadID = TThreadID(-1));
  31.     class procedure NameThreadForDebuggingTest(aThreadName: UnicodeString; aThreadID: TThreadID = TThreadID(-1));
  32.   end;
  33.  
  34. procedure TMyThread.Execute;
  35. begin
  36.   writeln('hello from thread '+ThreadID.ToString);
  37.   NameThreadForDebugging1(IntToStr(i));
  38.   sleep(30000);
  39. end;
  40.  
  41. procedure TMyThread.SetNumber(const aNum: Integer);
  42. begin
  43.   i := aNum;
  44. end;
  45.  
  46. procedure TMyThread.NameThreadForDebugging1(aThreadName: AnsiString; aThreadID: TThreadID);
  47. begin
  48.   writeln('ansi '+ThreadID.ToString);
  49.   NameThreadForDebugging1(UnicodeString(aThreadName), aThreadID);
  50. end;
  51.  
  52. procedure TMyThread.NameThreadForDebugging1(aThreadName: UnicodeString; aThreadID: TThreadID);
  53. begin
  54.   writeln('uni threadid '+ThreadID.ToString);
  55.   writeln('uni threadhandle '+Handle.ToString);
  56.  
  57.   NameThreadForDebuggingTest(aThreadName, aThreadID);
  58. end;
  59.  
  60. class procedure TMyThread.NameThreadForDebuggingTest(aThreadName: UnicodeString; aThreadID: TThreadID);
  61. const
  62.   MS_VC_EXCEPTION: DWord = $406D1388;
  63. type
  64.   THREADNAME_INFO = record
  65.     dwType: DWord; // Must be 0x1000.
  66.     szName: PAnsiChar; // Pointer to name (in user addr space).
  67.     dwThreadID: DWord; // Thread ID (-1=caller thread).
  68.     dwFlags: DWord; // Reserved for future use, must be zero.
  69.   end;
  70. var
  71.   ThreadHandle: THandle;
  72.   thrdinfo: THREADNAME_INFO;
  73. begin
  74.   writeln('setting threadname to '+aThreadName);
  75.  
  76.   if Assigned(SetThreadDescription) then
  77.   begin
  78.     writeln('this Windows has SetThreadDescription');
  79.     // at least Windows 10 version 1607 or Windows Server 2016
  80.     if aThreadID = TThreadID(-1) then
  81.     begin
  82.       ThreadHandle := GetCurrentThread();
  83.     end
  84.     else
  85.     begin
  86.       ThreadHandle := OpenThread($0400, False, aThreadID);
  87.     end;
  88.  
  89.     SetThreadDescription(ThreadHandle, @aThreadName[1]);
  90.   end
  91.   else
  92.   begin
  93.     writeln('fallback for older Windows');
  94.     // older Windows versions
  95.     if IsDebuggerPresent then
  96.     begin
  97.       writeln('changing name');
  98.  
  99.       thrdinfo.dwType := $1000;
  100.       thrdinfo.szName := @AnsiString(aThreadName)[1];
  101.       thrdinfo.dwThreadID := aThreadID;
  102.       thrdinfo.dwFlags := 0;
  103.  
  104.       RaiseException(MS_VC_EXCEPTION, 0, SizeOf(thrdinfo) div SizeOf(DWord), @thrdinfo);
  105.     end;
  106.   end;
  107. end;
  108.  
  109. var
  110.   threads: TArray<TMyThread>;
  111.   i: Integer;
  112.   thread: TMyThread;
  113.  
  114. begin
  115.   writeln('start');
  116.  
  117.   KernelHandle := GetModuleHandle(KernelDLL);
  118.   if KernelHandle <> 0 then
  119.     SetThreadDescription := TSetThreadDescription(GetProcAddress(KernelHandle, 'SetThreadDescription'));
  120.  
  121.   SetLength(threads, 5);
  122.   for i := Low(threads) to High(threads) do
  123.   begin
  124.     threads[i] := TMyThread.Create(True);
  125.     threads[i].SetNumber(i);
  126.   end;
  127.  
  128.   for thread in threads do
  129.   begin
  130.     thread.Start;
  131.   end;
  132.  
  133.   sleep(10000);
  134.  
  135.   TMyThread.NameThreadForDebuggingTest('test'); // main thread
  136.   TMyThread.NameThreadForDebuggingTest('bronze', threads[2].ThreadID); // third thread
  137.  
  138.   for thread in threads do
  139.   begin
  140.     thread.WaitFor;
  141.   end;
  142.  
  143.   writeln('done');
  144.   readln;
  145. end.

@ASerge
Would be good if you could test it inside a debugger on Windows 7 (e.g. x64dbg) ;)

ASerge

  • Hero Member
  • *****
  • Posts: 1558
Re: Is there any way to name my threads for debugging?
« Reply #54 on: April 09, 2020, 10:14:25 pm »
@ASerge
Would be good if you could test it inside a debugger on Windows 7 (e.g. x64dbg) ;)
After adding the Windows unit (otherwise it is not compiled) and running, it returned an error in line 101, because the DWORD parameter was attempted to be assigned a 64-bit value. I think it's better to test it first on Windows 10.

Bi0T1N

  • New Member
  • *
  • Posts: 29
Re: Is there any way to name my threads for debugging?
« Reply #55 on: April 09, 2020, 10:45:46 pm »
@ASerge
Would be good if you could test it inside a debugger on Windows 7 (e.g. x64dbg) ;)
After adding the Windows unit (otherwise it is not compiled) and running, it returned an error in line 101, because the DWORD parameter was attempted to be assigned a 64-bit value. I think it's better to test it first on Windows 10.

As I said, compiles and works fine with Free Pascal Compiler version 3.3.1-r44644 [2020/04/08] for x86_64. Also no need to add Windows unit.
So I guess your compiling 32-bit?

PascalDragon

  • Hero Member
  • *****
  • Posts: 1488
  • Compiler Developer
Re: Is there any way to name my threads for debugging?
« Reply #56 on: April 10, 2020, 10:45:11 am »
@ASerge
Would be good if you could test it inside a debugger on Windows 7 (e.g. x64dbg) ;)
After adding the Windows unit (otherwise it is not compiled) and running, it returned an error in line 101, because the DWORD parameter was attempted to be assigned a 64-bit value. I think it's better to test it first on Windows 10.

Just add a typecast to DWord. TThreadID is supposed to be a 32-bit type which is fixed with 3.2 (thus only the lower 32-bit will be set anyway).

440bx

  • Hero Member
  • *****
  • Posts: 1824
Re: Is there any way to name my threads for debugging?
« Reply #57 on: April 10, 2020, 11:00:31 am »
Just thinking out loud here...

Wouldn't just having a threadvar set to a string name do the trick ?  it seems that would be preferable since it could be used on versions other than Win10.

FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 1488
  • Compiler Developer
Re: Is there any way to name my threads for debugging?
« Reply #58 on: April 10, 2020, 12:00:10 pm »
We are talking about the NameThreadForDebugging method. The idea for this is to utilise the functionality of the operating system to set the thread's name so that it can easily be identifier in the debugger (thus using ThreadSetDescription in current Windows 10, raising a specific exception in older Windows systems (if a debugger is attached) and using pthread_setname_np on POSIX systems).
While a thread variable would be useful for having the thread's name accessible in the Pascal side this isn't as easy on the debugger side then (where identifying a thread is more often than not more important; I've been there...), cause thread variables aren't that easily accessed from the debugger (I don't even know if FPC generates correct debug information for them).

ASerge

  • Hero Member
  • *****
  • Posts: 1558
Re: Is there any way to name my threads for debugging?
« Reply #59 on: April 10, 2020, 03:45:28 pm »
As I said, compiles and works fine with Free Pascal Compiler version 3.3.1-r44644 [2020/04/08] for x86_64. Also no need to add Windows unit.
So I guess your compiling 32-bit?
Sorry, I missed about 3.3.1.
Of course 64 bit
Test with fpc 3.3.1 64bit, rev.44675. In Lazarus 2.1.0 rev 62926 and outside it.
1. Run app from console:
Code: [Select]
start
hello from thread 9312
ansi 9312
uni threadid 9312
uni threadhandle 132
hello from thread 9736
setting threadname to 3
ansi 9736
fallback for older Windows
uni threadid 9736
uni threadhandle 124
hello from thread 5936
setting threadname to 1
ansi 5936
fallback for older Windows 
uni threadid 5936
uni threadhandle 120
hello from thread 2992
setting threadname to 0
ansi 2992
fallback for older Windows
uni threadid 2992
uni threadhandle 128
hello from thread 9084
setting threadname to 2
ansi 9084
fallback for older Windows
uni threadid 9084
uni threadhandle 136
setting threadname to 4 
fallback for older Windows  <-- timeout
setting threadname to test
fallback for older Windows  <-- timeout
setting threadname to bronze
fallback for older Windows  <-- timeout
done  <-- Press return this

Heap dump by heaptrc unit of "%RealPath%\project1.exe"
168 memory blocks allocated : 7335/7808
163 memory blocks freed     : 6935/7408
5 unfreed memory blocks : 400
True heap size : 229376 (160 used in System startup)
True free heap : 227776
Should be : 227856
Call trace for block $0000000001277C80 size 80
  $000000010000B302
  $0000000100008FEA
  $000000010002459E
  $0000000100002021  main,  line 124 of project1.lpr
  $00000001000022A6
  $0000000100010A80
  $0000000100001770
  $00000000776F556D
  $000000007795372D
Call trace for block $0000000001277B60 size 80
  $000000010000B302
  $0000000100008FEA
  $000000010002459E
  $0000000100002021  main,  line 124 of project1.lpr
  $00000001000022A6
  $0000000100010A80
  $0000000100001770
  $00000000776F556D
  $000000007795372D
Call trace for block $0000000001277A40 size 80
  $000000010000B302
  $0000000100008FEA
  $000000010002459E
  $0000000100002021  main,  line 124 of project1.lpr
  $00000001000022A6
  $0000000100010A80
  $0000000100001770
  $00000000776F556D
  $000000007795372D
Call trace for block $0000000001277920 size 80
  $000000010000B302
  $0000000100008FEA
  $000000010002459E
  $0000000100002021  main,  line 124 of project1.lpr
  $00000001000022A6
  $0000000100010A80
  $0000000100001770
  $00000000776F556D
  $000000007795372D
Call trace for block $0000000001277800 size 80
  $000000010000B302
  $0000000100008FEA
  $000000010002459E
  $0000000100002021  main,  line 124 of project1.lpr
  $00000001000022A6
  $0000000100010A80
  $0000000100001770
  $00000000776F556D
  $000000007795372D

2. Run from IDE. Gdb 7.3.50.
Code: [Select]
start
hello from thread 7288
hello from thread 4716
ansi 7288
ansi 4716
uni threadid 7288
uni threadid 4716
uni threadhandle 128
hello from thread 3620
uni threadhandle 132
setting threadname to 2
ansi 3620
setting threadname to 3
fallback for older Windows
uni threadid 3620
fallback for older Windows
changing name
uni threadhandle 136
changing name
setting threadname to 4
fallback for older Windows
hello from thread 8720
changing name
ansi 8720
hello from thread 8256
ansi 8256
uni threadid 8720
uni threadid 8256
uni threadhandle 124
uni threadhandle 120
setting threadname to 1
setting threadname to 0
fallback for older Windows
fallback for older Windows
changing name
changing name
I wait about 15 seconds, the threads window shows only one thread without a name. After that, the program terminates itself with a similar heap trace.

 

TinyPortal © 2005-2018