(*  This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2, 3 or any later version
    of the License (at your option).

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*)

(*
  settings set target.output-path /tmp/out.txt
*)
unit LldbDebugger;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, strutils, math,
  // LazUtils
  LazClasses, LazFileUtils, LazLoggerBase, LazStringUtils, Maps,
  // DebuggerIntf
  DbgIntfDebuggerBase, DbgIntfBaseTypes,
  // CmdLineDebuggerBase
  DebugProcess,
  // LazDebuggerLldb
  LldbInstructions, LldbHelper, LazDebuggerIntf, LazDebuggerIntfBaseTypes;

type

  (*
   *  Commands
   *)

  TLldbDebugger = class;
  TLldbDebuggerCommand = class;

  { TLldbDebuggerCommandQueue }

  TLldbDebuggerCommandQueue = class(TRefCntObjList)
  private
    FDebugger: TLldbDebugger;
    FLockQueueRun: Integer;
    function Get(Index: Integer): TLldbDebuggerCommand;
    procedure Put(Index: Integer; const AValue: TLldbDebuggerCommand);
  private
    FRunningCommand: TLldbDebuggerCommand;
    procedure Run; // Call Debugger.OnIdle // set IsIdle
  protected
    procedure CommandFinished(ACommand: TLldbDebuggerCommand);
  public
    constructor Create(ADebugger: TLldbDebugger);
    destructor Destroy; override;
    procedure CancelAll;
    procedure CancelForRun;
    procedure LockQueueRun;
    procedure UnLockQueueRun;
    property Items[Index: Integer]: TLldbDebuggerCommand read Get write Put; default;
    procedure QueueCommand(AValue: TLldbDebuggerCommand);
    procedure DoLineDataReceived(var ALine: String);
    property RunningCommand: TLldbDebuggerCommand read FRunningCommand;
  end;

  { TLldbDebuggerCommand }

  TLldbDebuggerCommand = class(TRefCountedObject)
  private
    FCancelableForRun: Boolean;
    FOwner: TLldbDebugger;
    FIsRunning: Boolean;
    function GetDebuggerState: TDBGState;
    function GetCommandQueue: TLldbDebuggerCommandQueue;
    function GetInstructionQueue: TLldbInstructionQueue;
  protected
    procedure DoLineDataReceived(var ALine: String); virtual;
    procedure DoExecute; virtual; abstract;
    procedure DoCancel; virtual;
    procedure Finished;

    procedure InstructionSucceeded(AnInstruction: TObject);
    procedure InstructionFailed(AnInstruction: TObject);

    procedure QueueInstruction(AnInstruction: TLldbInstruction);
    procedure SetDebuggerState(const AValue: TDBGState);
    property Debugger: TLldbDebugger read FOwner;
    property CommandQueue: TLldbDebuggerCommandQueue read GetCommandQueue;
    property InstructionQueue: TLldbInstructionQueue read GetInstructionQueue;
    property DebuggerState: TDBGState read GetDebuggerState;
  public
    constructor Create(AOwner: TLldbDebugger);
    destructor Destroy; override;
    procedure Execute;
    procedure Cancel;
    property CancelableForRun: Boolean read FCancelableForRun write FCancelableForRun;
  end;

  { TLldbDebuggerCommandInit }

  TLldbDebuggerCommandInit = class(TLldbDebuggerCommand)
  private
    FGotLLDB: Boolean;
  protected
    procedure DoExecute; override;
    procedure DoLineDataReceived(var ALine: String); override;
  end;

  { TLldbDebuggerCommandRun }

  TLldbDebuggerCommandRun = class(TLldbDebuggerCommand)
  private type
    TExceptionInfoCommand = (exiReg0, exiReg2, exiClass, exiMsg);
    TExceptionInfoCommands = set of TExceptionInfoCommand;
  private
    FMode: (cmRun, cmRunToCatch, cmRunAfterCatch, cmRunToTmpBrk);
    FState: (crRunning, crReadingThreads, crStopped, crStoppedRaise, crDone);
    FNextStepAction: TLldbInstructionProcessStepAction;
    FWaitToResume: Boolean;
    FCurBrkId, FTmpBreakId: Integer;
    FUnknownStopReason: String;
    FThreadInstr: TLldbInstructionThreadList;
    FCurrentExceptionInfo: record
      FHasCommandData: TExceptionInfoCommands; // cleared in Setstate
      FObjAddress, FFramePtr: TDBGPtr;
      FExceptClass: String;
      FExceptMsg: String;
    end;
    FFramePtrAtStart: TDBGPtr;
    FFramesDescending: Boolean;
    procedure ThreadInstructionSucceeded(Sender: TObject);
    procedure ExceptionReadReg0Success(Sender: TObject);
    procedure ExceptionReadReg2Success(Sender: TObject);
    procedure ExceptionReadClassSuccess(Sender: TObject);
    procedure ExceptionReadMsgSuccess(Sender: TObject);
    procedure CatchesStackInstructionFinished(Sender: TObject);
    procedure SearchFpStackInstructionFinished(Sender: TObject);
    procedure SearchExceptFpStackInstructionFinished(Sender: TObject);
    procedure TempBreakPointSet(Sender: TObject);
    procedure RunInstructionSucceeded(AnInstruction: TObject);
    procedure ResetStateToRun;
    procedure SetNextStepCommand(AStepAction: TLldbInstructionProcessStepAction);
    procedure ResumeWithNextStepCommand;
    procedure SetTempBreakPoint(AnAddr: TDBGPtr);
    procedure DeleteTempBreakPoint;
    Procedure SetDebuggerLocation(AnAddr, AFrame: TDBGPtr; AFuncName, AFile, AFullFile: String; SrcLine: integer);
  protected
    FStepAction: TLldbInstructionProcessStepAction;
    procedure DoLineDataReceived(var ALine: String); override;
    procedure DoCancel; override;
    procedure DoInitialExecute; virtual; abstract;
    procedure DoExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger);
    destructor Destroy; override;
  end;

  { TLldbDebuggerCommandRunStep }

  TLldbDebuggerCommandRunStep = class(TLldbDebuggerCommandRun)
  private
  protected
    procedure DoInitialExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger; AStepAction: TLldbInstructionProcessStepAction);
  end;

  { TLldbDebuggerCommandRunLaunch }

  TLldbDebuggerCommandRunLaunch = class(TLldbDebuggerCommandRun)
  private
    FRunInstr: TLldbInstruction;
    FLaunchWarnings: String;
    procedure CollectDwarfLoadErrors(Sender: TObject);
    procedure ExceptBreakInstructionFinished(Sender: TObject);
    procedure LaunchInstructionSucceeded(Sender: TObject);
    procedure TargetCreated(Sender: TObject);
  protected
    procedure DoInitialExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger);
  end;

  { TLldbDebuggerCommandStop }

  TLldbDebuggerCommandStop = class(TLldbDebuggerCommand)
  private
    procedure StopInstructionSucceeded(Sender: TObject);
  protected
    procedure DoExecute; override;
  end;

  { TLldbDebuggerCommandLocals }

  TLldbDebuggerCommandLocals = class(TLldbDebuggerCommand)
  private
    FLocals: IDbgLocalsListIntf;
    FLocalsInstr: TLldbInstructionLocals;
    procedure DoLocalsFreed(Sender: TObject);
    procedure LocalsInstructionFinished(Sender: TObject);
  protected
    procedure DoExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger; ALocals: IDbgLocalsListIntf);
    destructor Destroy; override;
  end;

  { TLldbDebuggerCommandEvaluate }

  TLldbDebuggerCommandEvaluate = class(TLldbDebuggerCommand)
  private
    FInstr: TLldbInstructionExpression;
    FWatchValue: IDbgWatchValueIntf;
    FExpr: String;
    FFlags: TWatcheEvaluateFlags;
    FCallback: TDBGEvaluateResultCallback;
    procedure DoWatchFreed(Sender: TObject);
    procedure EvalInstructionFailed(Sender: TObject);
    procedure EvalInstructionSucceeded(Sender: TObject);
  protected
    procedure DoExecute; override;
  public
    // TODO: Pass FCurrentStackFrame to create
    constructor Create(AOwner: TLldbDebugger; AWatchValue: IDbgWatchValueIntf);
    constructor Create(AOwner: TLldbDebugger; AnExpr: String; AFlags: TWatcheEvaluateFlags;
                       ACallback: TDBGEvaluateResultCallback);
    destructor Destroy; override;
  end;


  { TlldbInternalBreakPoint }

  TlldbInternalBreakPoint = class
  private
    FDwarfLoadErrors: String;
    FName: String;
    FBeforePrologue: Boolean;
    FId: Integer;
    FDebugger: TLldbDebugger;
    FOnFail: TNotifyEvent;
    FOnFinish: TNotifyEvent;
    procedure BreakSetSuccess(Sender: TObject);
    procedure DoFailed(Sender: TObject);
    procedure DoFinished(Sender: TObject);
    procedure QueueInstruction(AnInstr: TLldbInstruction);
  public
    constructor Create(AName: String; ADebugger: TLldbDebugger; ABeforePrologue: Boolean = False);
    destructor Destroy; override;
    procedure Enable;
    procedure Disable;
    procedure Remove;
    property BreakId: Integer read FId;
    property OnFail: TNotifyEvent read FOnFail write FOnFail;
    property OnFinish: TNotifyEvent read FOnFinish write FOnFinish;
    property DwarfLoadErrors: String read FDwarfLoadErrors;
  end;

  { TLldbDebuggerCommandRegister }

  TLldbDebuggerCommandRegister = class(TLldbDebuggerCommand)
  private
    FRegisters: TRegisters;
    procedure RegisterInstructionFinished(Sender: TObject);
  protected
    procedure DoExecute; override;
    procedure DoCancel; override;
  public
    constructor Create(AOwner: TLldbDebugger; ARegisters: TRegisters);
    destructor Destroy; override;
    property Registers: TRegisters read FRegisters;
  end;

  (*
   *  Debugger
   *)
  { TLldbDebugger }

  { TLldbDebuggerProperties }

  TLldbDebuggerProperties = class(TDebuggerProperties)
  private
    FLaunchNewTerminal: Boolean;
    FSkipGDBDetection: Boolean;
    FIgnoreLaunchWarnings: Boolean;
  public
    constructor Create; override;
    procedure Assign(Source: TPersistent); override;
  published
    property LaunchNewTerminal: Boolean read FLaunchNewTerminal write FLaunchNewTerminal default False;
    property SkipGDBDetection: Boolean read FSkipGDBDetection write FSkipGDBDetection default False;
    property IgnoreLaunchWarnings: Boolean read FIgnoreLaunchWarnings write FIgnoreLaunchWarnings default False;
  end;

  TLldbDebugger = class(TDebuggerIntf)
  private
    FDebugProcess: TDebugProcess;
    FDebugInstructionQueue: TLldbInstructionQueue;
    FCommandQueue: TLldbDebuggerCommandQueue;
    FInIdle: Boolean;
    FCurrentLocation: TDBGLocationRec;
    FCurrentStackFrame: Integer;
    FCurrentThreadId: Integer;
    FCurrentThreadFramePtr: TDBGPtr;
    FBreakErrorBreak: TlldbInternalBreakPoint;
    FRunErrorBreak: TlldbInternalBreakPoint;
    FExceptionBreak: TlldbInternalBreakPoint;
    FPopExceptStack, FCatchesBreak, FReRaiseBreak: TlldbInternalBreakPoint;
    FTargetWidth: Byte;
    FTargetRegisters: array[0..2] of String;
    FLldbMissingBreakSetDisable: Boolean;
    FExceptionInfo: record
      FReg0Cmd, FReg2Cmd, FExceptClassCmd, FExceptMsgCmd: String;
      FAtExcepiton: Boolean; // cleared in Setstate
    end;
    (* DoAfterLineReceived is called after DebugInstruction.ProcessInputFromDbg
         (but not if ProcessInputFromDbg already handled the Line)
       DoAfterLineReceived will first call CommandQueue.DoLineDataReceived
    *)
    procedure DoAfterLineReceived(var ALine: String);  //
    procedure DoBeforeLineReceived(var ALine: String); // Before DebugInstruction.ProcessInputFromDbg
    procedure DoCmdLineDebuggerTerminated(Sender: TObject);
    procedure DoLineSentToDbg(Sender: TObject; ALine: String);

    function  LldbRun: Boolean;
    function  LldbStep(AStepAction: TLldbInstructionProcessStepAction): Boolean;
    function  LldbStop: Boolean;
    function  LldbPause: Boolean;
    function  LldbEvaluate(const AExpression: String; EvalFlags: TWatcheEvaluateFlags; ACallback: TDBGEvaluateResultCallback): Boolean;
    function  LldbEnvironment(const AVariable: String; const ASet: Boolean): Boolean;
    procedure TerminateLldb;          // Kills external debugger
  protected
    procedure DoBeforeLaunch; virtual;
    procedure DoAfterLaunch(var LaunchWarnings: string); virtual;
    procedure DoBeginReceivingLines(Sender: TObject);
    procedure DoEndReceivingLines(Sender: TObject);
    procedure LockRelease; override;
    procedure UnlockRelease; override;
    procedure QueueCommand(const ACommand: TLldbDebuggerCommand);
    procedure DoState(const OldState: TDBGState); override;
    //procedure DoBeforeState(const OldState: TDBGState); override;
    procedure SetErrorState(const AMsg: String; const AInfo: String = '');
    function DoExceptionHit(AExcClass, AExcMsg: String): Boolean;
    function DoBreakpointHit(BrkId: Integer): Boolean;

    property CurrentThreadId: Integer read FCurrentThreadId;
    property CurrentStackFrame: Integer read FCurrentStackFrame;
    property CurrentLocation: TDBGLocationRec read FCurrentLocation;
    property DebugInstructionQueue: TLldbInstructionQueue read FDebugInstructionQueue;
    property CommandQueue: TLldbDebuggerCommandQueue read FCommandQueue;
  protected
    function  CreateBreakPoints: TDBGBreakPoints; override;
    function  CreateLocals: TLocalsSupplier; override;
    //function  CreateLineInfo: TDBGLineInfo; override;
    function  CreateRegisters: TRegisterSupplier; override;
    function  CreateCallStack: TCallStackSupplier; override;
    //function  CreateDisassembler: TDBGDisassembler; override;
    function  CreateWatches: TWatchesSupplier; override;
    function  CreateThreads: TThreadsSupplier; override;
    function  GetTargetWidth: Byte; override;

    function GetIsIdle: Boolean; override;
    class function  GetSupportedCommands: TDBGCommands; override;
    //function  GetCommands: TDBGCommands; override;
    function  RequestCommand(const ACommand: TDBGCommand;
              const AParams: array of const;
              const ACallback: TMethod): Boolean; override;
  public
    class function CreateProperties: TDebuggerProperties; override; // Creates debuggerproperties
    class function ExeBaseName: String; override;
    class function Caption: String; override;
    class function ExePaths: String; override;
    class function ExePathsMruGroup: TDebuggerClass; override;

    constructor Create(const AExternalDebugger: String); override;
    destructor Destroy; override;
    procedure Init; override;         // Initializes external debugger
    procedure Done; override;         // Kills external debugger

    class function RequiredCompilerOpts(ATargetCPU, ATargetOS: String
      ): TDebugCompilerRequirements; override;
    function GetLocation: TDBGLocationRec; override;
