/** @file

Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <Uefi.h>
#include "Edb.h"

EFI_DEBUGGER_PRIVATE_DATA mDebuggerPrivate = {
  EFI_DEBUGGER_SIGNATURE,                    // Signature
  IsaEbc,                                    // Isa
  (EBC_DEBUGGER_MAJOR_VERSION << 16) |
    EBC_DEBUGGER_MINOR_VERSION,              // EfiDebuggerRevision
  (VM_MAJOR_VERSION << 16) |
    VM_MINOR_VERSION,                        // EbcVmRevision
  {
    EFI_DEBUGGER_CONFIGURATION_VERSION,
    &mDebuggerPrivate,
  },                                         // DebuggerConfiguration
  NULL,                                      // DebugImageInfoTableHeader
  NULL,                                      // Vol
  NULL,                                      // PciRootBridgeIo
  mDebuggerCommandSet,                       // DebuggerCommandSet
  {0},                                       // DebuggerSymbolContext
  0,                                         // DebuggerBreakpointCount
  {{0}},                                     // DebuggerBreakpointContext
  0,                                         // CallStackEntryCount
  {{0}},                                     // CallStackEntry
  0,                                         // TraceEntryCount
  {{0}},                                     // TraceEntry
  {0},                                       // StepContext
  {0},                                       // GoTilContext
  0,                                         // InstructionScope
  EFI_DEBUG_DEFAULT_INSTRUCTION_NUMBER,      // InstructionNumber
  EFI_DEBUG_FLAG_EBC_BOE | EFI_DEBUG_FLAG_EBC_BOT, // FeatureFlags
  0,                                               // StatusFlags
  FALSE,                                           // EnablePageBreak
  NULL                                             // BreakEvent
};

CHAR16 *mExceptionStr[] = {
  L"EXCEPT_EBC_UNDEFINED",
  L"EXCEPT_EBC_DIVIDE_ERROR",
  L"EXCEPT_EBC_DEBUG",
  L"EXCEPT_EBC_BREAKPOINT",
  L"EXCEPT_EBC_OVERFLOW",
  L"EXCEPT_EBC_INVALID_OPCODE",
  L"EXCEPT_EBC_STACK_FAULT",
  L"EXCEPT_EBC_ALIGNMENT_CHECK",
  L"EXCEPT_EBC_INSTRUCTION_ENCODING",
  L"EXCEPT_EBC_BAD_BREAK",
  L"EXCEPT_EBC_SINGLE_STEP",
};

/**

  Clear all the breakpoint.

  @param DebuggerPrivate    EBC Debugger private data structure
  @param NeedRemove         Whether need to remove all the breakpoint

**/
VOID
EdbClearAllBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     BOOLEAN                   NeedRemove
  )
{
  UINTN    Index;

  //
  // Patch all the breakpoint
  //
  for (Index = 0; (Index < DebuggerPrivate->DebuggerBreakpointCount) && (Index < EFI_DEBUGGER_BREAKPOINT_MAX); Index++) {
    if (DebuggerPrivate->DebuggerBreakpointContext[Index].State) {
      CopyMem (
        (VOID *)(UINTN)DebuggerPrivate->DebuggerBreakpointContext[Index].BreakpointAddress,
        &DebuggerPrivate->DebuggerBreakpointContext[Index].OldInstruction,
        sizeof(UINT16)
        );
    }
  }

  //
  // Zero Breakpoint context, if need to remove all breakpoint
  //
  if (NeedRemove) {
    DebuggerPrivate->DebuggerBreakpointCount = 0;
    ZeroMem (DebuggerPrivate->DebuggerBreakpointContext, sizeof(DebuggerPrivate->DebuggerBreakpointContext));
  }

  //
  // Done
  //
  return ;
}

/**

  Set all the breakpoint.

  @param DebuggerPrivate    EBC Debugger private data structure

**/
VOID
EdbSetAllBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate
  )
{
  UINTN    Index;
  UINT16   Data16;

  //
  // Set all the breakpoint (BREAK(3) : 0x0300)
  //
  Data16 = 0x0300;
  for (Index = 0; (Index < DebuggerPrivate->DebuggerBreakpointCount) && (Index < EFI_DEBUGGER_BREAKPOINT_MAX); Index++) {
    if (DebuggerPrivate->DebuggerBreakpointContext[Index].State) {
      CopyMem (
        (VOID *)(UINTN)DebuggerPrivate->DebuggerBreakpointContext[Index].BreakpointAddress,
        &Data16,
        sizeof(UINT16)
        );
    }
  }

  //
  // Check if current break is caused by breakpoint set.
  // If so, we need to patch memory back to let user see the real memory.
  //
  if (DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX].BreakpointAddress != 0) {
    CopyMem (
      (VOID *)(UINTN)DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX].BreakpointAddress,
      &DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX].OldInstruction,
      sizeof(UINT16)
      );
    DebuggerPrivate->StatusFlags &= ~EFI_DEBUG_FLAG_EBC_B_BP;
  }

  //
  // Done
  //
  return ;
}

