DBGF.cpp revision 1870b02d10275ce54e2b8a4da8d031f0318d93f0
/* $Id$ */
/** @file
* VMM DBGF - Debugger Facility.
*/
/*
* Copyright (C) 2006 InnoTek Systemberatung GmbH
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License as published by the Free Software Foundation,
* in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
* distribution. VirtualBox OSE is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY of any kind.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
/** @page pg_dbgf DBGC - The Debugger Facility
*
* The purpose of the DBGC is to provide an interface for debuggers to manipulate
* the VMM without having to mess up the source code for each of them. The DBGF
* is always built in and will always work when a debugger attaches to the VM.
* The DBGF provides the basic debugger features, such as halting execution,
* handling breakpoints and single step execution.
*
* The interface is working in a manner similar to the win32, linux and os2
* debugger interfaces. It interface has an asynchronous nature. This comes from
* the fact that the VMM and the Debugger are running in different threads.
* They are refered to as the "emulation thread" and the "debugger thread",
* or as the "ping thread" and the "pong thread, respectivly. (The last set
* of names comes from the use of the Ping-Pong synchronization construct from
* the RTSem API.)
*
*
*
* @section sec_dbgf_scenario Debugger Scenario
*
* The debugger starts by attaching to the VM. For pratical reasons we limit the
* number of concurrently attached debuggers to 1 per VM. The action of attaching
* to the VM causes the VM to check and generate debug events.
*
*
* The waiting and polling is done by the DBGFEventWait() function. It will wait
* for the emulation thread to send a ping, thus indicating that there is an
* event waiting to be processed.
*
* An event can be a respons to an command issued previously, the hitting of a
* the ping and must respond to the event at hand - the VMM is waiting. This
* usually means that the user of the debugger must do something, but it doesn't
* have to. The debugger is free to call any DBGF function (nearly at least)
* while processing the event.
*
* Typically the user will issue a request for the execution to be resumed, so
*
* When the user eventually terminates the debugging session or selects another
* VM, the debugger detaches from the VM. This means that breakpoints are disabled
* and that the emulation thread no longer polls for debugger commands.
*
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DBGF
#include "DBGFInternal.h"
#include <iprt/semaphore.h>
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Sets the VMM Debug Command variable.
*
* @returns Previous command.
* @param pVM VM Handle.
* @param enmCmd The command.
*/
{
if (enmCmd == DBGFCMD_NO_COMMAND)
{
}
else
{
AssertMsg(pVM->dbgf.s.enmVMMCmd == DBGFCMD_NO_COMMAND, ("enmCmd=%d enmVMMCmd=%d\n", enmCmd, pVM->dbgf.s.enmVMMCmd));
}
return rc;
}
/**
* Initializes the DBGF.
*
* @returns VBox status code.
* @param pVM VM handle.
*/
{
if (VBOX_SUCCESS(rc))
if (VBOX_SUCCESS(rc))
return rc;
}
/**
* Termiantes and cleans up resources allocated by the DBGF.
*
* @returns VBox status code.
* @param pVM VM Handle.
*/
{
int rc;
/*
* Send a termination event to any attached debuggers.
*/
/* wait for any pending events or whatever */
{
}
/* now, send the event */
{
if (VBOX_SUCCESS(rc))
{
/*
* Waits for the debugger to detach.
*/
{
/*
* Wait.
*/
/*
* Process the command.
*/
if (enmCmd != DBGFCMD_NO_COMMAND)
{
bool fResumeExecution = false;
if (enmCmd == DBGFCMD_DETACH_DEBUGGER)
break;
}
else if (VBOX_FAILURE(rc))
break;
}
}
}
/*
* Terminate the other bits.
*/
return VINF_SUCCESS;
}
/**
* Applies relocations to data and code managed by this
* component. This function will be called at init and
* whenever the VMM need to relocate it self inside the GC.
*
* @param pVM VM handle.
* @param offDelta Relocation delta relative to old location.
*/
{
}
/**
* Waits a little while for a debuggger to attach.
*
* @returns True is a debugger have attached.
* @param pVM VM handle.
* @param enmEvent Event.
*/
{
/*
* First a message.
*/
RTStrmPrintf(g_pStdErr, "DBGF: No debugger attached, waiting 15 seconds for one to attach (event=%d)\n", enmEvent);
#if 1 //def DEBUG_sandervl
int cWait = 10;
#else
int cWait = 150;
#endif
while (cWait > 0)
{
RTThreadSleep(100);
{
return true;
}
/* next */
if (!(cWait % 10))
{
}
cWait--;
}
return false;
}
/**
* Forced action callback.
* The VMM will call this from it's main loop when VM_FF_DBGF is set.
*
* The function checks and executes pending commands from the debugger.
*
* @returns VINF_SUCCESS normally.
* @returns VERR_DBGF_RAISE_FATAL_ERROR to pretend a fatal error happend.
* @param pVM VM Handle.
*/
{
/*
* Clear the FF DBGF request flag.
*/
/*
* Commands?
*/
int rc = VINF_SUCCESS;
{
/*
* Process the command.
*/
bool fResumeExecution;
if (!fResumeExecution)
}
return rc;
}
/**
* Flag whether the event implies that we're stopped in the hypervisor code
* and have to block certain operations.
*
* @param pVM The VM handle.
* @param enmEvent The event.
*/
{
switch (enmEvent)
{
case DBGFEVENT_STEPPED_HYPER:
break;
default:
break;
}
}
/**
* Try determin the event context.
*
* @returns debug event context.
* @param pVM The VM handle.
*/
{
switch (EMGetState(pVM))
{
case EMSTATE_RAW:
case EMSTATE_DEBUG_GUEST_RAW:
return DBGFEVENTCTX_RAW;
case EMSTATE_REM:
case EMSTATE_DEBUG_GUEST_REM:
return DBGFEVENTCTX_REM;
case EMSTATE_DEBUG_HYPER:
case EMSTATE_GURU_MEDITATION:
return DBGFEVENTCTX_HYPER;
default:
return DBGFEVENTCTX_OTHER;
}
}
/**
* The common event prologue code.
* It will set the 'stopped-in-hyper' flag, make sure someone's attach,
* and perhaps process any high priority pending actions (none yet).
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent The event to be sent.
*/
{
/*
* Check if a debugger is attached.
*/
{
return VERR_DBGF_NOT_ATTACHED;
}
/*
* Sync back the state from the REM.
*/
/*
* Look thru pending commands and finish those which make sense now.
*/
//int rc = DBGFR3VMMForcedAction(pVM);
return VINF_SUCCESS;
}
/**
* Sends the event in the event buffer.
*
* @returns VBox status code.
* @param pVM The VM handle.
*/
{
if (VBOX_SUCCESS(rc))
/** @todo sync VMM -> REM after exitting the debugger. everything may change while in the debugger! */
return rc;
}
/**
* Send a generic debugger event which takes no data.
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent The event to send.
*/
{
if (VBOX_FAILURE(rc))
return rc;
/*
* Send the event and process the reply communication.
*/
return dbgfR3SendEvent(pVM);
}
/**
* Send a debugger event which takes the full source file location.
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent The event to send.
* @param pszFile Source file.
* @param uLine Line number in source file.
* @param pszFunction Function name.
* @param pszFormat Message which accompanies the event.
* @param ... Message arguments.
*/
DBGFR3DECL(int) DBGFR3EventSrc(PVM pVM, DBGFEVENTTYPE enmEvent, const char *pszFile, unsigned uLine, const char *pszFunction, const char *pszFormat, ...)
{
return rc;
}
/**
* Send a debugger event which takes the full source file location.
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent The event to send.
* @param pszFile Source file.
* @param uLine Line number in source file.
* @param pszFunction Function name.
* @param pszFormat Message which accompanies the event.
* @param args Message arguments.
*/
DBGFR3DECL(int) DBGFR3EventSrcV(PVM pVM, DBGFEVENTTYPE enmEvent, const char *pszFile, unsigned uLine, const char *pszFunction, const char *pszFormat, va_list args)
{
if (VBOX_FAILURE(rc))
return rc;
/*
* Format the message.
*/
char *pszMessage = NULL;
char szMessage[8192];
{
pszMessage = &szMessage[0];
}
/*
* Send the event and process the reply communication.
*/
return dbgfR3SendEvent(pVM);
}
/**
* Send a debugger event which takes the two assertion messages.
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent The event to send.
* @param pszMsg1 First assertion message.
* @param pszMsg2 Second assertion message.
*/
DBGFR3DECL(int) DBGFR3EventAssertion(PVM pVM, DBGFEVENTTYPE enmEvent, const char *pszMsg1, const char *pszMsg2)
{
if (VBOX_FAILURE(rc))
return rc;
/*
* Send the event and process the reply communication.
*/
return dbgfR3SendEvent(pVM);
}
/**
* Breakpoint was hit somewhere.
* Figure out which breakpoint it is and notify the debugger.
*
* @returns VBox status.
* @param pVM The VM handle.
* @param enmEvent DBGFEVENT_BREAKPOINT_HYPER or DBGFEVENT_BREAKPOINT.
*/
{
if (VBOX_FAILURE(rc))
return rc;
/*
* Send the event and process the reply communication.
*/
if (iBp != ~0U)
else
{
/* REM breakpoints has be been searched for. */
#if 0 /** @todo get flat PC api! */
#else
#endif
{
break;
}
}
return dbgfR3SendEvent(pVM);
}
/**
* Waits for the debugger to respond.
*
* @returns VBox status. (clearify)
* @param pVM VM handle.
*/
{
LogFlow(("dbgfr3VMMWait:\n"));
int rcRet = VINF_SUCCESS;
/*
* Waits for the debugger to reply (i.e. issue an command).
*/
for (;;)
{
/*
* Wait.
*/
for (;;)
{
if (VBOX_SUCCESS(rc))
break;
if (rc != VERR_TIMEOUT)
{
return rc;
}
{
LogFlow(("dbgfr3VMMWait: Processes requests...\n"));
{
switch (rc)
{
case VINF_EM_DBG_BREAKPOINT:
case VINF_EM_DBG_STEPPED:
case VINF_EM_DBG_STEP:
case VINF_EM_DBG_STOP:
break;
/* return straight away */
case VINF_EM_TERMINATE:
case VINF_EM_OFF:
return rc;
/* remember return code. */
default:
case VINF_EM_RESET:
case VINF_EM_SUSPEND:
case VINF_EM_HALT:
case VINF_EM_RESUME:
case VINF_EM_RESCHEDULE:
case VINF_EM_RESCHEDULE_REM:
case VINF_EM_RESCHEDULE_RAW:
break;
}
}
else if (VBOX_FAILURE(rc))
{
return rc;
}
}
}
/*
* Process the command.
*/
bool fResumeExecution;
if (fResumeExecution)
{
if (VBOX_FAILURE(rc))
else if ( rc >= VINF_EM_FIRST
&& rc < VINF_EM_LAST
return rcRet;
}
}
}
/**
* Executes command from debugger.
* The caller is responsible for waiting or resuming execution based on the
* value returned in the *pfResumeExecution indicator.
*
* @returns VBox status. (clearify!)
* @param pVM VM Handle.
* @param enmCmd The command in question.
* @param pCmdData Pointer to the command data.
* @param pfResumeExecution Where to store the resume execution / continue waiting indicator.
*/
{
bool fSendEvent;
bool fResume;
int rc = VINF_SUCCESS;
switch (enmCmd)
{
/*
* Halt is answered by an event say that we've halted.
*/
case DBGFCMD_HALT:
{
fSendEvent = true;
fResume = false;
break;
}
/*
* Resume is not answered we'll just resume execution.
*/
case DBGFCMD_GO:
{
fSendEvent = false;
fResume = true;
break;
}
/** @todo implement (and define) the rest of the commands. */
/*
* Disable breakpoints and stuff.
* Send an everythings cool event to the debugger thread and resume execution.
*/
case DBGFCMD_DETACH_DEBUGGER:
{
fSendEvent = true;
fResume = true;
break;
}
/*
* Single step, with trace into.
*/
case DBGFCMD_SINGLE_STEP:
{
Log2(("Single step\n"));
fSendEvent = false;
fResume = true;
break;
}
/*
* Default is to send an invalid command event.
*/
default:
{
fSendEvent = true;
fResume = false;
break;
}
}
/*
* Send pending event.
*/
if (fSendEvent)
{
if (VBOX_FAILURE(rc2))
{
*pfResumeExecution = true;
return rc2;
}
}
/*
* Return.
*/
return rc;
}
/**
* Attaches a debugger to the specified VM.
*
* Only one debugger at a time.
*
* @returns VBox status code.
* @param pVM VM Handle.
*/
{
/*
* Check if already attached.
*/
{
AssertMsgFailed(("Already attached\n"));
return VERR_DBGF_ALREADY_ATTACHED;
}
/*
* Create the Ping-Pong structure.
*/
if (rc)
return rc;
/*
* Set the attached flag.
*/
return VINF_SUCCESS;
}
/**
* Detaches a debugger from the specified VM.
*
* Caller must be attached to the VM.
*
* @returns VBox status code.
* @param pVM VM Handle.
*/
{
/*
* Check if attached.
*/
{
AssertMsgFailed(("Not attached to VM!\n"));
return VERR_DBGF_NOT_ATTACHED;
}
/*
* Send detach command.
*/
{
if (VBOX_FAILURE(rc))
{
return rc;
}
}
else
/*
* Wait for the ok event.
*/
if (VBOX_FAILURE(rc))
{
return rc;
}
/*
* Destroy the ping-pong construct and return.
*/
RTThreadSleep(10);
return VINF_SUCCESS;
}
/**
* Wait for a debug event.
*
* @returns VBox status. Will not return VBOX_INTERRUPTED.
* @param pVM VM handle.
* @param cMillies Number of millies to wait.
* @param ppEvent Where to store the event pointer.
*/
{
/*
* Check state.
*/
{
AssertMsgFailed(("Not attached to VM!\n"));
return VERR_DBGF_NOT_ATTACHED;
}
/*
* Wait.
*/
if (VBOX_SUCCESS(rc))
{
return VINF_SUCCESS;
}
return rc;
}
/**
* Halts VM execution.
*
* After calling this the VM isn't actually halted till an DBGFEVENT_HALT_DONE
* arrives. Until that time it's not possible to issue any new commands.
*
* @returns VBox status.
* @param pVM VM handle.
*/
{
/*
* Check state.
*/
{
AssertMsgFailed(("Not attached to VM!\n"));
return VERR_DBGF_NOT_ATTACHED;
}
return VWRN_DBGF_ALREADY_HALTED;
/*
* Send command.
*/
return VINF_SUCCESS;
}
/**
* Checks if the VM is halted by the debugger.
*
* @returns True if halted.
* @returns False if not halted.
* @param pVM VM handle.
*/
{
|| enmSpeaker == RTPINGPONGSPEAKER_PONG);
}
/**
* Checks if the the debugger can wait for events or not.
*
* This function is only used by lazy, multiplexing debuggers. :-)
*
* @returns True if waitable.
* @returns False if not waitable.
* @param pVM VM handle.
*/
{
&& ( enmSpeaker == RTPINGPONGSPEAKER_PING
}
/**
* Resumes VM execution.
*
* There is no receipt event on this command.
*
* @returns VBox status.
* @param pVM VM handle.
*/
{
/*
* Check state.
*/
{
AssertMsgFailed(("Not attached to VM!\n"));
return VERR_DBGF_NOT_ATTACHED;
}
{
AssertMsgFailed(("Speaking out of turn!\n"));
return VERR_SEM_OUT_OF_TURN;
}
/*
* Send the ping back to the emulation thread telling it to run.
*/
return rc;
}
/**
* Step Into.
*
* A single step event is generated from this command.
* The current implementation is not reliable, so don't rely on the event comming.
*
* @returns VBox status.
* @param pVM VM handle.
*/
{
/*
* Check state.
*/
{
AssertMsgFailed(("Not attached to VM!\n"));
return VERR_DBGF_NOT_ATTACHED;
}
{
AssertMsgFailed(("Speaking out of turn!\n"));
return VERR_SEM_OUT_OF_TURN;
}
/*
* Send the ping back to the emulation thread telling it to run.
*/
return rc;
}
/**
* Call this to single step rawmode or recompiled mode.
*
* You must pass down the return code to the EM loop! That's
* where the actual single stepping take place (at least in the
* current implementation).
*
* @returns VINF_EM_DBG_STEP
* @thread EMT
*/
{
return VINF_EM_DBG_STEP;
}