//    function GetProcessList({%H-}AList: TRunningProcessInfoList): boolean; override;
    function NeedReset: Boolean; override;
    procedure TestCmd(const ACommand: String); override;
  end;


procedure Register;

implementation

var
  DBG_VERBOSE: PLazLoggerLogGroup;

type

  {%region
    *****
    *****     Threads
    ***** }

  { TLldbDebuggerCommandThreads }

  TLldbDebuggerCommandThreads = class(TLldbDebuggerCommand)
  private
    procedure ThreadInstructionSucceeded(Sender: TObject);
  protected
    procedure DoExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger);
  end;

  { TLldbThreads }

  TLldbThreads = class(TThreadsSupplier)
  private
    FThreadFramePointers: Array of TDBGPtr;
    function GetDebugger: TLldbDebugger;
  protected
    procedure DoStateEnterPause; override;
    procedure ReadFromThreadInstruction(Instr: TLldbInstructionThreadList; ACurrentId: Integer = -1);
  public
    procedure RequestMasterData; override;
    procedure ChangeCurrentThread(ANewId: Integer); override;
    function GetFramePointerForThread(AnId: Integer): TDBGPtr;
    property Debugger: TLldbDebugger read GetDebugger;
  end;

  {%endregion   ^^^^^  Threads  ^^^^^   }

  {%region
    *****
    *****     CallStack
    ***** }

  { TLldbDebuggerCommandCallStack }

  TLldbDebuggerCommandCallStack = class(TLldbDebuggerCommand)
  private
    FCurrentCallStack: TCallStackBase;
    procedure DoCallstackFreed(Sender: TObject);
    procedure StackInstructionFinished(Sender: TObject);
  protected
    procedure DoExecute; override;
  public
    constructor Create(AOwner: TLldbDebugger; ACurrentCallStack: TCallStackBase);
    destructor Destroy; override;
    property  CurrentCallStack: TCallStackBase read FCurrentCallStack;
  end;

  { TLldbCallStack }

  TLldbCallStack = class(TCallStackSupplier)
  protected
    //procedure Clear;
    procedure DoThreadChanged;
    procedure ParentRequestEntries(ACallstack: TCallStackBase);
  public
    procedure RequestAtLeastCount(ACallstack: TCallStackBase;
      ARequiredMinCount: Integer); override;
    procedure UpdateCurrentIndex; override;
    procedure RequestCurrent(ACallstack: TCallStackBase); override;
    procedure RequestEntries(ACallstack: TCallStackBase); override;
  end;

  {%endregion   ^^^^^  CallStack  ^^^^^   }

  {%region
    *****
    *****     Locals
    ***** }

  { TLldbLocals }

  TLldbLocals = class(TLocalsSupplier)
  public
    procedure RequestData(ALocals: IDbgLocalsListIntf); override;
  end;

  {%endregion   ^^^^^  Locals  ^^^^^   }

  {%region
    *****
    *****     Watches
    ***** }

  { TLldbWatches }

  TLldbWatches = class(TWatchesSupplier)
  private
  protected
    procedure InternalRequestData(AWatchValue: IDbgWatchValueIntf); override;
  public
  end;

  {%endregion   ^^^^^  Watches  ^^^^^   }

  {%region
    *****
    *****     BreakPoint
    ***** }

  { TLldbBreakPoint }

  TLldbBreakPoint = class(TDBGBreakPoint)
  private
    FBreakID: Integer;
    FCurrentInstruction: TLldbInstruction;
    FNeededChanges: TDbgBpChangeIndicators;
    procedure InstructionSetBreakFinished(Sender: TObject);
    procedure InstructionUpdateBreakFinished(Sender: TObject);
    procedure SetBreakPoint;
    procedure ReleaseBreakPoint;
    procedure UpdateProperties(AChanged: TDbgBpChangeIndicators);
    procedure DoCurrentInstructionFinished;
    procedure CancelCurrentInstruction;
  protected
    procedure DoStateChange(const AOldState: TDBGState); override;
    procedure DoPropertiesChanged(AChanged: TDbgBpChangeIndicators); override;
  public
//    constructor Create(ACollection: TCollection); override;
    destructor Destroy; override;
//    procedure DoLogExpression(const AnExpression: String); override;
  end;

  { TLldbBreakPoints }

  TLldbBreakPoints = class(TDBGBreakPoints)
  protected
    function FindById(AnId: Integer): TLldbBreakPoint;
  end;

  {%endregion   ^^^^^  BreakPoint  ^^^^^   }

  {%region
    *****
    *****     Register
    ***** }

  { TLldbRegisterSupplier }

  TLldbRegisterSupplier = class(TRegisterSupplier)
  public
    procedure Changed;
    procedure RequestData(ARegisters: TRegisters); override;
  end;

{ TLldbDebuggerProperties }

constructor TLldbDebuggerProperties.Create;
begin
  inherited Create;
  FLaunchNewTerminal := False;
  FSkipGDBDetection := False;
  FIgnoreLaunchWarnings := False;
end;

procedure TLldbDebuggerProperties.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  if Source is TLldbDebuggerProperties then begin
    FLaunchNewTerminal := TLldbDebuggerProperties(Source).FLaunchNewTerminal;
    FSkipGDBDetection := TLldbDebuggerProperties(Source).FSkipGDBDetection;
    FIgnoreLaunchWarnings := TLldbDebuggerProperties(Source).FIgnoreLaunchWarnings;
  end;
end;

{ TLldbDebuggerCommandRun }

procedure TLldbDebuggerCommandRun.CatchesStackInstructionFinished(Sender: TObject);
var
  r: TStringArray;
  Id, line: Integer;
  IsCur: Boolean;
  addr, stack, frame: TDBGPtr;
  func, filename, fullfile, d: String;
  Arguments: TStringList;
begin
  r := TLldbInstructionStackTrace(Sender).Res;
  if Length(r) < 1 then begin
    SetDebuggerState(dsPause);
    Finished;
    exit;
  end;

  ParseNewFrameLocation(r[0], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d);
  Arguments.Free;
  if addr = 0 then begin
    SetDebuggerState(dsPause);
    Finished;
    exit;
  end;

  if FMode = cmRunToTmpBrk then begin
    if (FFramesDescending and (frame > FFramePtrAtStart)) or
       ((not FFramesDescending) and (frame < FFramePtrAtStart))
    then begin
      ResetStateToRun;
      SetNextStepCommand(saContinue);
      exit;
    end;
    DeleteTempBreakPoint; // except stepped out below temp brkpoint
  end;


  SetTempBreakPoint(Addr);
  ResetStateToRun;
  FMode := cmRunAfterCatch;
  SetNextStepCommand(saContinue);
end;

procedure TLldbDebuggerCommandRun.TempBreakPointSet(Sender: TObject);
begin
  FTmpBreakId := TLldbInstructionBreakSet(Sender).BreakId;
end;

procedure TLldbDebuggerCommandRun.SearchFpStackInstructionFinished(
  Sender: TObject);
var
  r: TStringArray;
  fr: Integer;
  Id, line: Integer;
  IsCur: Boolean;
  addr, stack, frame, prev: TDBGPtr;
  func, filename, fullfile, d: String;
  Arguments: TStringList;
begin
  r := TLldbInstructionStackTrace(Sender).Res;
  if Length(r) < 1 then begin
    SetDebuggerState(dsPause);
    Finished;
    exit;
  end;

  fr := 0;
  prev := 0;
  repeat
    ParseNewFrameLocation(r[fr], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d);
    Arguments.Free;

    if fr = 0 then begin
      FFramesDescending := frame > FFramePtrAtStart;
      if (FState = crStoppedRaise) and (Length(r) >= 2) then begin
        inc(fr);
        Continue;
      end;
    end;

    if not( (frame = 0) or ((fr > 0) and (frame = {%H-}prev)) ) then begin

      if frame = FFramePtrAtStart then
        break;

      if (prev <> 0) and (
         ( (fr < prev) and not(FFramePtrAtStart < fr) ) or
         ( (fr > prev) and not(FFramePtrAtStart > fr) )
         )
      then begin
        SetDebuggerState(dsPause);
        Finished;
        exit;
      end;

      prev := frame;
    end;
    inc(fr);
  until fr >= Length(r);

  if (fr >= Length(r)) or (addr = 0) then begin
    SetDebuggerState(dsPause);
    Finished;
    exit;
  end;

  SetTempBreakPoint(Addr);
  ResetStateToRun;
  FMode := cmRunToTmpBrk;
  SetNextStepCommand(saContinue);
end;

procedure TLldbDebuggerCommandRun.SearchExceptFpStackInstructionFinished(
  Sender: TObject);
var
  r: TStringArray;
  fr: Integer;
  Id, line: Integer;
  IsCur: Boolean;
  addr, stack, frame: TDBGPtr;
  func, filename, fullfile, d: String;
  Arguments: TStringList;
begin
  r := TLldbInstructionStackTrace(Sender).Res;
  if Length(r) < 2 then begin
    SetDebuggerState(dsPause);
    Finished;
    exit;
  end;

  fr := 1;
  repeat
    ParseNewFrameLocation(r[fr], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d);
    Arguments.Free;

    if frame = FCurrentExceptionInfo.FFramePtr then begin
      SetDebuggerLocation(Addr, Frame, Func, filename, FullFile, Line);
      break;
    end;

    inc(fr);
  until fr >= Length(r);

  SetDebuggerState(dsPause);
  Debugger.DoCurrent(Debugger.FCurrentLocation);
  Finished;
end;

procedure TLldbDebuggerCommandRun.ThreadInstructionSucceeded(Sender: TObject);
begin
  FState := crStopped;
end;

procedure TLldbDebuggerCommandRun.ExceptionReadReg0Success(Sender: TObject);
var
  v: String;
  i: SizeInt;
begin
  v := TLldbInstructionReadExpression(Sender).Res;
  i := pos(' = ', v);
  if i > 1 then
    delete(v, 1, i+2);
  FCurrentExceptionInfo.FObjAddress := StrToInt64Def(v, 0);
  Include(FCurrentExceptionInfo.FHasCommandData, exiReg0);
end;

procedure TLldbDebuggerCommandRun.ExceptionReadReg2Success(Sender: TObject);
var
  v: String;
  i: SizeInt;
begin
  v := TLldbInstructionReadExpression(Sender).Res;
  i := pos(' = ', v);
  if i > 1 then
    delete(v, 1, i+2);
  FCurrentExceptionInfo.FFramePtr := StrToInt64Def(v, 0);
  Include(FCurrentExceptionInfo.FHasCommandData, exiReg2);
end;

procedure TLldbDebuggerCommandRun.ExceptionReadClassSuccess(Sender: TObject);
var
  s: String;
  i: SizeInt;
  l: Integer;
