-- DebugNub.mesa; edited by Sandman October 9, 1980  9:57 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [BYTE],
  BcplOps USING [BcplJSR],
  BFSDefs USING [MakeCFP],
  ControlDefs USING [
    ControlLink, FieldDescriptor, FrameHandle, GlobalFrameHandle, NullFrame,
    StateVector, SVPointer],
  CoreSwapDefs USING [
    BBArray, BBHandle, CallDP, DebugParameter, VersionID, ExternalStateVector,
    level, PuntInfo, PuntTable, StartDP, SVPointer, SwapReason, UBBPointer,
    UserBreakBlock],
  DiskDefs USING [RealDA],
  ForgotDefs USING [BitmapDS],
  FrameDefs USING [LockCode, UnlockCode, UnNew],
  FrameOps USING [
    GetReturnFrame, GetReturnLink, MyLocalFrame, SetReturnFrame, SetReturnLink],
  ImageDefs USING [
    AbortMesa, AddCleanupProcedure, AddFileRequest, AllReasons, CleanupItem,
    CleanupMask, CleanupProcedure, FileRequest, PuntMesa, StopMesa,
    UserCleanupProc],
  KeyDefs USING [Keys],
  LoadStateOps USING [state],
  MiscOps USING [BitmapPages],
  Mopcodes USING [zRFS],
  NubOps USING [Place],
  NucleusOps USING [Wart],
  ProcessDefs USING [DisableInterrupts, EnableInterrupts],
  SDDefs USING [
    sBreakBlock, sBreakBlockSize, sCallDebugger, sCoreSwap, SD, sInterrupt,
    sProcessBreakpoint, sUncaughtSignal],
  SegmentDefs USING [
    DataSegmentHandle, DefaultXMBase, DeleteDataSegment, DeleteFileSegment,
    FileHandle, FileSegmentHandle, GetFileSegmentDA, InsufficientVM,
    LongDataSegmentAddress, LongVMtoDataSegment, MakeDataSegment, memConfig,
    NewFileSegment, Read, ReleaseFile, UnlockFile],
  SwapperOps USING [systemTable],
  Storage USING [Node, CopyString, Free, FreeString];

