Jump to content
Javier Tarí

MCP server to catch windows debug messages

Recommended Posts

After many times of copying and pasting debug messages (OutputDebugString) from the debugger view to the Claude Code (CC) form, so it could read it all, I decided making a MCP server that will just provide that: services to catch and read debug messages

I had no idea on how to do that, but nevertheless asked Claude (rhe chat) to make a plan for it, so CC could just follow the instruction. Then asked CC to add it to one test MCP server I already had working. With help from another CC instance that connected to the server and tried using it, and finding why it errores, it's working now

I've attached the initial document that Claude made (could have asked it to GPT or Gemini or whatever)

Then I asked the CC instance that was using it for helpful enhancements 

I've attached both documents

If there is interest on it, I could make a public repo 

MCP-debug-server.md

MCP-debug-server-enhancements.md

  • Like 1
  • Thanks 1

Share this post


Link to post

In my experience, in a multithreaded app, Windows' built in OutputDebugString will from time to time fail to actually output your data, causing gaps in the logging.

I ended up sending all my debug output through my own logger, which queue the output in a pre-allocated circular buffer and batch writes periodically to file and to actual debug out (if I turn that option on).

Share this post


Link to post

I did made small package that capture all the output from the IDE at real time compiling/building, it does capture all messages in Output tab and Build tab, but it was tested on few and old IDEs, it works on XE8, Seattle( can't remember which number it was as i don't use it) , Delphi 2010, 2007 and 2009 .

 

If that is needed and can help you then i need a confirm before revisiting it, also it will need your help to make sure it will work on newer IDEs, so do you need it ?

Share this post


Link to post
5 hours ago, Lars Fosdal said:

In my experience, in a multithreaded app, Windows' built in OutputDebugString will from time to time fail to actually output your data, causing gaps in the logging.

I ended up sending all my debug output through my own logger, which queue the output in a pre-allocated circular buffer and batch writes periodically to file and to actual debug out (if I turn that option on).

Sure, but here I'm not using OutputDebugString as a log, but as a real time information tool: the difference is the usage with the AI; the AI can read it while the program is running, not at a later time. I will add some easy to implement additional features that will allow the agent to do a much better and faster debugging

Share this post


Link to post
4 hours ago, Kas Ob. said:

If that is needed and can help you then i need a confirm before revisiting it, also it will need your help to make sure it will work on newer IDEs, so do you need it ?

I'm not sure if I will use it, but would be glad to have it and check/adapt to D12/13

Right now the AI agent compiles with the CLI compiler, so it gets back all the compiler messages; but I'm considering interaction with the IDE, which would mean compiling from it

  • Like 1

Share this post


Link to post
1 hour ago, Javier Tarí said:

Sure, but here I'm not using OutputDebugString as a log, but as a real time information tool: the difference is the usage with the AI; the AI can read it while the program is running, not at a later time. I will add some easy to implement additional features that will allow the agent to do a much better and faster debugging

I understand that. My point was that OutputDebugString can fail on the windows side, so that the content does not always reach the listeners. YMMV.

  • Like 1

Share this post


Link to post

Here is a simple console app that extract exports from a DLL, this is needed to confirm compatibility or if adjustment is needed to make the Compiler/IDE message hook working on newer IDEs than mine.

 

20 hours ago, Javier Tarí said:

but would be glad to have it and check/adapt to D12/13

If the function is still there with the same signature (declaration) or similar then it will work, also if it is the same then that means it will work for all IDE newer than XE8.

 

Please, run the following console app, and dump the coreideXXX.bpl exports, and either pack the compress the whole output and attach it here, or just if there is the following or similar procedures 

@Msglines@TCompilerMsgLine@
@Msglines@TCompilerMsgLine@$bctr$qqr17Compintf@TMsgKindix20System@UnicodeStringiit3t3ox53System@%DelphiInterface$26Msglinesintf@IMessageGroup%
@Msglines@TCompilerMsgLine@Draw$qqrp20Vcl@Graphics@TCanvasrx18System@Types@TRecto
@Msglines@TCompilerMsgLine@GetLineText$qqrv

They are together, and might different after "@Msglines@TCompilerMsgLine@" but that what i need to decode and solve in case there is difference, the above are form coreide220.bpl (XE8)

 

the original code of dumping/getting exports from DLL is by David Heffernan

https://stackoverflow.com/questions/31917322/how-to-get-all-the-exported-functions-in-a-dll

 

program DllExportDumper;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  Windows;

// original code by David Heffernan from SO, modified to run in 64bit and support 64bit DLLs
// https://stackoverflow.com/questions/31917322/how-to-get-all-the-exported-functions-in-a-dll

function ImageNtHeader(Base: Pointer): Pointer; stdcall; external 'dbghelp.dll';
function ImageRvaToVa(NtHeaders: Pointer; Base: Pointer; Rva: ULONG; LastRvaSection: Pointer): Pointer; stdcall; external 'dbghelp.dll';

function EnumerateImageExportedFunctionNames(const ImageName: string; NamesList: TStrings): Boolean;
const
  IMAGE_FILE_MACHINE_AMD64 = $8664;