begin
  // (char * ) $2 = 0x005c18d0 "\tException"
  // (char *) $10 = 0x00652d44 "\x04TXXX"
  // (char *) $10 = 0x00652d44 "\U00000004TXXX"
  s := TLldbInstructionReadExpression(Sender).Res;
  i := pos('"', s);
  l := 255;
  if i > 0 then begin
    if (i + 2 < Length(s)) and (s[i+1] = '\') then begin
      inc(i, 2);
      if s[i] = 'x' then begin
        l := StrToIntDef('$'+copy(s, i+1, 2), 255);
        inc(i, 2);
      end
      else
      if (i + 9 < Length(s)) and (s[i] = 'U') and  (s[i+1] = '0') then begin
        l := StrToIntDef('$'+copy(s, i+1, 8), 255);
        inc(i, 8);
      end
      else begin
        case s[i] of
          'a': l := 7;
          'b': l := 8;
          't': l := 9;
          'n': l := 10;
          'v': l := 11;
          'f': l := 12;
          'r': l := 13;
          'e': l := 27;
          's': l := 32;
          '\': l := 92;
          'd': l := 127;
        end;
      end;
    end
    else begin
      inc(i, 1);
      l := ord(s[i]);
    end;
    s := copy(s, i+1, Min(l, Length(s)-i-1));
  end;
  FCurrentExceptionInfo.FExceptClass := s;
  Include(FCurrentExceptionInfo.FHasCommandData, exiClass);
end;

procedure TLldbDebuggerCommandRun.ExceptionReadMsgSuccess(Sender: TObject);
var
  s: String;
  i: SizeInt;
begin
  s := TLldbInstructionReadExpression(Sender).Res;
  i := pos('"', s);
  if i > 0 then
    s := copy(s, i+1, Length(s)-i-1);
  FCurrentExceptionInfo.FExceptMsg := s;
  Include(FCurrentExceptionInfo.FHasCommandData, exiMsg);
end;

procedure TLldbDebuggerCommandRun.DoLineDataReceived(var ALine: String);
const
  MaxStackSearch = 99;

  procedure ContinueRunning;
  var
    Instr: TLldbInstruction;
  begin
    if FStepAction = saContinue then begin
      DeleteTempBreakPoint;
      ResetStateToRun;
      FMode := cmRun;
      SetNextStepCommand(saContinue);
      exit;
    end;

    if FFramePtrAtStart = 0 then begin
      SetDebuggerState(dsPause);
      Finished;
      exit;
    end;

    if FTmpBreakId = 0 then begin
      FMode := cmRunToTmpBrk;
      FState := crRunning; // Ignore the STEP 3 / frame
      Instr := TLldbInstructionStackTrace.Create(MaxStackSearch, 0, Debugger.FCurrentThreadId);
      Instr.OnFinish := @SearchFpStackInstructionFinished;
      QueueInstruction(Instr);
      Instr.ReleaseReference;
      exit;
    end;

    ResetStateToRun;
    FMode := cmRunToTmpBrk;
    SetNextStepCommand(saContinue);

    //case FStepAction of
    //  saInsIn: SetDebuggerState(dsPause);
    //  saInsOver, saOver: ;
    //  saInto: ;
    //  saOut: ;
    //end;
  end;

  function GetBreakPointId(AReason: String): Integer;
  var
    i: Integer;
  begin
    i := pos('.', AReason);
    if i = 0 then i := Length(AReason)+1;
    Result := StrToIntDef(copy(AReason, 12, i-12), -1);
    debugln(DBG_VERBOSE, ['DoBreakPointHit ', AReason, ' / ', Result]);
  end;

  procedure DoException;
  var
    ExcClass, ExcMsg: String;
    CanContinue: Boolean;
    Instr: TLldbInstructionStackTrace;
    ExceptItem: TBaseException;
  begin
    if exiClass in FCurrentExceptionInfo.FHasCommandData then
      ExcClass := FCurrentExceptionInfo.FExceptClass
    else
      ExcClass := '<Unknown Class>'; // TODO: move to IDE
    if exiMsg in FCurrentExceptionInfo.FHasCommandData then
      ExcMsg := FCurrentExceptionInfo.FExceptMsg
    else
      ExcMsg := '<Unknown Message>'; // TODO: move to IDE

    ExceptItem := Debugger.Exceptions.Find(ExcClass);
    if (ExceptItem <> nil) and (ExceptItem.Enabled)
    then begin
      FState := crStoppedRaise;
      ContinueRunning;
      exit;
    end;

    CanContinue := Debugger.DoExceptionHit(ExcClass, ExcMsg);

    if CanContinue then begin
      FState := crStoppedRaise;
      ContinueRunning;
      exit;
    end
    else begin
      Debugger.FExceptionInfo.FAtExcepiton := True;

      if (exiReg2 in FCurrentExceptionInfo.FHasCommandData) and (FCurrentExceptionInfo.FFramePtr <> 0) then begin
        FState := crRunning; // ensure command is not finished early
        Instr := TLldbInstructionStackTrace.Create(MaxStackSearch, 0, Debugger.FCurrentThreadId);
        Instr.OnFinish := @SearchExceptFpStackInstructionFinished;
        QueueInstruction(Instr);
        Instr.ReleaseReference;
        exit;
      end;

      Debugger.FCurrentLocation.SrcLine := -1;
      SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
    end;
  end;

  procedure DoRunError;
  var
    CanContinue: Boolean;
    ErrNo: Integer;
    ExceptName: String;
    ExceptItem: TBaseException;
  begin
    ErrNo := 0;
    if exiReg0 in FCurrentExceptionInfo.FHasCommandData then
      ErrNo := FCurrentExceptionInfo.FObjAddress;
    ErrNo := ErrNo and $FFFF;

    ExceptName := Format('RunError(%d)', [ErrNo]);
    ExceptItem := Debugger.Exceptions.Find(ExceptName);
    if (ExceptItem <> nil) and (ExceptItem.Enabled)
    then begin
      FState := crStoppedRaise;
      ContinueRunning;
      exit;
    end;

    Debugger.DoException(deRunError, ExceptName, Debugger.FCurrentLocation, Debugger.RunErrorText[ErrNo], CanContinue);
    if CanContinue
    then begin
      FState := crStoppedRaise;
      ContinueRunning;
      exit;
    end;

    SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
  end;

  procedure DoUnknownStopReason(AStopReason: String);
  var
    CanContinue: Boolean;
  begin
    Debugger.DoException(deExternal, Format('Process stopped with reason: %s', [AStopReason]), Debugger.FCurrentLocation, '', CanContinue);

    SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
  end;

  procedure DoCatchesHit;
  var
    Instr: TLldbInstruction;
  begin
    FState := crRunning; // Ignore the STEP 3 / frame
    Instr := TLldbInstructionStackTrace.Create(1, 1, Debugger.FCurrentThreadId);
    Instr.OnFinish := @CatchesStackInstructionFinished;
    QueueInstruction(Instr);
    Instr.ReleaseReference;
  end;

  procedure DoStopTemp;
  begin
    if (FMode in [cmRunAfterCatch, cmRunToTmpBrk]) and (Debugger.FCurrentLocation.SrcLine = 0) then begin
      DeleteTempBreakPoint;
      ResetStateToRun;
      FMode := cmRun;
      SetNextStepCommand(saOver);
      exit;
    end;

    SetDebuggerState(dsPause);
  end;

  procedure DoBreakPointHit(BrkId: Integer);
  var
    CanContinue: Boolean;
  begin
    CanContinue := Debugger.DoBreakpointHit(BrkId);

    if CanContinue
    then begin
      // Important trigger State => as snapshot is taken in TDebugManager.DebuggerChangeState
      SetDebuggerState(dsInternalPause); // TODO: need location?
      ContinueRunning;
    end
    else begin
      SetDebuggerState(dsPause);
    end;
  end;

var
  Instr: TLldbInstruction;
  found: TStringArray;
  AnId, SrcLine, i: Integer;
  AnIsCurrent: Boolean;
  AnAddr, stack, frame: TDBGPtr;
  AFuncName, AFile, AReminder, AFullFile, s, Name: String;
  AnArgs: TStringList;
begin
  (* When the debuggee stops (pause), the following will be received:
    // for EXCEPTIONS ONLY  (less the spaces between * ) )
     p/x $eax
     (unsigned int) $1 = 0x04dfd920
     p ((char *** )$eax)[0][3]
     (char * ) $2 = 0x005c18d0 "\tException"
     p ((char ** )$eax)[1]
     (char * ) $3 = 0x00000000 <no value available>

    // Hit breakpoint
      Process 10992 stopped
    // thread list => but thread ID may be wrong (maybe look for "reason", instead of leading *
     * thread #1: tid=0x1644: 0x00409e91, 0x0158FF38, 0x0158FF50 &&//FULL: \FPC\SVN\fixes_3_0\rtl\win32\..\inc\except.inc &&//SHORT: except.inc &&//LINE: 185 &&//MOD: project1.exe &&//FUNC: fpc_raiseexception(OBJ=0x038f5a90, ANADDR=0x00401601, AFRAME=0x0158ff58) <<&&//FRAME"
       thread #2: tid=0x27bc: 0x77abf8dc, 0x0557FE64, 0x0557FEC8 &&//FULL:  &&//SHORT:  &&//LINE:  &&//MOD: ntdll.dll &&//FUNC: NtDelayExecution <<&&//FRAME"
      Process 10992 stopped
    // thread (correct) and frame
      * thread #1, stop reason = breakpoint 6.1
          frame #0: 0x0042b855 &&//FULL: \tmp\New Folder (2)\unit1.pas &&//SHORT: unit1.pas &&//LINE: 54 &&//MOD: project1.exe &&//FUNC: FORMCREATE(this=0x04c81248, SENDER=0x04c81248) <<&&//FRAME
      ... stop reason = Exception 0xc0000005 encountered at address 0x42e067
      ... stop reason = EXC_BAD_ACCESS (code=1, address=0x4)
      ... stop reason = step over
      ... stop reason = step in
      ... stop reason = instruction step over
      ... google stop reason = signal / trace / watchpoint
  *)

  {%region exception }
  s := TrimLeft(ALine);
  Instr := nil;
  if StrStartsWith(s, Debugger.FExceptionInfo.FReg0Cmd, True) then begin
    Instr := TLldbInstructionReadExpression.Create;
    Instr.OnSuccess := @ExceptionReadReg0Success;
  end
  else
  if StrStartsWith(s, Debugger.FExceptionInfo.FReg2Cmd, True) then begin
    Instr := TLldbInstructionReadExpression.Create;
    Instr.OnSuccess := @ExceptionReadReg2Success;
  end
  else
  if StrStartsWith(s, Debugger.FExceptionInfo.FExceptClassCmd, True) then begin
    Instr := TLldbInstructionReadExpression.Create;
    Instr.OnSuccess := @ExceptionReadClassSuccess;
  end
  else
  if StrStartsWith(s, Debugger.FExceptionInfo.FExceptMsgCmd, True) then begin
    Instr := TLldbInstructionReadExpression.Create;
    Instr.OnSuccess := @ExceptionReadMsgSuccess;
  end;

  if Instr <> nil then begin
    ALine := '';
    debugln(DBG_VERBOSE, ['Reading exception info']);
    assert(InstructionQueue.RunningInstruction = nil, 'InstructionQueue.RunningInstruction = nil');
    QueueInstruction(Instr);
    Instr.ReleaseReference;
    exit;
  end;
  {%endregion exception }

  // STEP 1:   Process 10992 stopped
  if (FState = crRunning) and StrMatches(ALine, ['Process ', 'stopped']) then begin
    FState := crReadingThreads;
    debugln(DBG_VERBOSE, ['Reading thread info']);
    FThreadInstr := TLldbInstructionThreadListReader.Create();
    FThreadInstr.OnSuccess := @ThreadInstructionSucceeded;
    QueueInstruction(FThreadInstr);
    exit;
  end;

  // STEP 2:   * thread #1, stop reason = breakpoint 6.1
  if StrMatches(ALine, ['* thread #', ', stop reason = ', ''], found) then begin
    FState := crStopped;
    debugln(DBG_VERBOSE, ['Reading stopped thread']);
    SetDebuggerLocation(0, 0, '', '', '', 0);
    if StrStartsWith(found[1], 'breakpoint ') then begin
      FCurBrkId := GetBreakPointId(found[1])
    //end else
    //if StrStartsWith(found[1], 'watchpoint ') then begin
    end else begin
      FUnknownStopReason := '';
      if not( ( StrStartsWith(found[1], 'step ') or StrStartsWith(found[1], 'instruction step ') ) and
              ( StrContains(found[1], ' in') or StrContains(found[1], ' over') or StrContains(found[1], ' out') )
            )
      then
        FUnknownStopReason := found[1];

      FCurBrkId := -1;
    end;

    ParseNewThreadLocation(ALine, AnId, AnIsCurrent, Name, AnAddr,
      Stack, Frame, AFuncName, AnArgs, AFile, AFullFile, SrcLine, AReminder);
    AnArgs.Free;

    Debugger.FCurrentThreadId := AnId;
    Debugger.FCurrentStackFrame := 0;
    SetDebuggerLocation(AnAddr, Frame, AFuncName, AFile, AFullFile, SrcLine);

    InstructionQueue.SetKnownThreadAndFrame(Debugger.FCurrentThreadId, 0);
    Debugger.Threads.CurrentThreads.CurrentThreadId := Debugger.FCurrentThreadId; // set again from thread list

    if StrStartsWith(found[1], 'breakpoint ') then begin
      if FCurBrkId = Debugger.FExceptionBreak.BreakId then
        DoException
      else
      if FCurBrkId = Debugger.FRunErrorBreak.BreakId then
        DoRunError // location = frame with fp // see gdbmi
      else
      if FCurBrkId = Debugger.FBreakErrorBreak.BreakId then
        DoRunError // location = frame(1) // see gdbmi
      else
      if (FCurBrkId = Debugger.FCatchesBreak.BreakId) or
         (FCurBrkId = Debugger.FPopExceptStack.BreakId)
      then
        DoCatchesHit
      else
      if FCurBrkId = Debugger.FReRaiseBreak.BreakId then begin
        FState := crStoppedRaise;
        ContinueRunning;
      end
      else
      if FCurBrkId = FTmpBreakId then
        DoStopTemp
      else
        DoBreakPointHit(FCurBrkId);
    end
    else
    if FUnknownStopReason <> '' then begin
      DoUnknownStopReason(FUnknownStopReason);
    end
    else
      SetDebuggerState(dsPause);

    if (FState = crRunning) then
      exit;

    if DebuggerState in [dsPause, dsInternalPause, dsStop] then
      Debugger.DoCurrent(Debugger.FCurrentLocation);
    FState := crDone;
    ALine := '';

    exit;
  end;

  if (FState = crRunning) then
    exit;

  // STEP 3:   frame #0: 0x0042b855 &&//FULL: \tmp\New Folder (2)\unit1.pas &&//SHORT: unit1.pas &&//LINE: 54 &&//MOD: project1.exe &&//FUNC: FORMCREATE(this=0x04c81248, SENDER=0x04c81248) <<&&//FRAME
  if ParseNewFrameLocation(ALine, AnId, AnIsCurrent, AnAddr, stack, frame, AFuncName, AnArgs,
    AFile, AFullFile, SrcLine, AReminder)
  then begin
    AnArgs.Free;
    if FState = crReadingThreads then begin
      FState := crStopped;
      // did not execute "thread list" / thread cmd reader has read "stop reason"
      for i := 0 to length(FThreadInstr.Res) - 1 do
        DoLineDataReceived(FThreadInstr.Res[i]);
    end;

    DeleteTempBreakPoint;
    Finished;
  end;

  if (LeftStr(ALine, 8) = 'Process ') and (pos('exited with status = ', ALine) > 0) then begin
    DeleteTempBreakPoint;
    Finished;
    exit; // handle in main debugger
  end;

end;

procedure TLldbDebuggerCommandRun.DoCancel;
begin
  InstructionQueue.CancelAllForCommand(Self); // in case there still are any
  DeleteTempBreakPoint;
  // Must not call Finished; => would cancel DeleteTempBreakPoint;
  if CommandQueue.RunningCommand = Self then
    CommandQueue.CommandFinished(Self);
end;

procedure TLldbDebuggerCommandRun.DoExecute;
begin
  if FWaitToResume then
    ResumeWithNextStepCommand
  else
    DoInitialExecute;
end;

procedure TLldbDebuggerCommandRun.RunInstructionSucceeded(AnInstruction: TObject
  );
begin
  FCurrentExceptionInfo.FHasCommandData := [];
end;

procedure TLldbDebuggerCommandRun.ResetStateToRun;
begin
  FState := crRunning;
  FCurBrkId := 0;
  if FThreadInstr <> nil then begin
    FThreadInstr.ReleaseReference;
    FThreadInstr := nil;
  end;
  FCurrentExceptionInfo.FHasCommandData := [];
end;

procedure TLldbDebuggerCommandRun.SetNextStepCommand(
  AStepAction: TLldbInstructionProcessStepAction);
begin
  FNextStepAction := AStepAction;
  {$IFDEF LLDB_SKIP_SNAP}
  ResumeWithNextStepCommand;
  exit;
  {$ENDIF}
  FWaitToResume := True;

  // Run the queue, before continue
  CommandQueue.QueueCommand(Self);
  CommandQueue.CommandFinished(Self);
  //CommandQueue.FRunningCommand := nil;
  //CommandQueue.Run;
end;

procedure TLldbDebuggerCommandRun.ResumeWithNextStepCommand;
var
  Instr: TLldbInstructionProcessStep;
begin
  if FNextStepAction in [saOver, saInto, saOut, saInsOver] then
    Debugger.FReRaiseBreak.Enable
  else
    Debugger.FReRaiseBreak.Disable;

  if FMode in [cmRunToCatch, cmRunToTmpBrk] then begin
    Debugger.FCatchesBreak.Enable;
    Debugger.FPopExceptStack.Enable;
    Instr := TLldbInstructionProcessStep.Create(saContinue);
  end
  else begin
    Debugger.FCatchesBreak.Disable;
    Debugger.FPopExceptStack.Disable;
    Instr := TLldbInstructionProcessStep.Create(FNextStepAction, Debugger.FCurrentThreadId);
  end;
  Instr.OnFinish := @RunInstructionSucceeded;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
  if DebuggerState <> dsRun then
    SetDebuggerState(dsRun);
end;

procedure TLldbDebuggerCommandRun.SetTempBreakPoint(AnAddr: TDBGPtr);
var
  Instr: TLldbInstructionBreakSet;
begin
  Instr := TLldbInstructionBreakSet.Create(AnAddr);
  Instr.OnFinish := @TempBreakPointSet;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

procedure TLldbDebuggerCommandRun.DeleteTempBreakPoint;
var
  Instr: TLldbInstruction;
begin
  if FTmpBreakId = 0 then
    exit;
  Instr := TLldbInstructionBreakDelete.Create(FTmpBreakId);
  QueueInstruction(Instr);
  Instr.ReleaseReference;
  FTmpBreakId := 0;
end;

procedure TLldbDebuggerCommandRun.SetDebuggerLocation(AnAddr, AFrame: TDBGPtr;
  AFuncName, AFile, AFullFile: String; SrcLine: integer);
begin
  Debugger.FCurrentThreadFramePtr := AFrame;
  Debugger.FCurrentLocation.Address := AnAddr;
  Debugger.FCurrentLocation.FuncName := AFuncName;
  Debugger.FCurrentLocation.SrcFile := AFile;
  Debugger.FCurrentLocation.SrcFullName := AFullFile;
  Debugger.FCurrentLocation.SrcLine := SrcLine;
end;

constructor TLldbDebuggerCommandRun.Create(AOwner: TLldbDebugger);
begin
  AOwner.FExceptionInfo.FAtExcepiton := False;
  FState := crRunning;
  FMode := cmRun;
  FFramePtrAtStart := AOwner.FCurrentThreadFramePtr;
  inherited Create(AOwner);
end;

destructor TLldbDebuggerCommandRun.Destroy;
begin
  FThreadInstr.ReleaseReference;
  inherited Destroy;
end;

{ TLldbDebuggerCommandLocals }

procedure TLldbDebuggerCommandLocals.LocalsInstructionFinished(Sender: TObject
  );
var
  n: String;
  i: Integer;
  r: IDbgWatchDataIntf;
begin
  if FLocals <> nil then begin
    FLocals.BeginUpdate;
    for i := 0 to FLocalsInstr.Res.Count - 1 do begin
      n := FLocalsInstr.Res.Names[i];
      r := FLocals.Add(n);
      r.CreatePrePrinted(FLocalsInstr.Res.Values[n]);
    end;
    FLocals.Validity := ddsValid;
    FLocals.EndUpdate;
  end;

  ReleaseRefAndNil(FLocalsInstr);
  Finished;
end;

procedure TLldbDebuggerCommandLocals.DoLocalsFreed(Sender: TObject);
begin
  FLocals := nil;
  if FLocalsInstr <> nil then begin
    FLocalsInstr.OnFinish := nil;
    FLocalsInstr.Cancel;
    ReleaseRefAndNil(FLocalsInstr);
    Finished;
  end;
end;

procedure TLldbDebuggerCommandLocals.DoExecute;
begin
  if FLocals = nil then begin
    Finished;
    exit;
  end;

  if FLocalsInstr <> nil then begin
    FLocalsInstr.OnFinish := nil;
    ReleaseRefAndNil(FLocalsInstr);
  end;
  FLocalsInstr := TLldbInstructionLocals.Create(FLocals.ThreadId, FLocals.StackFrame);
  FLocalsInstr.OnFinish := @LocalsInstructionFinished;
  QueueInstruction(FLocalsInstr);
end;

constructor TLldbDebuggerCommandLocals.Create(AOwner: TLldbDebugger;
  ALocals: IDbgLocalsListIntf);
begin
  FLocals := ALocals;
  FLocals.AddFreeNotification(@DoLocalsFreed);
  CancelableForRun := True;
  inherited Create(AOwner);
end;

destructor TLldbDebuggerCommandLocals.Destroy;
begin
  if FLocalsInstr <> nil then begin
    FLocalsInstr.OnFinish := nil;
    ReleaseRefAndNil(FLocalsInstr);
  end;
  if FLocals <> nil then
    FLocals.RemoveFreeNotification(@DoLocalsFreed);
  inherited Destroy;
end;

  {%endregion   ^^^^^  Register  ^^^^^   }

{%region
  *****
  *****     Threads
  ***** }

{ TLldbDebuggerCommandThreads }

procedure TLldbDebuggerCommandThreads.ThreadInstructionSucceeded(Sender: TObject);
begin
  TLldbThreads(Debugger.Threads).ReadFromThreadInstruction(TLldbInstructionThreadList(Sender));
  Finished;
end;

constructor TLldbDebuggerCommandThreads.Create(AOwner: TLldbDebugger);
begin
  CancelableForRun := True;
  inherited Create(AOwner);
end;

procedure TLldbDebuggerCommandThreads.DoExecute;
var
  Instr: TLldbInstructionThreadList;
begin
  Instr := TLldbInstructionThreadList.Create();
  Instr.OnFinish := @ThreadInstructionSucceeded;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

{ TLldbThreads }

function TLldbThreads.GetDebugger: TLldbDebugger;
begin
  Result := TLldbDebugger(inherited Debugger);
end;

procedure TLldbThreads.DoStateEnterPause;
begin
  inherited DoStateEnterPause;
  Changed;
end;

procedure TLldbThreads.ReadFromThreadInstruction(
  Instr: TLldbInstructionThreadList; ACurrentId: Integer);
var
  i, line: Integer;
  s, func, filename, name, d, fullfile: String;
  TId, CurThrId: LongInt;
  CurThr: Boolean;
  Arguments: TStringList;
  addr, stack, frame: TDBGPtr;
  te: TThreadEntry;
begin
  CurrentThreads.Clear;
  SetLength(FThreadFramePointers, Length(Instr.Res));
  for i := 0 to Length(Instr.Res) - 1 do begin
    s := Instr.Res[i];
    ParseNewThreadLocation(s, TId, CurThr, name, addr, stack, frame, func, Arguments, filename, fullfile, line, d);
    FThreadFramePointers[i] := frame;
    if CurThr then
      CurThrId := TId;

    te := CurrentThreads.CreateEntry(
      addr,
      Arguments,
      func,
      filename, fullfile,
      line,
      TId, name, dtsUnknown
    );
    CurrentThreads.Add(te);
    te.Free;

    Arguments.Free;
  end;

  if ACurrentId >= 0 then
    CurThrId := ACurrentId;
  CurrentThreads.CurrentThreadId := CurThrId;
  CurrentThreads.SetValidity(ddsValid);
end;

procedure TLldbThreads.RequestMasterData;
var
  Cmd: TLldbDebuggerCommandThreads;
  RunCmd: TLldbDebuggerCommand;
  Instr: TLldbInstructionThreadList;
begin
  if not (Debugger.State in [dsPause, dsInternalPause]) then
    exit;

  RunCmd := Debugger.CommandQueue.RunningCommand;
  if RunCmd is TLldbDebuggerCommandRun then begin
    Instr := TLldbDebuggerCommandRun(RunCmd).FThreadInstr;
    if (Instr <> nil) and Instr.IsSuccess then begin
      // FThreadInstr, may have the wrong thread marked. Use Debugger.FCurrentThreadId (which should not have changed since the RunCommand set it)
      ReadFromThreadInstruction(TLldbInstructionThreadList(Instr), Debugger.FCurrentThreadId);
      exit;
    end;
  end;

  Cmd := TLldbDebuggerCommandThreads.Create(Debugger);
  Debugger.QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

procedure TLldbThreads.ChangeCurrentThread(ANewId: Integer);
begin
  if Debugger = nil then Exit;
  if not(Debugger.State in [dsPause, dsInternalPause]) then exit;

  Debugger.FCurrentThreadId := ANewId;
  Debugger.FCurrentThreadFramePtr := GetFramePointerForThread(ANewId);

  if CurrentThreads <> nil
  then CurrentThreads.CurrentThreadId := ANewId;

  TLldbCallStack(Debugger.CallStack).DoThreadChanged;
end;

function TLldbThreads.GetFramePointerForThread(AnId: Integer): TDBGPtr;
begin
  if (AnId < 0) or (AnId >= Length(FThreadFramePointers)) then
    exit(0);
  Result := FThreadFramePointers[AnId];
end;

{%endregion   ^^^^^  Threads  ^^^^^   }

{%region
  *****
  *****     CallStack
  ***** }

{ TLldbDebuggerCommandCallStack }

procedure TLldbDebuggerCommandCallStack.StackInstructionFinished(Sender: TObject
  );
var
  Instr: TLldbInstructionStackTrace absolute Sender;
  i, FId, line: Integer;
  e: TCallStackEntry;
  Arguments: TStringList;
  It: TMapIterator;
  s, func, filename, d, fullfile: String;
  IsCur: Boolean;
  addr, stack, frame: TDBGPtr;
begin
  if FCurrentCallStack = nil then begin
    Finished;
    exit;
  end;

  It := TMapIterator.Create(FCurrentCallStack.RawEntries);

  for i := 0 to Length(Instr.Res) - 1 do begin
    s := Instr.Res[i];
    ParseNewFrameLocation(s, FId, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d);
    if It.Locate(FId) then begin
      e := TCallStackEntry(It.DataPtr^);
      e.Init(addr, Arguments, func, filename, fullfile, line);
    end;
    Arguments.Free;
  end;
  It.Free;

  TLldbCallStack(Debugger.CallStack).ParentRequestEntries(FCurrentCallStack);

  Finished;
end;

procedure TLldbDebuggerCommandCallStack.DoCallstackFreed(Sender: TObject);
begin
  FCurrentCallStack := nil;
  //TODO cancel
end;

procedure TLldbDebuggerCommandCallStack.DoExecute;
var
  StartIdx, EndIdx: Integer;
  Instr: TLldbInstructionStackTrace;
begin
  if FCurrentCallStack = nil then begin
    Finished;
    exit;
  end;

  StartIdx := Max(FCurrentCallStack.LowestUnknown, 0);
  EndIdx   := FCurrentCallStack.HighestUnknown;
  if EndIdx < StartIdx then begin
    Finished;
    exit;
  end;

  Instr := TLldbInstructionStackTrace.Create(EndIdx+1, FCurrentCallStack.ThreadId);
  Instr.OnFinish := @StackInstructionFinished;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

constructor TLldbDebuggerCommandCallStack.Create(AOwner: TLldbDebugger;
  ACurrentCallStack: TCallStackBase);
begin
  inherited Create(AOwner);
  FCurrentCallStack := ACurrentCallStack;
  FCurrentCallStack.AddFreeNotification(@DoCallstackFreed);
  CancelableForRun := True;
end;

destructor TLldbDebuggerCommandCallStack.Destroy;
begin
  if FCurrentCallStack <> nil then
    FCurrentCallStack.RemoveFreeNotification(@DoCallstackFreed);
  inherited Destroy;
end;

{ TLldbCallStack }

procedure TLldbCallStack.DoThreadChanged;
var
  tid, idx: Integer;
  cs: TCallStackBase;
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
    exit;
  end;

  TLldbDebugger(Debugger).FCurrentStackFrame := 0;
  tid := Debugger.Threads.CurrentThreads.CurrentThreadId;
  cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]);
  idx := cs.CurrentIndex;  // CURRENT
  if idx < 0 then idx := 0;

  TLldbDebugger(Debugger).FCurrentStackFrame := idx;
  cs.CurrentIndex := idx;