DebugNub: PROGRAM [user: PROGRAM]
  IMPORTS
    BFSDefs, CoreSwapDefs, DiskDefs, FrameDefs, FrameOps, ImageDefs, LoadStateOps,
    BcplOps, NucleusOps, ProcessDefs, SegmentDefs, SwapperOps, Storage
  EXPORTS CoreSwapDefs, MiscOps, NubOps
  SHARES SegmentDefs, ControlDefs =
  BEGIN OPEN CoreSwapDefs, SegmentDefs;

  WhereAmI: PUBLIC PROCEDURE RETURNS [NubOps.Place] = {RETURN[normal]};

  FrameHandle: TYPE = ControlDefs.FrameHandle;
  SVPointer: TYPE = ControlDefs.SVPointer;

  ProcessBreakpoint: PROCEDURE [s: SVPointer] =
    BEGIN -- called by BRK trap handler in resident code
    inst: AltoDefs.BYTE;
    swap: BOOLEAN;
    IF ~Swappable THEN BEGIN SwatBreak[s]; RETURN END;
    [inst, swap] ← DoBreakpoint[s];
    IF swap THEN
      BEGIN
      FrameDefs.LockCode[s.dest];
      CoreSwap[breakpoint, s];
      FrameDefs.UnlockCode[s.dest];
      END
    ELSE s.instbyte ← inst; --replant the instruction and go on
    RETURN
    END;

  DoBreakpoint: PROCEDURE [s: SVPointer] RETURNS [AltoDefs.BYTE, BOOLEAN] =
    BEGIN OPEN ControlDefs;
    ubb: CoreSwapDefs.UBBPointer;
    bba: BBHandle = SDDefs.SD[SDDefs.sBreakBlock];
    i: CARDINAL;
    l: FrameHandle ← s.dest;
    FOR i IN [0..bba.length) DO
      ubb ← @bba.blocks[i];
      IF ubb.frame = l.accesslink AND ubb.pc = l.pc THEN
	IF TrueCondition[ubb, l, s] THEN EXIT ELSE RETURN[ubb.inst, FALSE];
      ENDLOOP;
    RETURN[0, TRUE];
    END;

  TrueCondition: PROCEDURE [ubb: CoreSwapDefs.UBBPointer, base: POINTER, s: SVPointer]
    RETURNS [BOOLEAN] =
    BEGIN --decide whether to take the breakpoint
    fd: ControlDefs.FieldDescriptor;
    locL, locR: POINTER;
    left, right: UNSPECIFIED;
    IF ubb.counterL THEN
      IF (ubb.ptrL ← ubb.ptrL + 1) = ubb.ptrR THEN
	BEGIN ubb.ptrL ← LOOPHOLE[0]; RETURN[TRUE] END
      ELSE RETURN[FALSE];
    locL ← SELECT TRUE FROM
       ubb.localL => base + LOOPHOLE[ubb.ptrL, CARDINAL],
       ubb.stackRelative => @s.stk[LOOPHOLE[ubb.ptrL, CARDINAL]],
       ENDCASE => ubb.ptrL;
    fd ← [offset: 0, posn: ubb.posnL, size: ubb.sizeL];
    left ← ReadField[locL, fd];
    IF ~ubb.immediateR THEN
      BEGIN
      fd ← [offset: 0, posn: ubb.posnR, size: ubb.sizeR];
      locR ← IF ubb.localR THEN base + LOOPHOLE[ubb.ptrR, CARDINAL] ELSE ubb.ptrR;
      right ← ReadField[locR, fd];
      END
    ELSE right ← ubb.ptrR;
    RETURN[
      SELECT ubb.relation FROM
	lt => left < right,
	gt => left > right,
	eq => left = right,
	ne => left # right,
	le => left <= right,
	ge => left >= right,
	ENDCASE => FALSE]
    END;

  ReadField: PROCEDURE [POINTER, ControlDefs.FieldDescriptor]
    RETURNS [UNSPECIFIED] = MACHINE CODE BEGIN Mopcodes.zRFS END;

  NumberBlocks: CARDINAL = 5;

  InitBreakBlocks: PROCEDURE =
    BEGIN OPEN SDDefs;
    p: CoreSwapDefs.BBHandle ← Storage.Node[
      SIZE[UserBreakBlock]*NumberBlocks + SIZE[BBArray]];
    SD[sBreakBlock] ← p;
    SD[sBreakBlockSize] ← SIZE[UserBreakBlock]*NumberBlocks + SIZE[BBArray];
    p.length ← 0;
    RETURN
    END;

  SwatBreak: PROCEDURE [s: CoreSwapDefs.SVPointer] =
    BEGIN OPEN BcplOps;
    break: RECORD [a, b: WORD];
    break ← [77400B, 1400B];
    s.instbyte ← BcplJSR[JSR, @break, 0];
    RETURN
    END;

  Interrupt: PROCEDURE =
    BEGIN -- called by BRK trap handler in resident code
    state: ControlDefs.StateVector;
    state ← STATE;
    state.dest ← FrameOps.MyLocalFrame[];
    CoreSwap[breakpoint, @state];
    END;

  Catcher: PROCEDURE [msg, signal: UNSPECIFIED, frame: FrameHandle] =
    BEGIN OPEN ControlDefs;
    SignallerGF: GlobalFrameHandle;
    state: StateVector;
    f: FrameHandle;
    state.stk[0] ← msg;
    state.stk[1] ← signal;
    state.stkptr ← 0;
    -- the call stack below here is: Signaller, [Signaller,] offender
    f ← FrameOps.GetReturnFrame[];
    SignallerGF ← f.accesslink;
    state.dest ← f ← f.returnlink.frame;
    IF f.accesslink = SignallerGF THEN state.dest ← f.returnlink;
    IF ~Swappable THEN BEGIN SwatBreak[@state]; RETURN END;
    BEGIN
    CoreSwap[uncaughtsignal, @state ! ABORTED => IF signal = ABORTED THEN GOTO abort];
    EXITS abort => {BackStop[frame]; ERROR KillThisTurkey};
    END;
    RETURN
    END;

  BackStop: PROCEDURE [root: FrameHandle] =
    BEGIN
    endProcess: ControlDefs.ControlLink ← root.returnlink;
    caller: PROCEDURE = LOOPHOLE[FrameOps.GetReturnLink[]];
    root.returnlink ← LOOPHOLE[FrameOps.MyLocalFrame[]];
    FrameOps.SetReturnFrame[ControlDefs.NullFrame];
    caller[ ! KillThisTurkey => CONTINUE];
    FrameOps.SetReturnLink[endProcess];
    RETURN
    END;

  KillThisTurkey: SIGNAL = CODE;

  -- The core swapper

  Quit: SIGNAL = CODE;
  CantSwap: PUBLIC SIGNAL = CODE;
  DoSwap: PORT [POINTER TO ExternalStateVector];

  parmstring: STRING ← [40];

  CoreSwap: PUBLIC PROCEDURE [why: SwapReason, sp: SVPointer] =
    BEGIN OPEN BcplOps;
    loadstate: FileSegmentHandle ← LoadStateOps.state;
    e: ExternalStateVector;
    DP: DebugParameter;
    decode: PROCEDURE RETURNS [BOOLEAN] =
      BEGIN OPEN ControlDefs; -- decode the SwapReason
      f: GlobalFrameHandle;
      lsv: StateVector;
      SELECT e.reason FROM
	proceed, resume => RETURN[TRUE];
	call =>
	  BEGIN
	  lsv ← LOOPHOLE[e.parameter, CallDP].sv;
	  lsv.source ← FrameOps.MyLocalFrame[];
	  TRANSFER WITH lsv;
	  lsv ← STATE;
	  LOOPHOLE[e.parameter, CallDP].sv ← lsv;
	  why ← return;
	  END;
	start =>
	  BEGIN
	  f ← LOOPHOLE[e.parameter, StartDP].frame;
	  IF ~f.started THEN START LOOPHOLE[f, PROGRAM] ELSE RESTART f;
	  why ← return;
	  END;
	quit => SIGNAL Quit;
	kill => ImageDefs.AbortMesa[];
	showscreen =>
	  BEGIN
	  UNTIL KeyDefs.Keys.Spare3 = down DO NULL ENDLOOP;
	  why ← return;
	  END;
	ENDCASE => BEGIN RETURN[TRUE]; END;
      RETURN[FALSE]
      END;
    -- Body of CoreSwap
    IF ~Swappable THEN SIGNAL CantSwap;
    DP.string ← parmstring;
    e ←
      [state: sp, reason:, level:, tables: @SwapperOps.systemTable,
	drumFile: MesaCoreFH, parameter: @DP, versionident: VersionID,
	loadstateCFA:
	[fp: loadstate.file.fp,
	  fa: [page: loadstate.base, byte: 0, da: GetFileSegmentDA[loadstate]]],
	lspages: loadstate.pages, mds: 0, bitmap: debuggerBitmap,
	bitmapPages: bitmapPages, fill: ALL[0]];
    DO
      e.reason ← why;
      ImageDefs.UserCleanupProc[OutLd ! ANY => CONTINUE];
      ProcessDefs.DisableInterrupts[];
      DoSwap[@e];
      ProcessDefs.EnableInterrupts[];
      ImageDefs.UserCleanupProc[InLd];
      BEGIN
      IF decode[
	! ABORTED => IF e.level > 0 THEN {why ← return; CONTINUE};
	Quit => GOTO abort] THEN EXIT
      EXITS abort => ERROR ABORTED;
      END;
      ENDLOOP;
    RETURN
    END;

  -- initialization

  Swappable: BOOLEAN;

  puntData: PuntTable;
  MesaCoreFH: FileHandle ← NIL;

  FindFiles: PROCEDURE =
    BEGIN OPEN ControlDefs;
    f: FileHandle;
    s: FileSegmentHandle;
    puntData.puntESV.reason ← punt;
    puntData.puntESV.tables ← @SwapperOps.systemTable;
    s ← LoadStateOps.state;
    puntData.puntESV.versionident ← VersionID;
    puntData.puntESV.loadstateCFA.fp ← s.file.fp;
    puntData.puntESV.loadstateCFA.fa ←
      [page: s.base, byte: 0, da: GetFileSegmentDA[s]];
    puntData.puntESV.lspages ← s.pages;
    puntData.pDebuggerFP ← puntData.pCoreFP ← LOOPHOLE[0];
    puntData.puntESV.bitmap ← NIL;
    puntData.puntESV.bitmapPages ← 0;
    puntData.puntESV.fill ← ALL[0];
    Swappable ← TRUE;
    IF (f ← requests[core].file) = NIL THEN
      IF (f ← requests[swatee].file) = NIL THEN Swappable ← FALSE ELSE NULL
    ELSE
      IF requests[swatee].file # NIL THEN
	BEGIN OPEN SegmentDefs;
	fh: FileHandle = requests[swatee].file;
	UnlockFile[fh];
	ReleaseFile[fh];
	END;
    IF Swappable THEN
      BEGIN OPEN DiskDefs, SegmentDefs;
      ENABLE ANY => GOTO bad;
      puntData.puntESV.drumFile ← MesaCoreFH ← f;
      s ← NewFileSegment[f, 1, 1, Read];
      BFSDefs.MakeCFP[@puntData.coreFP, @f.fp];
      puntData.coreFP.leaderDA ← LOOPHOLE[RealDA[GetFileSegmentDA[s]]];
      puntData.pCoreFP ← @puntData.coreFP;
      DeleteFileSegment[s];
      IF (f ← requests[debugger].file) = NIL THEN GOTO bad;
      s ← NewFileSegment[f, 1, 1, Read];
      BFSDefs.MakeCFP[@puntData.debuggerFP, @f.fp];
      puntData.debuggerFP.leaderDA ← LOOPHOLE[RealDA[GetFileSegmentDA[s]]];
      puntData.pDebuggerFP ← @puntData.debuggerFP;
      UnlockFile[f];
      DeleteFileSegment[s];
      EXITS bad => Swappable ← FALSE;
      END
    ELSE puntData.pDebuggerFP ← NIL;
    Storage.FreeString[requests[debugger].name];
    Storage.FreeString[requests[core].name];
    Storage.FreeString[requests[swatee].name];
    Storage.Free[requests];
    puntData.puntESV.drumFile ← MesaCoreFH;
    IF bitmapWanted THEN AllocateDebuggerBitmap[MiscOps.BitmapPages];
    PuntInfo↑ ← @puntData;
    RETURN
    END;

  RequestType: TYPE = {debugger, core, swatee};
  Requests: TYPE = ARRAY RequestType OF ImageDefs.FileRequest;
  requests: POINTER TO Requests;

  debuggerBitmap: PUBLIC LONG POINTER ← NIL;
  bitmapPages: CARDINAL ← 0;
  bitmapWanted: BOOLEAN ← TRUE;

  AllocateDebuggerBitmap: PUBLIC PROCEDURE [nPages: CARDINAL] =
    BEGIN OPEN SegmentDefs;
    seg: DataSegmentHandle;
    IF debuggerBitmap # NIL THEN ReleaseDebuggerBitmap[];
    bitmapWanted ← TRUE;
    IF ~memConfig.useXM OR ~Swappable THEN RETURN;
    seg ← MakeDataSegment[
      base: DefaultXMBase, pages: (bitmapPages ← nPages) !
      InsufficientVM => {seg ← NIL; CONTINUE}];
    IF seg # NIL THEN
      BEGIN
      debuggerBitmap ← LongDataSegmentAddress[seg];
      seg.type ← ForgotDefs.BitmapDS;
      puntData.puntESV.bitmap ← debuggerBitmap;
      puntData.puntESV.bitmapPages ← nPages;
      END;
    END;

  ReleaseDebuggerBitmap: PUBLIC PROCEDURE =
    BEGIN OPEN SegmentDefs;
    seg: DataSegmentHandle;
    IF debuggerBitmap # NIL THEN
      BEGIN
      seg ← LongVMtoDataSegment[debuggerBitmap];
      debuggerBitmap ← puntData.puntESV.bitmap ← NIL;
      bitmapPages ← puntData.puntESV.bitmapPages ← 0;
      DeleteDataSegment[seg];
      END;
    bitmapWanted ← FALSE;
    END;

  RequestFiles: PROCEDURE =
    BEGIN OPEN Storage;
    requests ← Node[SIZE[Requests]];
    requests[debugger] ←
      [file: NIL, access: Read, link:, name: CopyString["MesaDebugger."L]];
    requests[core] ←
      [file: NIL, access: Read, link:, name: CopyString["MesaCore."L]];
    requests[swatee] ←
      [file: NIL, access: Read, link:, name: CopyString["Swatee."L]];
    ImageDefs.AddFileRequest[@requests[debugger]];
    ImageDefs.AddFileRequest[@requests[core]];
    ImageDefs.AddFileRequest[@requests[swatee]];
    END;

  bypassExec: PUBLIC BOOLEAN ← FALSE;

  item1: ImageDefs.CleanupItem ←
    [link:, proc: GoToDebugger,
      mask: ImageDefs.CleanupMask[Finish] + ImageDefs.CleanupMask[Abort]];

  GoToDebugger: ImageDefs.CleanupProcedure =
    BEGIN
    IF ~bypassExec THEN RETURN;
    IF debuggerBitmap = NIL THEN AllocateDebuggerBitmap[MiscOps.BitmapPages];
    CoreSwapDefs.level ← -1;
    bypassExec ← FALSE;
    CallTheDebugger["You Called?"L];
    END;

  item2: ImageDefs.CleanupItem ←
    [link:, proc: CleanupNub, mask: ImageDefs.AllReasons];

  CleanupNub: ImageDefs.CleanupProcedure =
    BEGIN
    save: BOOLEAN ← bitmapWanted;
    SELECT why FROM
      Save => {RequestFiles[]; ReleaseDebuggerBitmap[]; bitmapWanted ← save};
      Restore => FindFiles[];
      Checkpoint => {ReleaseDebuggerBitmap[]; bitmapWanted ← save};
      Continue, Restart =>
	IF bitmapWanted THEN AllocateDebuggerBitmap[MiscOps.BitmapPages];
      ENDCASE;
    END;

  CallTheDebugger: PROCEDURE [s: STRING] =
    BEGIN -- user's entry point to debugger
    state: ControlDefs.StateVector;
    filler0, filler1: CARDINAL;
    state ← STATE;
    state.stk[0] ← s;
    state.stkptr ← 1;
    state.dest ← FrameOps.GetReturnLink[];
    CoreSwap[explicitcall, @state];
    IF FALSE THEN filler0 ← filler1 ← 0;
    RETURN
    END;

  SetSD: PROCEDURE =
    BEGIN OPEN SDDefs;
    sd: POINTER TO ARRAY [0..0) OF UNSPECIFIED ← SD;
    sd[sProcessBreakpoint] ← ProcessBreakpoint;
    sd[sUncaughtSignal] ← Catcher;
    sd[sInterrupt] ← Interrupt;
    sd[sCallDebugger] ← CallTheDebugger;
    END;

  -- Main body

  P: TYPE = MACHINE DEPENDENT RECORD [in, out: UNSPECIFIED]; -- PORT

  LOOPHOLE[DoSwap, P] ← [in: 0, out: SDDefs.SD[SDDefs.sCoreSwap]];
  RequestFiles[];

  ImageDefs.AddCleanupProcedure[@item1];
  START user;

  STOP;

  BEGIN
  ENABLE ANY => ImageDefs.PuntMesa;
  FindFiles[];
  InitBreakBlocks[];
  SetSD[];
  FrameDefs.UnNew[LOOPHOLE[NucleusOps.Wart]];
  END;

  ImageDefs.AddCleanupProcedure[@item2];
  RESTART user[ ! ABORTED => CONTINUE];
  ImageDefs.StopMesa[];

  END...