type
  PIMAGE_NT_HEADERSEX = ^IMAGE_NT_HEADERSEX;
  IMAGE_NT_HEADERSEX = record
    Signature: DWORD;
    FileHeader: IMAGE_FILE_HEADER;
    {$IFNDEF FPC}{$IF CompilerVersion >= 24.0 } {$LEGACYIFEND ON} {$IFEND}{$ENDIF}
    {$IF not Declared(IMAGE_OPTIONAL_HEADER64)}
    OptionalHeader: IMAGE_OPTIONAL_HEADER;
    {$ELSE}
    OptionalHeader: IMAGE_OPTIONAL_HEADER32;
    {$IFEND}
  end;
var
  i: Integer;
  FileHandle: THandle;
  ImageHandle: THandle;
  ImagePointer: Pointer;
  NtHeader: Pointer;
  HeaderEx: PIMAGE_NT_HEADERSEX;  // trick to slide by 16 instead of defining record for both 32bit and 64bit, support many compilers
  ExportTable: ^_IMAGE_EXPORT_DIRECTORY;
  NamesPtr: PULONG;
  NamePtr: PAnsiChar;
  Is64Bit: Boolean;
begin
  Result := False;
  NamesList.Clear;

  FileHandle := CreateFile(PChar(ImageName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if FileHandle = INVALID_HANDLE_VALUE then
    Exit;
  try
    ImageHandle := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil);
    if ImageHandle = 0 then
      Exit;
    try
      ImagePointer := MapViewOfFile(ImageHandle, FILE_MAP_READ, 0, 0, 0);
      if not Assigned(ImagePointer) then
        Exit;
      try
        NtHeader := ImageNtHeader(ImagePointer);
        if not Assigned(NtHeader) or (PIMAGE_NT_HEADERSEX(NtHeader)^.Signature <> $00004550) then
          Exit;

        Is64Bit := PIMAGE_NT_HEADERSEX(NtHeader)^.FileHeader.Machine = IMAGE_FILE_MACHINE_AMD64;
        if Is64Bit then      // slide by 16 byte for 64bit
          HeaderEx := PIMAGE_NT_HEADERSEX(NativeUInt(NtHeader) + 16)
        else
          HeaderEx := PIMAGE_NT_HEADERSEX(NtHeader);
        ExportTable := ImageRvaToVa(NtHeader, ImagePointer, HeaderEx^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress, nil);

        if not Assigned(ExportTable) or (ExportTable^.NumberOfNames = 0) or (ExportTable^.NumberOfNames > 1000000) or (Cardinal(ExportTable^.AddressOfNames) = 0) then
          Exit;

        NamesPtr := ImageRvaToVa(NtHeader, ImagePointer, Cardinal(ExportTable^.AddressOfNames), nil);
        if not Assigned(NamesPtr) then
          Exit;

        for i := 0 to ExportTable^.NumberOfNames - 1 do
        begin
          if NamesPtr^ = 0 then
            Exit;
          NamePtr := ImageRvaToVa(NtHeader, ImagePointer, NamesPtr^, nil);
          if not Assigned(NamePtr) then
            Exit;
          NamesList.Add(string(NamePtr));
          inc(NamesPtr);
        end;

        Result := True;
      finally
        UnmapViewOfFile(ImagePointer);
      end;
    finally
      CloseHandle(ImageHandle);
    end;
  finally
    CloseHandle(FileHandle);
  end;
end;

procedure DumpDllExports(const FileName, OutputPath: string);
var
  ExportsList: TStringList;
  OutputFileName: string;
begin
  ExportsList := TStringList.Create;
  try
    if EnumerateImageExportedFunctionNames(FileName, ExportsList) then
    begin
      Writeln('Exports API found: ', ExportsList.Count);
      Writeln('Saving exports for ', FileName);

      OutputFileName := IncludeTrailingPathDelimiter(OutputPath) + ExtractFileName(FileName) + '.txt';
      ExportsList.SaveToFile(OutputFileName);
      Writeln('File saved -> ', OutputFileName);
    end
    else
      Writeln('No exports found or parsing failed for ', FileName);
  finally
    ExportsList.Free;
  end;
end;

begin
  try
    DumpDllExports('D:\Program Files (x86)\Embarcadero\Studio\16.0\bin\coreide220.bpl', GetCurrentDir);
    DumpDllExports('C:\Windows\System32\d3dx10_33.dll', GetCurrentDir);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

For this exact two dlls on my device the coreide220.bpl return more than 22k exports, while that direct3d library return only 177.

  • Like 1

Share this post


Link to post

This is from Delphi 12

 

@Msglines@TCompilerMsgLine@
@Msglines@TCompilerMsgLine@$bctr$qqr17Compintf@TMsgKindix20System@UnicodeStringiit3t3ox53System@%DelphiInterface$26Msglinesintf@IMessageGroup%
@Msglines@TCompilerMsgLine@Draw$qqrp20Vcl@Graphics@TCanvasrx18System@Types@TRecto
@Msglines@TCompilerMsgLine@GetLineText$qqrv

 

  • Like 1
  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×