end;

procedure TLldbCallStack.ParentRequestEntries(ACallstack: TCallStackBase);
begin
  inherited RequestEntries(ACallstack);
end;

procedure TLldbCallStack.RequestAtLeastCount(ACallstack: TCallStackBase;
  ARequiredMinCount: Integer);
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
    ACallstack.SetCurrentValidity(ddsInvalid);
    Exit;
  end;

  ACallstack.Count := ARequiredMinCount + 1; // TODO: get data, and return correct result
  ACallstack.SetCountValidity(ddsValid);
end;

procedure TLldbCallStack.UpdateCurrentIndex;
var
  tid, idx: Integer;
  cs: TCallStackBase;
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
    exit;
  end;

  tid := Debugger.Threads.CurrentThreads.CurrentThreadId; // FCurrentThreadId ?
  cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]);
  idx := cs.NewCurrentIndex;  // NEW-CURRENT

  if TLldbDebugger(Debugger).FCurrentStackFrame = idx then Exit;

  TLldbDebugger(Debugger).FCurrentStackFrame := idx;

  if cs <> nil then begin
    cs.CurrentIndex := idx;
    cs.SetCurrentValidity(ddsValid);
  end;
end;

procedure TLldbCallStack.RequestCurrent(ACallstack: TCallStackBase);
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
    ACallstack.SetCurrentValidity(ddsInvalid);
    Exit;
  end;

  if ACallstack.ThreadId = TLldbDebugger(Debugger).FCurrentThreadId
  then ACallstack.CurrentIndex := TLldbDebugger(Debugger).FCurrentStackFrame
  else ACallstack.CurrentIndex := 0; // will be used, if thread is changed
  ACallstack.SetCurrentValidity(ddsValid);