/**

  Check all the breakpoint, if match, then set status flag, and record current breakpoint.
  Then clear all breakpoint to let user see a clean memory

  @param   DebuggerPrivate    EBC Debugger private data structure
  @param   SystemContext      EBC system context.

**/
VOID
EdbCheckBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_SYSTEM_CONTEXT        SystemContext
  )
{
  UINT64   Address;
  UINTN    Index;
  BOOLEAN  IsHitBreakpoint;

  //
  // Roll back IP for breakpoint instruction (BREAK(3) : 0x0300)
  //
  Address = SystemContext.SystemContextEbc->Ip - sizeof(UINT16);

  //
  // Check if the breakpoint is hit
  //
  IsHitBreakpoint = FALSE;
  for (Index = 0; (Index < DebuggerPrivate->DebuggerBreakpointCount) && (Index < EFI_DEBUGGER_BREAKPOINT_MAX); Index++) {
    if ((DebuggerPrivate->DebuggerBreakpointContext[Index].BreakpointAddress == Address) &&
        (DebuggerPrivate->DebuggerBreakpointContext[Index].State)) {
      IsHitBreakpoint = TRUE;
      break;
    }
  }

  if (IsHitBreakpoint) {
    //
    // If hit, record current breakpoint
    //
    DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX] = DebuggerPrivate->DebuggerBreakpointContext[Index];
    DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX].State = TRUE;
    //
    // Update: IP and Instruction (NOTE: Since we not allow set breakpoint to BREAK 3, this update is safe)
    //
    SystemContext.SystemContextEbc->Ip = Address;
    //
    // Set Flags
    //
    DebuggerPrivate->StatusFlags |= EFI_DEBUG_FLAG_EBC_BP;
  } else {
    //
    // If not hit, check whether current IP is in breakpoint list,
    // because STEP will be triggered before execute the instruction.
    // We should not patch it in de-init.
    //
    Address = SystemContext.SystemContextEbc->Ip;

    //
    // Check if the breakpoint is hit
    //
    IsHitBreakpoint = FALSE;
    for (Index = 0; (Index < DebuggerPrivate->DebuggerBreakpointCount) && (Index < EFI_DEBUGGER_BREAKPOINT_MAX); Index++) {
      if ((DebuggerPrivate->DebuggerBreakpointContext[Index].BreakpointAddress == Address) &&
          (DebuggerPrivate->DebuggerBreakpointContext[Index].State)) {
        IsHitBreakpoint = TRUE;
        break;
      }
    }

    if (IsHitBreakpoint) {
      //
      // If hit, record current breakpoint
      //
      DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX] = DebuggerPrivate->DebuggerBreakpointContext[Index];
      DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX].State = TRUE;
      //
      // Do not set Breakpoint flag. We record the address here just let it not patch breakpoint address when de-init.
      //
    } else {
      //
      // Zero current breakpoint
      //
      ZeroMem (
        &DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX],
        sizeof(DebuggerPrivate->DebuggerBreakpointContext[EFI_DEBUGGER_BREAKPOINT_MAX])
        );
    }
  }

  //
  // Done
  //
  return ;
}

/**
  clear all the symbol.

  @param DebuggerPrivate    EBC Debugger private data structure

**/
VOID
EdbClearSymbol (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate
  )
{
  EFI_DEBUGGER_SYMBOL_CONTEXT *DebuggerSymbolContext;
  EFI_DEBUGGER_SYMBOL_OBJECT  *Object;
  UINTN                       ObjectIndex;
  UINTN                       Index;

  //
  // Go throuth each object
  //
  DebuggerSymbolContext = &DebuggerPrivate->DebuggerSymbolContext;
  for (ObjectIndex = 0; ObjectIndex < DebuggerSymbolContext->ObjectCount; ObjectIndex++) {
    Object = &DebuggerSymbolContext->Object[ObjectIndex];
    //
    // Go throuth each entry
    //
    for (Index = 0; Index < Object->EntryCount; Index++) {
      ZeroMem (&Object->Entry[Index], sizeof(Object->Entry[Index]));
    }
    ZeroMem (Object->Name, sizeof(Object->Name));
    Object->EntryCount = 0;
    Object->BaseAddress = 0;
    Object->StartEntrypointRVA = 0;
    Object->MainEntrypointRVA = 0;
    //
    // Free source buffer
    //
    for (Index = 0; Object->SourceBuffer[Index] != NULL; Index++) {
      gBS->FreePool (Object->SourceBuffer[Index]);
      Object->SourceBuffer[Index] = NULL;
    }
  }
  DebuggerSymbolContext->ObjectCount = 0;

  return ;
}

