/*++

Copyright (c) 2007 - 2016, Intel Corporation
All rights reserved. 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.

Module Name:

  Ebc.c

Abstract:

--*/

#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",
};

VOID
EdbClearAllBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     BOOLEAN                   NeedRemove
  )
/*++

Routine Description:

  Clear all the breakpoint

Arguments:

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

Returns:

  None

--*/
{
  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 ;
}

VOID
EdbSetAllBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate
  )
/*++

Routine Description:

  Set all the breakpoint

Arguments:

  DebuggerPrivate - EBC Debugger private data structure

Returns:

  None

--*/
{
  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 ;
}

VOID
EdbCheckBreakpoint (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_SYSTEM_CONTEXT        SystemContext
  )
/*++

Routine Description:

  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

Arguments:

  DebuggerPrivate - EBC Debugger private data structure
  SystemContext   - EBC system context.

Returns:

  None

--*/
{
  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 ;
}

VOID
EdbClearSymbol (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate
  )
/*++

Routine Description:

  clear all the symbol

Arguments:

  DebuggerPrivate - EBC Debugger private data structure

Returns:

  None

--*/
{
  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 ;
}

EFI_STATUS
InitDebuggerPrivateData (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
/*++

Routine Description:

  Initialize Debugger private data structure

Arguments:

  DebuggerPrivate - EBC Debugger private data structure
  InterruptType   - Interrupt type.
  SystemContext   - EBC system context.
  Initialized     - Whether the DebuggerPrivate data is initialized.

Returns:

  None

--*/
{
  //
  // 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;
}

EFI_STATUS
DeinitDebuggerPrivateData (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
/*++

Routine Description:

  De-initialize Debugger private data structure

Arguments:

  DebuggerPrivate - EBC Debugger private data structure
  InterruptType   - Interrupt type.
  SystemContext   - EBC system context.
  Initialized     - Whether the DebuggerPrivate data is initialized.

Returns:

  None

--*/
{
  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;
}

VOID
PrintExceptionReason (
  IN     EFI_DEBUGGER_PRIVATE_DATA *DebuggerPrivate,
  IN     EFI_EXCEPTION_TYPE        ExceptionType,
  IN     EFI_SYSTEM_CONTEXT        SystemContext,
  IN     BOOLEAN                   Initialized
  )
/*++

Routine Description:

  Print the reason of current break to EbcDebugger.

Arguments:

  DebuggerPrivate - EBC Debugger private data structure
  InterruptType   - Interrupt type.
  SystemContext   - EBC system context.
  Initialized     - Whether the DebuggerPrivate data is initialized.

Returns:

  None

--*/
{
  //
  // 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 ;
}

VOID
EFIAPI
EdbExceptionHandler (
  IN     EFI_EXCEPTION_TYPE   ExceptionType,
  IN OUT EFI_SYSTEM_CONTEXT   SystemContext
  )
/*++

Routine Description:

  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.

Arguments:

  InterruptType - Interrupt type.
  SystemContext - EBC system context.

Returns:

  None

--*/
{
  CHAR16                  InputBuffer[EFI_DEBUG_INPUS_BUFFER_SIZE];
  CHAR16                  *CommandArg;
  EFI_DEBUGGER_COMMAND    DebuggerCommand;
  EFI_DEBUG_STATUS        DebugStatus;
  STATIC BOOLEAN          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;
}