end;

procedure TLldbCallStack.RequestEntries(ACallstack: TCallStackBase);
var
  Cmd: TLldbDebuggerCommandCallStack;
begin
  if not (Debugger.State in [dsPause, dsInternalPause]) then
    exit;

  Cmd := TLldbDebuggerCommandCallStack.Create(TLldbDebugger(Debugger), ACallstack);
  TLldbDebugger(Debugger).QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

{%endregion   ^^^^^  CallStack  ^^^^^   }

{ TLldbLocals }

procedure TLldbLocals.RequestData(ALocals: IDbgLocalsListIntf);
var
  Cmd: TLldbDebuggerCommandLocals;
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit;

  Cmd := TLldbDebuggerCommandLocals.Create(TLldbDebugger(Debugger), ALocals);
  TLldbDebugger(Debugger).QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

{ TLldbWatches }

procedure TLldbWatches.InternalRequestData(AWatchValue: IDbgWatchValueIntf);
var
  Cmd: TLldbDebuggerCommandEvaluate;
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit;

  Cmd := TLldbDebuggerCommandEvaluate.Create(TLldbDebugger(Debugger), AWatchValue);
  TLldbDebugger(Debugger).QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

{ TLldbBreakPoint }

procedure TLldbBreakPoint.SetBreakPoint;
var
  i: Integer;
  s: String;
  Instr: TLldbInstruction;
  en: Boolean;
begin
  debugln(DBG_VERBOSE, ['TLldbBreakPoint.SetBreakPoint ']);

  if FCurrentInstruction <> nil then begin
    if (FBreakID <> 0) or (not FCurrentInstruction.IsRunning) then begin
      // Can be a queued SetBreak => replace
      // Or an Update => don't care, ReleaseBreakpoint will be called
      CancelCurrentInstruction;
    end
    else begin
      // already running a SetBreakPoint
      FNeededChanges := FNeededChanges + [ciLocation]; // wait for instruction to finish // need ID to del
      exit;
    end;
  end;

  if (FBreakID <> 0) then
    ReleaseBreakPoint;

  en := Enabled;
  if TLldbDebugger(Debugger).FLldbMissingBreakSetDisable and (not en) and (Kind <> bpkData) then begin
    en := True;
    FNeededChanges := FNeededChanges + [ciEnabled];
  end;

  case Kind of
    bpkSource: begin
      i := LastPos(PathDelim, Source);
      if i > 0 then
        s := Copy(Source, i+1, Length(Source))
      else
        s := Source;
      Instr := TLldbInstructionBreakSet.Create(s, Line, not en, Expression);
    end;
    bpkAddress: begin
      Instr := TLldbInstructionBreakSet.Create(Address, not en, Expression);
    end;
    bpkData: begin
      if not Enabled then // do not set, if not enabled
        exit;
      // TODO: scope
      // TODO: apply , Expression, not Enabled
      Instr := TLldbInstructionWatchSet.Create(WatchData, WatchKind);
      if Expression <> '' then
        FNeededChanges := FNeededChanges + [ciCondition];
    end;
  end;

  Instr.OnFinish := @InstructionSetBreakFinished;
  TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr);
  FCurrentInstruction := Instr;
end;

procedure TLldbBreakPoint.InstructionSetBreakFinished(Sender: TObject);
var
  nc: TDbgBpChangeIndicators;
begin
  DoCurrentInstructionFinished;

  if TLldbInstructionBreakOrWatchSet(Sender).LldbNoDisableError then begin
    TLldbDebugger(Debugger).FLldbMissingBreakSetDisable := True;
    FNeededChanges := FNeededChanges + [ciLocation]
  end;

  if TLldbInstructionBreakOrWatchSet(Sender).IsSuccess then begin
    FBreakID := TLldbInstructionBreakOrWatchSet(Sender).BreakId;
    if FNeededChanges * [ciDestroy, ciLocation] = [] then
      SetValid(TLldbInstructionBreakOrWatchSet(Sender).State);
  end
  else
    SetValid(vsInvalid);

  nc := FNeededChanges;
  FNeededChanges := [];
  MarkPropertiesChanged(nc);
end;

procedure TLldbBreakPoint.InstructionUpdateBreakFinished(Sender: TObject);
var
  nc: TDbgBpChangeIndicators;
begin
  DoCurrentInstructionFinished;

  nc := FNeededChanges;
  FNeededChanges := [];
  MarkPropertiesChanged(nc);
end;

procedure TLldbBreakPoint.ReleaseBreakPoint;
var
  Instr: TLldbInstruction;
begin
  CancelCurrentInstruction;

  if FBreakID <= 0 then exit;
  SetHitCount(0);

  case Kind of
  	bpkSource, bpkAddress:
      Instr := TLldbInstructionBreakDelete.Create(FBreakID);
    bpkData:
      Instr := TLldbInstructionWatchDelete.Create(FBreakID);
  end;
  FBreakID := 0; // Allow a new location to be set immediately

  //Instr.OwningCommand := Self;  // if it needs to be cancelled
  TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

procedure TLldbBreakPoint.UpdateProperties(AChanged: TDbgBpChangeIndicators);
var
  Instr: TLldbInstruction;
begin
  assert(AChanged * [ciEnabled, ciCondition] <> [], 'break.UpdateProperties() AChanged * [ciEnabled, ciCondition] <> []');

  if (FCurrentInstruction <> nil) then begin
    FNeededChanges := FNeededChanges + AChanged;
    exit;
  end;

  if FBreakID = 0 then // SetBreakPoint may have failed / nothing to do
    exit;

  case Kind of
  	bpkSource, bpkAddress:
      if ciCondition in AChanged
      then Instr := TLldbInstructionBreakModify.Create(FBreakID, not Enabled, Expression)
      else Instr := TLldbInstructionBreakModify.Create(FBreakID, not Enabled);
    bpkData:
    begin
      if Enabled <> (FBreakID <> 0) then begin
        if Enabled
        then SetBreakPoint // will
        else ReleaseBreakPoint;
        exit;
      end;
      if ciCondition in AChanged then
        Instr := TLldbInstructionWatchModify.Create(FBreakID, Expression);
    end;
  end;

  TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr);
  Instr.OnFinish := @InstructionUpdateBreakFinished;
  FCurrentInstruction := Instr;
end;

procedure TLldbBreakPoint.DoCurrentInstructionFinished;
begin
  if FCurrentInstruction <> nil then begin
    FCurrentInstruction.OnFinish := nil;
    ReleaseRefAndNil(FCurrentInstruction);
  end;
end;

procedure TLldbBreakPoint.CancelCurrentInstruction;
begin
  if FCurrentInstruction <> nil then begin
    FCurrentInstruction.OnFinish := nil;
    FCurrentInstruction.Cancel;
    ReleaseRefAndNil(FCurrentInstruction);
  end;
end;

procedure TLldbBreakPoint.DoStateChange(const AOldState: TDBGState);
begin
  inherited DoStateChange(AOldState);
  case DebuggerState of
    dsRun: if AOldState = dsInit then begin
      // Disabled data breakpoints: wait until enabled
      // Disabled other breakpoints: Give to LLDB to see if they are valid
      SetBreakPoint
    end;
    dsStop: begin
      if FBreakID > 0
      then ReleaseBreakpoint;
    end;
  end;
end;

procedure TLldbBreakPoint.DoPropertiesChanged(AChanged: TDbgBpChangeIndicators);
begin
  FNeededChanges := [];
  if not (DebuggerState in [dsPause, dsInternalPause, dsRun]) then
    exit;

  if ciDestroy in AChanged then begin
    ReleaseBreakPoint;
    DoCurrentInstructionFinished;
    exit;
  end;

  if AChanged * [ciLocation, ciCreated] <> [] then
    SetBreakPoint
  else
    UpdateProperties(AChanged);
end;

destructor TLldbBreakPoint.Destroy;
begin
  DoCurrentInstructionFinished;
  inherited Destroy;
end;

{ TLldbBreakPoints }

function TLldbBreakPoints.FindById(AnId: Integer): TLldbBreakPoint;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do begin
    Result := TLldbBreakPoint(Items[i]);
    if Result.FBreakID = AnId then
      exit;
  end;
  Result := nil;
end;

  {%region
    *****
    *****     Register
    ***** }

{ TLldbDebuggerCommandRegister }

procedure TLldbDebuggerCommandRegister.RegisterInstructionFinished(
  Sender: TObject);
var
  Instr: TLldbInstructionRegister absolute Sender;
  RegVal: TRegisterValue;
  n: String;
  i: Integer;
begin
  if not Instr.IsSuccess then begin
    if FRegisters.DataValidity in [ddsRequested, ddsEvaluating] then
      FRegisters.DataValidity := ddsInvalid;
    exit;
  end;

  FRegisters.DataValidity := ddsEvaluating;

  for i := 0 to Instr.Res.Count - 1 do begin
    n := Instr.Res.Names[i];
    RegVal := FRegisters.EntriesByName[n];
    RegVal.Value := Instr.Res.Values[n];
    RegVal.DataValidity := ddsValid;
  end;

  FRegisters.DataValidity := ddsValid;
  Finished;
end;

procedure TLldbDebuggerCommandRegister.DoExecute;
var
  Instr: TLldbInstructionRegister;
begin
  // TODO: store thread/frame when command is created
  Instr := TLldbInstructionRegister.Create(FRegisters.ThreadId, FRegisters.StackFrame);
  Instr.OnFinish := @RegisterInstructionFinished;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

procedure TLldbDebuggerCommandRegister.DoCancel;
begin
  if FRegisters <> nil then
    FRegisters.DataValidity := ddsInvalid;
  inherited DoCancel;
end;

constructor TLldbDebuggerCommandRegister.Create(AOwner: TLldbDebugger;
  ARegisters: TRegisters);
begin
  FRegisters := ARegisters;
  FRegisters.AddReference;
  CancelableForRun := True;
  inherited Create(AOwner);
end;

destructor TLldbDebuggerCommandRegister.Destroy;
begin
  ReleaseRefAndNil(FRegisters);
  inherited Destroy;
end;

{ TLldbRegisterSupplier }