/**

  Initialize Debugger private data structure

  @param DebuggerPrivate   EBC Debugger private data structure
  @param ExceptionType     Exception type.
  @param SystemContext     EBC system context.
  @param Initialized       Whether the DebuggerPrivate data is initialized.

**/
EFI_STATUS
InitDebuggerPrivateData (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
{
  //
  // clear STEP flag in any condition.
  //
  if (SystemContext.SystemContextEbc->Flags & ((UINT64) VMFLAGS_STEP)) {
    SystemContext.SystemContextEbc->Flags &= ~((UINT64) VMFLAGS_STEP);
  }

  if (!Initialized) {
    //
    // Initialize everything
    //
    DebuggerPrivate->InstructionNumber = EFI_DEBUG_DEFAULT_INSTRUCTION_NUMBER;

    DebuggerPrivate->DebuggerBreakpointCount = 0;
    ZeroMem (DebuggerPrivate->DebuggerBreakpointContext, sizeof(DebuggerPrivate->DebuggerBreakpointContext));

//    DebuggerPrivate->StatusFlags = 0;

    DebuggerPrivate->DebuggerSymbolContext.DisplaySymbol = TRUE;
    DebuggerPrivate->DebuggerSymbolContext.DisplayCodeOnly = FALSE;
    DebuggerPrivate->DebuggerSymbolContext.ObjectCount = 0;
  } else {
    //
    // Already initialized, just check Breakpoint here.
    //
    if (ExceptionType == EXCEPT_EBC_BREAKPOINT) {
      EdbCheckBreakpoint (DebuggerPrivate, SystemContext);
    }

    //
    // Clear all breakpoint
    //
    EdbClearAllBreakpoint (DebuggerPrivate, FALSE);
  }

  //
  // Set Scope to currentl IP. (Note: Check Breakpoint may change Ip)
  //
  DebuggerPrivate->InstructionScope = SystemContext.SystemContextEbc->Ip;

  //
  // Done
  //
  return EFI_SUCCESS;
}

/**

  De-initialize Debugger private data structure.

  @param DebuggerPrivate   EBC Debugger private data structure
  @param ExceptionType     Exception type.
  @param SystemContext     EBC system context.
  @param Initialized       Whether the DebuggerPrivate data is initialized.

**/
EFI_STATUS
DeinitDebuggerPrivateData (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
{
  if (!Initialized) {
    //
    // If it does not want initialized state, de-init everything
    //
    DebuggerPrivate->FeatureFlags = EFI_DEBUG_FLAG_EBC_BOE | EFI_DEBUG_FLAG_EBC_BOT;
    DebuggerPrivate->CallStackEntryCount = 0;
    DebuggerPrivate->TraceEntryCount = 0;
    ZeroMem (DebuggerPrivate->CallStackEntry, sizeof(DebuggerPrivate->CallStackEntry));
    ZeroMem (DebuggerPrivate->TraceEntry, sizeof(DebuggerPrivate->TraceEntry));

    //
    // Clear all breakpoint
    //
    EdbClearAllBreakpoint (DebuggerPrivate, TRUE);

    //
    // Clear symbol
    //
    EdbClearSymbol (DebuggerPrivate);
  } else {
    //
    // If it wants to keep initialized state, just set breakpoint.
    //
    EdbSetAllBreakpoint (DebuggerPrivate);
  }

  //
  // Clear Step context
  //
  ZeroMem (&mDebuggerPrivate.StepContext, sizeof(mDebuggerPrivate.StepContext));
  DebuggerPrivate->StatusFlags = 0;

  //
  // Done
  //
  return EFI_SUCCESS;
}

/**

  Print the reason of current break to EbcDebugger.

  @param DebuggerPrivate   EBC Debugger private data structure
  @param ExceptionType     Exception type.
  @param SystemContext     EBC system context.
  @param Initialized       Whether the DebuggerPrivate data is initialized.

**/
VOID
PrintExceptionReason (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
{
  //
  // Print break status
  //
  if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_GT) == EFI_DEBUG_FLAG_EBC_GT) {
    EDBPrint (L"Break on GoTil\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOC) == EFI_DEBUG_FLAG_EBC_BOC) {
    EDBPrint (L"Break on CALL\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOCX) == EFI_DEBUG_FLAG_EBC_BOCX) {
    EDBPrint (L"Break on CALLEX\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOR) == EFI_DEBUG_FLAG_EBC_BOR) {
    EDBPrint (L"Break on RET\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOE) == EFI_DEBUG_FLAG_EBC_BOE) {
    EDBPrint (L"Break on Entrypoint\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOT) == EFI_DEBUG_FLAG_EBC_BOT) {
    EDBPrint (L"Break on Thunk\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_STEPOVER) == EFI_DEBUG_FLAG_EBC_STEPOVER) {
    EDBPrint (L"Break on StepOver\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_STEPOUT) == EFI_DEBUG_FLAG_EBC_STEPOUT) {
    EDBPrint (L"Break on StepOut\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BP) == EFI_DEBUG_FLAG_EBC_BP) {
    EDBPrint (L"Break on Breakpoint\n");
  } else if ((DebuggerPrivate->StatusFlags & EFI_DEBUG_FLAG_EBC_BOK) == EFI_DEBUG_FLAG_EBC_BOK) {
    EDBPrint (L"Break on Key\n");
  } else {
    EDBPrint (L"Exception Type - %x", (UINTN)ExceptionType);
    if ((ExceptionType >= EXCEPT_EBC_UNDEFINED) && (ExceptionType <= EXCEPT_EBC_STEP)) {
      EDBPrint (L" (%s)\n", mExceptionStr[ExceptionType]);
    } else {
      EDBPrint (L"\n");
    }
  }

  return ;
}

/**

  The default Exception Callback for the VM interpreter.
  In this function, we report status code, and print debug information
  about EBC_CONTEXT, then dead loop.

  @param ExceptionType    Exception type.
  @param SystemContext    EBC system context.

**/
VOID
EFIAPI
EdbExceptionHandler (
  IN     EFI_EXCEPTION_TYPE   ExceptionType,
  IN OUT EFI_SYSTEM_CONTEXT   SystemContext
  )
{
  CHAR16                  InputBuffer[EFI_DEBUG_INPUS_BUFFER_SIZE];
  CHAR16                  *CommandArg;
  EFI_DEBUGGER_COMMAND    DebuggerCommand;
  EFI_DEBUG_STATUS        DebugStatus;
  STATIC BOOLEAN          mInitialized;

  mInitialized = FALSE;

  DEBUG ((DEBUG_ERROR, "Hello EBC Debugger!\n"));

  if (!mInitialized) {
    //
    // Print version
    //
    EDBPrint (
      L"EBC Interpreter Version - %d.%d\n",
      (UINTN)VM_MAJOR_VERSION,
      (UINTN)VM_MINOR_VERSION
      );
    EDBPrint (
      L"EBC Debugger Version - %d.%d\n",
      (UINTN)EBC_DEBUGGER_MAJOR_VERSION,
      (UINTN)EBC_DEBUGGER_MINOR_VERSION
      );
  }
  //
  // Init Private Data
  //
  InitDebuggerPrivateData (&mDebuggerPrivate, ExceptionType, SystemContext, mInitialized);

  //
  // EDBPrint basic info
  //
  PrintExceptionReason (&mDebuggerPrivate, ExceptionType, SystemContext, mInitialized);

  EdbShowDisasm (&mDebuggerPrivate, SystemContext);
  // EFI_BREAKPOINT ();

  if (!mInitialized) {
    //
    // Interactive with user
    //
    EDBPrint (L"\nPlease enter command now, \'h\' for help.\n");
    EDBPrint (L"(Using <Command> -b <...> to enable page break.)\n");
  }
  mInitialized = TRUE;

  //
  // Dispatch each command
  //
  while (TRUE) {
    //
    // Get user input
    //
    Input (L"\n\r" EFI_DEBUG_PROMPT_STRING, InputBuffer, EFI_DEBUG_INPUS_BUFFER_SIZE);
    EDBPrint (L"\n");

    //
    // Get command
    //
    DebuggerCommand = MatchDebuggerCommand (InputBuffer, &CommandArg);
    if (DebuggerCommand == NULL) {
      EDBPrint (L"ERROR: Command not found!\n");
      continue;
    }

    //
    // Check PageBreak;
    //
    if (CommandArg != NULL) {
      if (StriCmp (CommandArg, L"-b") == 0) {
        CommandArg = StrGetNextTokenLine (L" ");
        mDebuggerPrivate.EnablePageBreak = TRUE;
      }
    }

    //
    // Dispatch command
    //
    DebugStatus = DebuggerCommand (CommandArg, &mDebuggerPrivate, ExceptionType, SystemContext);
    mDebuggerPrivate.EnablePageBreak = FALSE;

    //
    // Check command return status
    //
    if (DebugStatus == EFI_DEBUG_RETURN) {
      mInitialized = FALSE;
      break;
    } else if (DebugStatus == EFI_DEBUG_BREAK) {
      break;
    } else if (DebugStatus == EFI_DEBUG_CONTINUE) {
      continue;
    } else {
      ASSERT (FALSE);
    }
  }

  //
  // Deinit Private Data
  //
  DeinitDebuggerPrivateData (&mDebuggerPrivate, ExceptionType, SystemContext, mInitialized);

  DEBUG ((DEBUG_ERROR, "Goodbye EBC Debugger!\n"));

  return;
}