procedure TLldbRegisterSupplier.Changed;
begin
  if CurrentRegistersList <> nil
  then CurrentRegistersList.Clear;
end;

procedure TLldbRegisterSupplier.RequestData(ARegisters: TRegisters);
var
  Cmd: TLldbDebuggerCommandRegister;
begin
  if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause, dsStop]) then
    exit;

  Cmd := TLldbDebuggerCommandRegister.Create(TLldbDebugger(Debugger), ARegisters);
  TLldbDebugger(Debugger).QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

  {%endregion   ^^^^^  Register  ^^^^^   }

{ TLldbDebuggerCommandQueue }

function TLldbDebuggerCommandQueue.Get(Index: Integer): TLldbDebuggerCommand;
begin
  Result := TLldbDebuggerCommand(inherited Get(Index));
end;

procedure TLldbDebuggerCommandQueue.Put(Index: Integer;
  const AValue: TLldbDebuggerCommand);
begin
  inherited Put(Index, AValue);
end;

procedure TLldbDebuggerCommandQueue.QueueCommand(AValue: TLldbDebuggerCommand);
begin
  debugln(DBG_VERBOSE, ['CommandQueue.QueueCommand ', AValue.ClassName]);
  Insert(Count, AValue);
  Run;
end;

procedure TLldbDebuggerCommandQueue.DoLineDataReceived(var ALine: String);
begin
  if FRunningCommand <> nil then
    FRunningCommand.DoLineDataReceived(ALine);
end;

procedure TLldbDebuggerCommandQueue.Run;
begin
  if (FRunningCommand <> nil) or (FLockQueueRun > 0) then
    exit;
  {$IFnDEF LLDB_SKIP_IDLE}
  if Count = 0 then begin
    if Assigned(FDebugger.OnIdle) and (not FDebugger.FInIdle) then begin
      FDebugger.FInIdle := True;
      LockQueueRun;
      FDebugger.OnIdle(Self);
      UnLockQueueRun;
      FDebugger.FInIdle := False;
    end;
    exit;
  end;
  {$ENDIF}

  FRunningCommand := Items[0];
  FRunningCommand.AddReference;
  Delete(0);
DebugLnEnter(DBG_VERBOSE, ['||||||||>>> CommandQueue.Run ', FRunningCommand.ClassName, ', ', dbgs(fDebugger.State), ' Cnt:',Count]);
  FRunningCommand.Execute;
  // debugger and queue may get destroyed at the end of execute
end;

procedure TLldbDebuggerCommandQueue.CommandFinished(
  ACommand: TLldbDebuggerCommand);
begin
  if FRunningCommand = ACommand then begin
DebugLnExit(DBG_VERBOSE, ['||||||||<<< CommandQueue.Run ', FRunningCommand.ClassName, ', ', dbgs(fDebugger.State), ' Cnt:',Count]);
    ReleaseRefAndNil(FRunningCommand);
  end//;
else DebugLn(DBG_VERBOSE, ['|||||||| TLldbDebuggerCommandQueue.CommandFinished >> unknown ???', ', ', dbgs(fDebugger.State), ' Cnt:',Count]);
  if not(FDebugger.State in [dsError, dsDestroying, dsNone]) then
    Run;
end;

constructor TLldbDebuggerCommandQueue.Create(ADebugger: TLldbDebugger);
begin
  FDebugger := ADebugger;
  inherited Create;
end;

destructor TLldbDebuggerCommandQueue.Destroy;
begin
  while Count > 0 do
    Delete(0);
  if FRunningCommand <> nil then begin
DebugLnExit(DBG_VERBOSE, ['<<< CommandQueue.Run (Destroy)', FRunningCommand.ClassName, ', ', fDebugger.State]);
    ReleaseRefAndNil(FRunningCommand);
  end;
  inherited Destroy;
end;

procedure TLldbDebuggerCommandQueue.CancelAll;
var
  i: Integer;
begin
  i := Count - 1;
  while i >= 0 do begin
    Items[i].Cancel;
    dec(i);
    if i > Count then
      i := Count - 1;
  end;
  if FRunningCommand <> nil then
    FRunningCommand.Cancel;
end;

procedure TLldbDebuggerCommandQueue.CancelForRun;
var
  i: Integer;
begin
  i := Count - 1;
  while i >= 0 do begin
    if Items[i].CancelableForRun then
      Items[i].Cancel;
    dec(i);
    if i > Count then
      i := Count - 1;
  end;
  if (FRunningCommand <> nil) and (FRunningCommand.CancelableForRun) then
    FRunningCommand.Cancel;
end;

procedure TLldbDebuggerCommandQueue.LockQueueRun;
begin
  inc(FLockQueueRun);
  debugln(DBG_VERBOSE, ['TLldbDebuggerCommandQueue.LockQueueRun ',FLockQueueRun]);
end;

procedure TLldbDebuggerCommandQueue.UnLockQueueRun;
begin
  debugln(DBG_VERBOSE, ['TLldbDebuggerCommandQueue.UnLockQueueRun ',FLockQueueRun]);
  dec(FLockQueueRun);
  if FLockQueueRun = 0 then Run;
end;

{ TLldbDebuggerCommand }

function TLldbDebuggerCommand.GetDebuggerState: TDBGState;
begin
  Result := Debugger.State;
end;

procedure TLldbDebuggerCommand.InstructionSucceeded(AnInstruction: TObject);
begin
  Finished;
end;

procedure TLldbDebuggerCommand.InstructionFailed(AnInstruction: TObject);
begin
  SetDebuggerState(dsError);
  Finished;
end;

procedure TLldbDebuggerCommand.Finished;
begin
  InstructionQueue.CancelAllForCommand(Self); // in case there still are any
  CommandQueue.CommandFinished(Self);
end;

function TLldbDebuggerCommand.GetCommandQueue: TLldbDebuggerCommandQueue;
begin
  Result := Debugger.FCommandQueue;
end;

function TLldbDebuggerCommand.GetInstructionQueue: TLldbInstructionQueue;
begin
  Result := Debugger.FDebugInstructionQueue;
end;

procedure TLldbDebuggerCommand.QueueInstruction(AnInstruction: TLldbInstruction);
begin
  AnInstruction.OwningCommand := Self;
  InstructionQueue.QueueInstruction(AnInstruction);
end;

procedure TLldbDebuggerCommand.SetDebuggerState(const AValue: TDBGState);
begin
  Debugger.SetState(AValue);
end;

constructor TLldbDebuggerCommand.Create(AOwner: TLldbDebugger);
begin
  FOwner := AOwner;
  inherited Create;
  AddReference;
end;

destructor TLldbDebuggerCommand.Destroy;
begin
  if InstructionQueue <> nil then
    InstructionQueue.CancelAllForCommand(Self);
  inherited Destroy;
end;

procedure TLldbDebuggerCommand.Execute;
var
  d: TLldbDebugger;
begin
  FIsRunning := True;
  d := Debugger;
  try
    AddReference;
    d.LockRelease;
    DoExecute;  // may call Finished and Destroy Self
  finally
    d.UnlockRelease;
    ReleaseReference;
  end;
end;

procedure TLldbDebuggerCommand.Cancel;
begin
  AddReference;
  Debugger.CommandQueue.Remove(Self); // current running command is not on queue // dec refcount, may call destroy
  if FIsRunning then
    DoCancel;  // should call CommandQueue.CommandFinished
  ReleaseReference;
end;

procedure TLldbDebuggerCommand.DoLineDataReceived(var ALine: String);
begin
  //
end;

procedure TLldbDebuggerCommand.DoCancel;
begin
  //
end;

{ TLldbDebuggerCommandInit }

procedure TLldbDebuggerCommandInit.DoExecute;
const
  FRAME_INFO =
    '${frame.pc}, {${frame.sp}}, {${frame.fp}}' +
    ' &&//FULL: {${line.file.fullpath}} &&//SHORT: {${line.file.basename}} &&//LINE: {${line.number}}' +
    ' &&//MOD: {${module.file.basename}} &&//FUNC: {${function.name-with-args}}' +
    ' <<&&//FRAME';
var
  Instr: TLldbInstruction;
begin
  Instr := TLldbInstructionSettingSet.Create('frame-format',
    '"frame #${frame.index}: ' + FRAME_INFO + '\n"'
  );
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionSettingSet.Create('thread-format',
    '"thread #${thread.index}: tid=${thread.id%tid}: ' + FRAME_INFO +
    //'{, activity = ''${thread.info.activity.name}''}{, ${thread.info.trace_messages} messages}' +
    '{, stop reason = ${thread.stop-reason}}' +
    //'{\nReturn value: ${thread.return-value}}{\nCompleted expression: ${thread.completed-expression}}' +
    '\n"'
  );
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  // Not all versions of lldb have this
  Instr := TLldbInstructionSettingSet.Create('thread-stop-format',
    '"thread #${thread.index}: tid=${thread.id%tid}: ' + FRAME_INFO +
    //'{, activity = ''${thread.info.activity.name}''}{, ${thread.info.trace_messages} messages}' +
    '{, stop reason = ${thread.stop-reason}}' +
    //'{\nReturn value: ${thread.return-value}}{\nCompleted expression: ${thread.completed-expression}}' +
    '\n"'
  );
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionTargetStopHook.Create('thread list');
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionSettingSet.Create('stop-line-count-after', '0');
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionSettingSet.Create('stop-line-count-before', '0');
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionSettingSet.Create('stop-disassembly-count', '0');
  Instr.OnFinish := @InstructionSucceeded;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

procedure TLldbDebuggerCommandInit.DoLineDataReceived(var ALine: String);
begin
  inherited DoLineDataReceived(ALine);
  if FGotLLDB then
    exit;
  if TLldbDebuggerProperties(Debugger.GetProperties).SkipGDBDetection then
    FGotLLDB := True
  else
  if StrContains(UpperCase(ALine), 'LLDB') then
    FGotLLDB := True
  else
  if StrContains(UpperCase(ALine), '(GDB)') then
    Debugger.SetErrorState('GDB detected', 'The external debugger identified itself as GDB. The IDE expected LLDB.');
end;

{ TLldbDebuggerCommandRunStep }

procedure TLldbDebuggerCommandRunStep.DoInitialExecute;
begin
  SetNextStepCommand(FStepAction);
end;

constructor TLldbDebuggerCommandRunStep.Create(AOwner: TLldbDebugger;
  AStepAction: TLldbInstructionProcessStepAction);
var
  AtExcepiton: Boolean;
begin
  AtExcepiton := AOwner.FExceptionInfo.FAtExcepiton;
  FStepAction := AStepAction;
  inherited Create(AOwner);
  if AtExcepiton and
     (AStepAction in [saOver, saInto, saOut])
  then
    FMode := cmRunToCatch;
end;

{ TLldbDebuggerCommandRunLaunch }

procedure TLldbDebuggerCommandRunLaunch.TargetCreated(Sender: TObject);
var
  TargetInstr: TLldbInstructionTargetCreate absolute Sender;
  Instr: TLldbInstruction;
  found: TStringArray;
begin
  if not TargetInstr.IsSuccess then begin
    SetDebuggerState(dsError);
    Finished;
  end;
  CollectDwarfLoadErrors(Sender);

  If StrMatches(TargetInstr.Res, [''{}, '','('{}, ')',''], found) then begin
    if (found[1] = 'i386') or (found[1] = 'i686') then begin
      DebugLn(DBG_VERBOSE, ['Target 32 bit: ', found[1]]);
      Debugger.FTargetWidth := 32;
      Debugger.FTargetRegisters[0] := '$eax';
      Debugger.FTargetRegisters[1] := '$edx';
      Debugger.FTargetRegisters[2] := '$ecx';
    end
    else
    if StrContains(found[1], 'arm64') then begin
      DebugLn(DBG_VERBOSE, ['Target ARM/M1: ', found[1]]);
      Debugger.FTargetWidth := 64;
      Debugger.FTargetRegisters[0] := '$x0';
      Debugger.FTargetRegisters[1] := '$x1';
      Debugger.FTargetRegisters[2] := '$x2';
    end
    else
    if (found[1] = '(x86_64)') or  (found[1] = 'x86_64') then begin
      DebugLn(DBG_VERBOSE, ['Target 64 bit: ', found[1]]);
      Debugger.FTargetWidth := 64;
      // target list  gives more detailed result. But until remote debugging is added, use the current system
      {$IFDEF MSWindows}
      Debugger.FTargetRegisters[0] := '$rcx';
      Debugger.FTargetRegisters[1] := '$rdx';
      Debugger.FTargetRegisters[2] := '$r8';
      {$ELSE}
      Debugger.FTargetRegisters[0] := '$rdi';
      Debugger.FTargetRegisters[1] := '$rsi';
      Debugger.FTargetRegisters[2] := '$rdx';
      {$ENDIF}
    end
    else found := nil;
  end
  else found := nil;
  if found = nil then begin
    DebugLn(DBG_VERBOSE, ['Target bitness UNKNOWN']);
    // use architecture of IDE
    {$IFDEF cpu64}
    Debugger.FTargetWidth := 64;
    {$IFDEF MSWindows}
    Debugger.FTargetRegisters[0] := '$rcx';
    Debugger.FTargetRegisters[1] := '$rdx';
    Debugger.FTargetRegisters[2] := '$r8';
    {$ELSE}
    Debugger.FTargetRegisters[0] := '$rdi';
    Debugger.FTargetRegisters[1] := '$rsi';
    Debugger.FTargetRegisters[2] := '$rdx';
    {$ENDIF}
    {$ELSE}
    Debugger.FTargetWidth := 32;
    Debugger.FTargetRegisters[0] := '$eax';
    Debugger.FTargetRegisters[1] := '$edx';
    Debugger.FTargetRegisters[2] := '$ecx';
    {$ENDIF}
  end;

  if Trim(Debugger.Arguments) <> '' then
    Instr := TLldbInstructionSettingSet.Create('target.run-args', Debugger.Arguments)
  else
    Instr := TLldbInstructionSettingClear.Create('target.run-args');
  Instr.OnFinish := @CollectDwarfLoadErrors;
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Debugger.FBreakErrorBreak.OnFinish := @CollectDwarfLoadErrors;
  Debugger.FRunErrorBreak.OnFinish   := @CollectDwarfLoadErrors;
  Debugger.FExceptionBreak.OnFinish  := @ExceptBreakInstructionFinished;
  Debugger.FBreakErrorBreak.Enable;
  Debugger.FRunErrorBreak.Enable;
  Debugger.FExceptionBreak.Enable;
end;

procedure TLldbDebuggerCommandRunLaunch.CollectDwarfLoadErrors(Sender: TObject);
begin
  if Sender is TlldbInternalBreakPoint then begin
    FLaunchWarnings := FLaunchWarnings + TlldbInternalBreakPoint(Sender).DwarfLoadErrors;
    TlldbInternalBreakPoint(Sender).OnFinish := nil;
  end
  else
    FLaunchWarnings := FLaunchWarnings + TLldbInstruction(Sender).DwarfLoadErrors;
end;

procedure TLldbDebuggerCommandRunLaunch.ExceptBreakInstructionFinished(Sender: TObject
  );
var
  Instr: TLldbInstruction;
  BrkId: Integer;
begin
  CollectDwarfLoadErrors(Sender);
  Debugger.FBreakErrorBreak.OnFinish := nil;

  Debugger.FExceptionInfo.FReg0Cmd := '';
  Debugger.FExceptionInfo.FReg2Cmd := '';
  Debugger.FExceptionInfo.FExceptClassCmd := '';
  Debugger.FExceptionInfo.FExceptMsgCmd := '';

  BrkId := Debugger.FExceptionBreak.BreakId;
  if BrkId > 0 then begin
    Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0];
    Debugger.FExceptionInfo.FReg2Cmd := 'p/x ' + Debugger.FTargetRegisters[2];
    Debugger.FExceptionInfo.FExceptClassCmd := 'p ((char ***)' + Debugger.FTargetRegisters[0] + ')[0][3]';
    Debugger.FExceptionInfo.FExceptMsgCmd := 'p ((char **)' + Debugger.FTargetRegisters[0] + ')[1]';
                  // 'p ((EXCEPTION *)' + Debugger.FTargetRegisters[0] + ')->FMESSAGE'

    Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [
      Debugger.FExceptionInfo.FReg0Cmd, Debugger.FExceptionInfo.FReg2Cmd, Debugger.FExceptionInfo.FExceptClassCmd, Debugger.FExceptionInfo.FExceptMsgCmd
    ]);
    Instr.OnFinish := @CollectDwarfLoadErrors;
    QueueInstruction(Instr);
    Instr.ReleaseReference;
  end;

  BrkId := Debugger.FRunErrorBreak.BreakId;
  if BrkId > 0 then begin
    Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0];
    Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [Debugger.FExceptionInfo.FReg0Cmd]);
    Instr.OnFinish := @CollectDwarfLoadErrors;
    QueueInstruction(Instr);
    Instr.ReleaseReference;
  end;

  BrkId := Debugger.FBreakErrorBreak.BreakId;
  if BrkId > 0 then begin
    Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0];
    Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [Debugger.FExceptionInfo.FReg0Cmd]);
    Instr.OnFinish := @CollectDwarfLoadErrors;
    QueueInstruction(Instr);
    Instr.ReleaseReference;
  end;

  SetDebuggerState(dsRun);
  // the state change allows breakpoints to be set, before the run command is issued.

  FRunInstr := TLldbInstructionProcessLaunch.Create(TLldbDebuggerProperties(Debugger.GetProperties).LaunchNewTerminal);
  FRunInstr.OnSuccess := @LaunchInstructionSucceeded;
  FRunInstr.OnFailure := @InstructionFailed;
  QueueInstruction(FRunInstr);
  FRunInstr.ReleaseReference;
end;

procedure TLldbDebuggerCommandRunLaunch.LaunchInstructionSucceeded(Sender: TObject);
begin
  CollectDwarfLoadErrors(Sender);
  Debugger.DoAfterLaunch(FLaunchWarnings);
  if (not TLldbDebuggerProperties(Debugger.GetProperties).IgnoreLaunchWarnings) and
     (FLaunchWarnings <> '') and
     assigned(Debugger.OnFeedback)
  then begin
    case Debugger.OnFeedback(self,
             Format('The debugger encountered some errors/warnings while launching the target application.%0:s'
               + 'Press "Ok" to continue debugging.%0:s'
               + 'Press "Stop" to end the debug session.',
               [LineEnding]),
             FLaunchWarnings, ftWarning, [frOk, frStop]
         ) of
      frOk: begin
        end;
      frStop: begin
          Debugger.Stop;
        end;
    end;
  end;
  RunInstructionSucceeded(Sender);
end;

procedure TLldbDebuggerCommandRunLaunch.DoInitialExecute;
var
  Instr: TLldbInstruction;
begin
  Instr := TLldbInstructionTargetCreate.Create(Debugger.FileName);
  Instr.OnSuccess := @TargetCreated;
  Instr.OnFailure := @InstructionFailed;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

constructor TLldbDebuggerCommandRunLaunch.Create(AOwner: TLldbDebugger);
begin
  FStepAction := saContinue;
  inherited Create(AOwner);
end;

{ TLldbDebuggerCommandStop }

procedure TLldbDebuggerCommandStop.StopInstructionSucceeded(Sender: TObject);
begin
  if DebuggerState <> dsIdle then
    SetDebuggerState(dsStop);
end;

procedure TLldbDebuggerCommandStop.DoExecute;
var
  Instr: TLldbInstruction;
begin
  Instr := TLldbInstructionProcessKill.Create();
  Instr.OnSuccess := @StopInstructionSucceeded;
  Instr.OnFailure := @InstructionFailed;
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionTargetDelete.Create();
  Instr.OnFailure := @InstructionFailed;
  QueueInstruction(Instr);
  Instr.ReleaseReference;

  Instr := TLldbInstructionTargetDelete.Create();
  Instr.OnSuccess := @InstructionSucceeded;
  Instr.OnFailure := @InstructionFailed;
  QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

{ TLldbDebuggerCommandEvaluate }

procedure TLldbDebuggerCommandEvaluate.EvalInstructionSucceeded(Sender: TObject
  );
begin
  if FWatchValue <> nil then begin
    FWatchValue.Value := FInstr.Res;
    //FWatchValue.TypeInfo := TypeInfo;
    FWatchValue.Validity := ddsValid;
  end
  else
  if FCallback <> nil then
    FCallback(Debugger, True, FInstr.Res, nil);

  FInstr.ReleaseReference;
  Finished;
end;

procedure TLldbDebuggerCommandEvaluate.EvalInstructionFailed(Sender: TObject);
begin
  if FWatchValue <> nil then
    FWatchValue.Validity := ddsError
  else
  if FCallback <> nil then
    FCallback(Debugger, False, '', nil);
  FInstr.ReleaseReference;
  Finished;
end;

procedure TLldbDebuggerCommandEvaluate.DoWatchFreed(Sender: TObject);
begin
  FWatchValue := nil;
end;

procedure TLldbDebuggerCommandEvaluate.DoExecute;
begin
  if FWatchValue <> nil then
    FInstr := TLldbInstructionExpression.Create(FWatchValue.Expression, FWatchValue.ThreadId, FWatchValue.StackFrame)
  else
    // todo: only if FCallback ?
    FInstr := TLldbInstructionExpression.Create(FExpr, Debugger.FCurrentThreadId, Debugger.FCurrentStackFrame);
  FInstr.OnSuccess := @EvalInstructionSucceeded;
  FInstr.OnFailure := @EvalInstructionFailed;
  QueueInstruction(FInstr);
end;

constructor TLldbDebuggerCommandEvaluate.Create(AOwner: TLldbDebugger;
  AWatchValue: IDbgWatchValueIntf);
begin
  FWatchValue := AWatchValue;
  FWatchValue.AddFreeNotification(@DoWatchFreed);
  CancelableForRun := True;
  inherited Create(AOwner);
end;

constructor TLldbDebuggerCommandEvaluate.Create(AOwner: TLldbDebugger;
  AnExpr: String; AFlags: TWatcheEvaluateFlags;
  ACallback: TDBGEvaluateResultCallback);
begin
  FExpr := AnExpr;
  FFlags := AFlags;
  FCallback := ACallback;
  CancelableForRun := True;
  inherited Create(AOwner);
end;

destructor TLldbDebuggerCommandEvaluate.Destroy;
begin
  if FWatchValue <> nil then
    FWatchValue.RemoveFreeNotification(@DoWatchFreed);
  inherited Destroy;
end;

{ TlldbInternalBreakPoint }

procedure TlldbInternalBreakPoint.QueueInstruction(AnInstr: TLldbInstruction);
begin
  AnInstr.OnFinish := @DoFinished;
  FDebugger.DebugInstructionQueue.QueueInstruction(AnInstr);
  AnInstr.ReleaseReference;
end;

procedure TlldbInternalBreakPoint.BreakSetSuccess(Sender: TObject);
begin
  FId := TLldbInstructionBreakSet(Sender).BreakId;
end;

procedure TlldbInternalBreakPoint.DoFailed(Sender: TObject);
begin
  if FId = 0 then
    FId := -1;
  if OnFail <> nil then
    OnFail(Self);
end;

procedure TlldbInternalBreakPoint.DoFinished(Sender: TObject);
begin
  FDwarfLoadErrors := TLldbInstruction(Sender).DwarfLoadErrors;
  if OnFinish <> nil then
    OnFinish(Self);
end;

constructor TlldbInternalBreakPoint.Create(AName: String;
  ADebugger: TLldbDebugger; ABeforePrologue: Boolean);
begin
  FName := AName;
  FDebugger := ADebugger;
  FBeforePrologue := ABeforePrologue;
  FId := 0;
  inherited Create;
end;

destructor TlldbInternalBreakPoint.Destroy;
begin
  Remove;
  inherited Destroy;
end;

procedure TlldbInternalBreakPoint.Enable;
var
  Instr: TLldbInstruction;
begin
  if FId = 0 then begin
    Instr := TLldbInstructionBreakSet.Create(FName, False, FBeforePrologue);
    Instr.OnSuccess := @BreakSetSuccess;
    Instr.OnFailure := @DoFailed;
    QueueInstruction(Instr);
    exit;
  end;

  if FId < 0 then begin
    DoFailed(nil);
    exit;
  end;

  Instr := TLldbInstructionBreakModify.Create(FId, False);
  Instr.OnFailure := @DoFailed;
  QueueInstruction(Instr);
end;

procedure TlldbInternalBreakPoint.Disable;
var
  Instr: TLldbInstruction;
begin
  if FId <= 0 then
    exit;

  Instr := TLldbInstructionBreakModify.Create(FId, True);
  Instr.OnFailure := @DoFailed;
  QueueInstruction(Instr);
end;

procedure TlldbInternalBreakPoint.Remove;
var
  Instr: TLldbInstruction;
begin
  if FId <= 0 then
    exit;

  Instr := TLldbInstructionBreakDelete.Create(FId);
  QueueInstruction(Instr);
  FId := 0;
end;

{ TLldbDebugger }

function TLldbDebugger.LldbRun: Boolean;
var
  Cmd: TLldbDebuggerCommandRunLaunch;
begin
  DebugLn(DBG_VERBOSE, '*** Run');
  Result := True;

  if State in [dsPause, dsInternalPause, dsRun] then begin // dsRun in case of exception
    CommandQueue.CancelForRun;
    LldbStep(saContinue);
    exit;
  end;

  if State in [dsNone, dsIdle, dsStop] then
    SetState(dsInit);

  FInIdle := False;

  DoBeforeLaunch;

  Cmd := TLldbDebuggerCommandRunLaunch.Create(Self);
  QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

procedure TLldbDebugger.DoAfterLineReceived(var ALine: String);
var
  Instr: TLldbInstruction;
begin
  if ALine = '' then
    exit;

  FCommandQueue.DoLineDataReceived(ALine);
  if ALine = '' then
    exit;


  // Process 8888 exited with status = 0 (0x00000000)
  if (LeftStr(ALine, 8) = 'Process ') and (pos('exited with status = ', ALine) > 0) then begin
// todo: target delete
    if State <> dsIdle then
      SetState(dsStop);
    ALine := '';

    Instr := TLldbInstructionTargetDelete.Create();
    FDebugInstructionQueue.QueueInstruction(Instr);
    Instr.ReleaseReference;
    exit;
  end;

end;

procedure TLldbDebugger.DoBeforeLineReceived(var ALine: String);
begin
  DoDbgOutput(ALine);
end;

procedure TLldbDebugger.DoBeginReceivingLines(Sender: TObject);
begin
  LockRelease;
end;

procedure TLldbDebugger.DoCmdLineDebuggerTerminated(Sender: TObject);
begin
  SetState(dsError);
end;

procedure TLldbDebugger.DoLineSentToDbg(Sender: TObject; ALine: String);
begin
  DoDbgOutput('>> '+ALine);
end;

procedure TLldbDebugger.DoEndReceivingLines(Sender: TObject);
begin
  UnlockRelease;
end;

function TLldbDebugger.LldbStep(AStepAction: TLldbInstructionProcessStepAction
  ): Boolean;
var
  Cmd: TLldbDebuggerCommandRunStep;
begin
  Result := True;
  CommandQueue.CancelForRun;
  Cmd := TLldbDebuggerCommandRunStep.Create(Self, AStepAction);
  QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

function TLldbDebugger.LldbStop: Boolean;
var
  Cmd: TLldbDebuggerCommandStop;
begin
  DebugLn(DBG_VERBOSE, '*** Stop');
  Result := True;

  CommandQueue.CancelAll;
  Cmd := TLldbDebuggerCommandStop.Create(Self);
  QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

function TLldbDebugger.LldbPause: Boolean;
var
  Instr: TLldbInstruction;
begin
  Result := True;
  Instr := TLldbInstructionProcessInterrupt.Create();
  FDebugInstructionQueue.QueueInstruction(Instr);
  Instr.ReleaseReference;
end;

function TLldbDebugger.LldbEvaluate(const AExpression: String;
  EvalFlags: TWatcheEvaluateFlags; ACallback: TDBGEvaluateResultCallback): Boolean;
var
  Cmd: TLldbDebuggerCommandEvaluate;
begin
  Cmd := TLldbDebuggerCommandEvaluate.Create(Self, AExpression, EvalFlags, ACallback);
  QueueCommand(Cmd);
  Cmd.ReleaseReference;
  Result := True;
end;

function TLldbDebugger.LldbEnvironment(const AVariable: String;
  const ASet: Boolean): Boolean;
var
  Instr: TLldbInstruction;
  s: String;
begin
  debugln(DBG_VERBOSE, ['-----------------------------------------', AVariable]);
  if ASet then
    Instr := TLldbInstructionSettingSet.Create('target.env-vars', AVariable, False, True)
  else begin
    s := AVariable;
    Instr := TLldbInstructionSettingRemove.Create('target.env-vars', GetPart([], ['='], s, False, False));
  end;

  FDebugInstructionQueue.QueueInstruction(Instr);
  Instr.ReleaseReference;
  Result := True;
end;

procedure TLldbDebugger.TerminateLldb;
begin
  if FDebugProcess.DebugProcessRunning then begin
    FDebugProcess.SendCmdLn('process kill');
    FDebugProcess.SendCmdLn('quit');
    Sleep(100);
  end;
  FDebugInstructionQueue.OnDebuggerTerminated := nil;  // TODO: use a flag to prevent this
  FDebugProcess.StopDebugProcess;
  FDebugInstructionQueue.OnDebuggerTerminated := @DoCmdLineDebuggerTerminated;
end;

procedure TLldbDebugger.DoBeforeLaunch;
begin
  //
end;

procedure TLldbDebugger.DoAfterLaunch(var LaunchWarnings: string);
begin
  //
end;

procedure TLldbDebugger.LockRelease;
begin
  inherited LockRelease;
end;

procedure TLldbDebugger.UnlockRelease;
begin
  inherited UnlockRelease;
end;

procedure TLldbDebugger.QueueCommand(const ACommand: TLldbDebuggerCommand);
begin
  FCommandQueue.QueueCommand(ACommand);
end;

procedure TLldbDebugger.SetErrorState(const AMsg: String; const AInfo: String);
begin
  inherited SetErrorState(AMsg, AInfo);
end;

procedure TLldbDebugger.DoState(const OldState: TDBGState);
begin
  inherited DoState(OldState);
  if (State = dsError) then
    TerminateLldb;
end;

function TLldbDebugger.DoExceptionHit(AExcClass, AExcMsg: String): Boolean;
begin
  if Assigned(EventLogHandler) then
    EventLogHandler.LogCustomEvent(ecDebugger, etExceptionRaised,
      Format('Exception class "%s" at $%.' + IntToStr(TargetWidth div 4) + 'x with message "%s"',
      [AExcClass, FCurrentLocation.Address, AExcMsg]));

  if Assigned(OnException) then
    OnException(Self, deInternal, AExcClass, FCurrentLocation, AExcMsg, Result) // TODO: Location
  else
    Result := True; // CanContinue
end;

function TLldbDebugger.DoBreakpointHit(BrkId: Integer): Boolean;
var
  BreakPoint: TLldbBreakPoint;
begin
  if (BrkId >= 0) then
    BreakPoint := TLldbBreakPoints(BreakPoints).FindById(BrkId)
  else
    BreakPoint := nil;

  if Assigned(EventLogHandler) then
    EventLogHandler.LogEventBreakPointHit(Breakpoint, FCurrentLocation);

  if BreakPoint <> nil then begin
    if (BreakPoint.Valid = vsPending) then
      BreakPoint.SetPendingToValid(vsValid);

    try
      BreakPoint.AddReference;

      // Important: The Queue must be unlocked
      //   BreakPoint.Hit may evaluate stack and expressions
      //   SetDebuggerState may evaluate data for Snapshot
      Result := False; // Result;
      BreakPoint.Hit(Result);
    finally
      BreakPoint.ReleaseReference;
    end;

  end
  else
  if (State = dsRun)
  then begin
    debugln(DBG_VERBOSE, ['********** WARNING: breakpoint hit, but nothing known about it ABreakId=', BrkId]);
  end;
end;

function TLldbDebugger.CreateBreakPoints: TDBGBreakPoints;
begin
  Result := TLldbBreakPoints.Create(Self, TLldbBreakPoint);
end;

function TLldbDebugger.CreateLocals: TLocalsSupplier;
begin
  Result := TLldbLocals.Create(Self);
end;

function TLldbDebugger.CreateRegisters: TRegisterSupplier;
begin
  Result := TLldbRegisterSupplier.Create(Self);
end;

function TLldbDebugger.CreateCallStack: TCallStackSupplier;
begin
  Result := TLldbCallStack.Create(Self);
end;

function TLldbDebugger.CreateWatches: TWatchesSupplier;
begin
  Result := TLldbWatches.Create(Self);
end;

function TLldbDebugger.CreateThreads: TThreadsSupplier;
begin
  Result := TLldbThreads.Create(Self);
end;

function TLldbDebugger.GetTargetWidth: Byte;
begin
  Result := FTargetWidth;
end;

function TLldbDebugger.GetIsIdle: Boolean;
begin
  Result := FInIdle or ( (CommandQueue.Count = 0) and (CommandQueue.RunningCommand = nil) );
end;

class function TLldbDebugger.GetSupportedCommands: TDBGCommands;
begin
  Result := [dcRun, dcStop, dcStepOver, dcStepInto, dcStepOut, dcEvaluate,
             dcStepOverInstr, dcStepIntoInstr, dcPause, dcEnvironment];
//  Result := [dcStepTo, dcAttach, dcDetach, dcJumpto,
//             dcBreak, dcWatch, dcLocal, dcEvaluate, dcModify,
//             dcSetStackFrame, dcDisassemble
//            ];
end;

function TLldbDebugger.RequestCommand(const ACommand: TDBGCommand;
  const AParams: array of const; const ACallback: TMethod): Boolean;
var
  EvalFlags: TWatcheEvaluateFlags;
begin
  LockRelease;
  try
    case ACommand of
      dcRun:         Result := LldbRun;
      dcPause:       Result := LldbPause;
      dcStop:        Result := LldbStop;
      dcStepOver:    Result := LldbStep(saOver);
      dcStepInto:    Result := LldbStep(saInto);
      dcStepOut:     Result := LldbStep(saOut);
      dcStepOverInstr: Result := LldbStep(saInsOver);
      dcStepIntoInstr: Result := LldbStep(saInsIn);
      dcEvaluate:    begin
                       EvalFlags := [];
                       if high(AParams) >= 1 then
                         EvalFlags := TWatcheEvaluateFlags(AParams[1].VInteger);
                       Result := LldbEvaluate(String(AParams[0].VAnsiString),
                         EvalFlags, TDBGEvaluateResultCallback(ACallback));
                     end;
//      dcStepTo:       Result := GDBRunTo(String(AParams[0].VAnsiString), AParams[1].VInteger);
//      dcJumpto:      Result := GDBJumpTo(String(AParams[0].VAnsiString), AParams[1].VInteger);
//      dcAttach:      Result := GDBAttach(String(AParams[0].VAnsiString));
//      dcDetach:      Result := GDBDetach;
//      dcModify:      Result := GDBModify(String(AParams[0].VAnsiString), String(AParams[1].VAnsiString));
      dcEnvironment: Result := LldbEnvironment(String(AParams[0].VAnsiString), AParams[1].VBoolean);
//      dcDisassemble: Result := GDBDisassemble(AParams[0].VQWord^, AParams[1].VBoolean, TDbgPtr(AParams[2].VPointer^),
//                                              String(AParams[3].VPointer^), String(AParams[4].VPointer^),
//                                              String(AParams[5].VPointer^), Integer(AParams[6].VPointer^))
//                                              {%H-};
    end;
  finally
    UnlockRelease;
  end;
end;

class function TLldbDebugger.CreateProperties: TDebuggerProperties;
begin
  Result := TLldbDebuggerProperties.Create;
end;

class function TLldbDebugger.ExeBaseName: String;
begin
  Result := 'lldb';
end;

class function TLldbDebugger.Caption: String;
begin
  Result := 'LLDB Debugger (Alpha)';
end;

class function TLldbDebugger.ExePaths: String;
begin
  {$IFdef MSWindows}
  Result := '';
  {$ELSE}
  Result := '/usr/bin/lldb';
  {$ENDIF}
end;

class function TLldbDebugger.ExePathsMruGroup: TDebuggerClass;
begin
  Result := TLldbDebugger;
end;

constructor TLldbDebugger.Create(const AExternalDebugger: String);
begin
  inherited Create(AExternalDebugger);
  FDebugProcess := TDebugProcess.Create(AExternalDebugger);
  FDebugProcess.OnLineSent := @DoLineSentToDbg;

  FDebugInstructionQueue := TLldbInstructionQueue.Create(FDebugProcess);
  FDebugInstructionQueue.OnBeginLinesReceived := @DoBeginReceivingLines;
  FDebugInstructionQueue.OnEndLinesReceived := @DoEndReceivingLines;
  FDebugInstructionQueue.OnBeforeHandleLineReceived := @DoBeforeLineReceived;
  FDebugInstructionQueue.OnAfterHandleLineReceived := @DoAfterLineReceived;
  FDebugInstructionQueue.OnDebuggerTerminated := @DoCmdLineDebuggerTerminated;

  FCommandQueue := TLldbDebuggerCommandQueue.Create(Self);

  FBreakErrorBreak := TlldbInternalBreakPoint.Create('fpc_break_error', Self, True);
  FRunErrorBreak   := TlldbInternalBreakPoint.Create('fpc_runerror', Self, True);
  FExceptionBreak  := TlldbInternalBreakPoint.Create('fpc_raiseexception', Self, True);
  FPopExceptStack  := TlldbInternalBreakPoint.Create('fpc_popaddrstack', Self);
  FCatchesBreak    := TlldbInternalBreakPoint.Create('fpc_catches', Self);
  FReRaiseBreak    := TlldbInternalBreakPoint.Create('fpc_reraise', Self);
end;

destructor TLldbDebugger.Destroy;
begin
  debugln(DBG_VERBOSE, ['!!!!!!!!!!!!!!! TLldbDebugger.Destroy ']);
  FBreakErrorBreak.Remove;
  FRunErrorBreak.Remove;
  FExceptionBreak.Remove;
  FPopExceptStack.Remove;
  FCatchesBreak.Remove;
  FReRaiseBreak.Remove;
  FDebugInstructionQueue.LockQueueRun;
  inherited Destroy;
  FBreakErrorBreak.Destroy;
  FRunErrorBreak.Destroy;
  FExceptionBreak.Destroy;
  FPopExceptStack.Destroy;
  FCatchesBreak.Destroy;
  FReRaiseBreak.Destroy;
  FCommandQueue.Destroy;
  FDebugInstructionQueue.Destroy;
  FDebugProcess.Destroy;
end;

procedure TLldbDebugger.Init;
var
  Cmd: TLldbDebuggerCommandInit;
begin
  FDebugProcess.CreateDebugProcess('', Environment);
  inherited Init;

  Cmd := TLldbDebuggerCommandInit.Create(Self);
  QueueCommand(Cmd);
  Cmd.ReleaseReference;
end;

procedure TLldbDebugger.Done;
begin
  DebugLnEnter(DBG_VERBOSE, '!!! TLldbDebugger.Done;');
  // TODO: cancel all commands

  TerminateLldb;
  inherited Done;
  DebugLnExit(DBG_VERBOSE, '!!! TLldbDebugger.Done;');
end;

class function TLldbDebugger.RequiredCompilerOpts(ATargetCPU, ATargetOS: String
  ): TDebugCompilerRequirements;
begin
  Result:=[dcrDwarfOnly, dcrPreferDwarf2Sets];
end;

function TLldbDebugger.GetLocation: TDBGLocationRec;
begin
  Result := FCurrentLocation;
end;

function TLldbDebugger.NeedReset: Boolean;
begin
  Result := true;
end;

procedure TLldbDebugger.TestCmd(const ACommand: String);
begin
  FDebugProcess.SendCmdLn(ACommand);
end;

procedure Register;
begin
  //RegisterDebugger(TLldbDebugger);
end;

initialization
  RegisterDebugger(TLldbDebugger);

  DBG_VERBOSE       := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} );

end.

