JDEBUG.CPP

/*++ 

Copyright (c) 1996 Microsoft Corporation

Module Name:

jdebug.cpp

Abstract:

Java debugger class implementation.

The C++ debugger sample preforms the following steps:

1) Connects to the debug manager.
2) Runs the Java debuggee applet.
3) Watches for the debuggee's Java VM to be created, and puts up a message box.
4) Connects to the debuggee's Java VM.
5) Watches for the debuggee class to be loaded.
6) Sets a breakpoint in a method in the debuggee class.
7) Watches for the breakpoint to be hit, and puts up a message box.
8) Clears the breakpoint.
9) Watches for the debuggee's Java VM to be destroyed, and puts up a message box.
10) Disconnects from the debuggee's Java VM.
11) Disconnects from the debug manager.
12) Exits.

--*/


/* Headers
**********/

#include "project.hpp"

#include <javadbg.h>
#include <jdbgguid.h>

#include "refcount.hpp"


/* Classes
**********/

// sample Java debugger class

class JavaDebugger : public RefCount,
public IRemoteDebugManagerCallback,
public IRemoteProcessCallback
{
private:
/* Fields
*********/

// debug manager
IRemoteDebugManager *m_pirdm;

// remote process
IRemoteProcess *m_pirp;

// debuggee class name
LPWSTR m_pwszDebugClass;

/* Methods
**********/

UINT RunMessageLoop(void);
void QuitMessageLoop(UINT uResult);

public:
/* Methods
**********/

JavaDebugger(void);
~JavaDebugger(void);
HRESULT Initialize(IRemoteDebugManager *pirdm);
void Terminate(void);
HRESULT Debug(LPCSTR pcszDebugClass);

// IRemoteDebugManagerCallback methods

HRESULT STDMETHODCALLTYPE ProcessCreateEvent(IRemoteProcess *pirpNew, IRemoteProcess *pirpParent);

// IRemoteProcessCallback methods

HRESULT STDMETHODCALLTYPE DebugStringEvent(IRemoteThread *pirth, LPCWSTR pcwszDebugMsg);
HRESULT STDMETHODCALLTYPE CodeBreakpointEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE DataBreakpointEvent(IRemoteThread *pirth, IRemoteObject *piro);
HRESULT STDMETHODCALLTYPE ExceptionEvent(IRemoteThread *pirth, IRemoteClassField *pircfException, EXCEPTIONKIND exk);
HRESULT STDMETHODCALLTYPE StepEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE CanStopEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE BreakEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE ThreadCreateEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE ThreadDestroyEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE ThreadGroupCreateEvent(IRemoteThread *pirth, IRemoteThreadGroup *pirthg);
HRESULT STDMETHODCALLTYPE ThreadGroupDestroyEvent(IRemoteThread *pirth, IRemoteThreadGroup *pirthg);
HRESULT STDMETHODCALLTYPE ClassLoadEvent(IRemoteThread *pirth, IRemoteClassField *pircfClass);
HRESULT STDMETHODCALLTYPE ClassUnloadEvent(IRemoteThread *pirth, IRemoteClassField *pircfClass);
HRESULT STDMETHODCALLTYPE ProcessDestroyEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE TraceEvent(IRemoteThread *pirth);
HRESULT STDMETHODCALLTYPE LoadCompleteEvent(IRemoteThread *pirth);

// IUnknown methods

DEFINE_DELEGATED_REFCOUNT_ADDREF(JavaDebugger);
DEFINE_DELEGATED_REFCOUNT_RELEASE(JavaDebugger);
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObject);
};


/* Module Constants
*******************/

#pragma data_seg(DATA_SEG_READ_ONLY)

const char SPACE = ' ';
const char TAB = '\t';
const char QUOTE = '\'';
const char QUOTES = '"';

const char s_cszMsgBoxTitle[] = "Sample Java Debugger";

const char s_cszDebugKey[] = "Software\\Microsoft\\Java VM\\Debug";

const char s_cszDebugClass[] = "Hello";
const WCHAR s_cwszDebugMethod[] = L"main";
const ULONG s_ulcBreakpointPC = 0;

#pragma data_seg()


/* Macros
*********/

//
// Compile-time type check cast macro.
//
#define SAFE_CAST(type, obj) \
(((type)(obj) == (obj) ? 0 : 0), (type)(obj))

//
// Safely delete an object.
//
#define SAFE_DELETE(ptr) \
{ \
if (! (ptr)) \
; \
else \
{ \
delete(ptr); \
(ptr) = NULL; \
} \
} \


/***************************** Private Functions *****************************/


//
// Create a dynamically allocated Unicode copy of an ANSI string.
//
static HRESULT ANSIToUnicode(LPCSTR pcszANSI, LPWSTR *ppwszUnicode)
{
HRESULT hr = E_UNEXPECTED;
int ncchANSI;
int ncchUnicode;

// (+ 1) for null terminator.
ncchANSI = lstrlen(pcszANSI) + 1;

ncchUnicode = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pcszANSI, ncchANSI, NULL, 0);

if (ncchUnicode > 0)
{
*ppwszUnicode = new(WCHAR[ncchUnicode]);

if (*ppwszUnicode)
{
if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pcszANSI, ncchANSI, *ppwszUnicode, ncchUnicode) == ncchUnicode)
hr = S_OK;
else
{
delete(*ppwszUnicode);
*ppwszUnicode = NULL;
}
}
else
hr = E_OUTOFMEMORY;
}
else
*ppwszUnicode = NULL;

return(hr);
}


//
// Skip over the white space at the beginning of a string.
//
LPCSTR SkipWhiteSpace(LPCSTR pcsz)
{
while (*pcsz == SPACE ||
*pcsz == TAB)
pcsz = CharNext(pcsz);

return(pcsz);
}


//
// Skip over the meat at the beginning of a string.
//
static LPCSTR SkipNonWhiteSpace(LPCSTR pcsz)
{
while (*pcsz &&
*pcsz != SPACE &&
*pcsz != TAB)
pcsz = CharNext(pcsz);

return(pcsz);
}


//
// Skip over a quoted substring at the beginning of a string.
//
static LPCSTR SkipQuotedArg(LPCSTR pcsz)
{
char chQ;

// Skip over quoted argument past matching quote.

chQ = *pcsz;
pcsz = CharNext(pcsz);

while (*pcsz &&
*pcsz != chQ)
pcsz = CharNext(pcsz);

if (*pcsz == chQ)
pcsz = CharNext(pcsz);

return(pcsz);
}


//
// Skip over a possibly quoted substring at the beginning of a string.
//
static LPCSTR SkipPossiblyQuotedArg(LPCSTR pcsz)
{
pcsz = SkipWhiteSpace(pcsz);

switch (*pcsz)
{
case QUOTE:
case QUOTES:
pcsz = SkipQuotedArg(pcsz);
break;

default:
pcsz = SkipNonWhiteSpace(pcsz);
break;
}

return(pcsz);
}


//
// Display a printf()-style message box.
//
static BOOL MyMessageBox(HWND hwndParent, UINT uStyle, LPCSTR pcszFormat, ...)
{
char szMsg[1024];
va_list valArgs;

// Lamely assume that wvsprintf() won't overflow szMsg[].

va_start(valArgs, pcszFormat);
wvsprintf(szMsg, pcszFormat, valArgs);
va_end(valArgs);

return(MessageBox(hwndParent, szMsg, s_cszMsgBoxTitle, uStyle));
}


//
// Create the Java VM Debug key.
//
static BOOL CreateDebugKey(void)
{
BOOL bResult;
DWORD dwDisposition;
HKEY hkeyDebug;

bResult = (RegCreateKeyEx(HKEY_LOCAL_MACHINE, s_cszDebugKey, 0, NULL, 0, KEY_WRITE, NULL, &hkeyDebug, &dwDisposition)
== ERROR_SUCCESS);

if (bResult)
RegCloseKey(hkeyDebug);

return(bResult);
}


//
// Delete the Java VM Debug key.
//
static BOOL DeleteDebugKey(void)
{
return(RegDeleteKey(HKEY_LOCAL_MACHINE, s_cszDebugKey) == ERROR_SUCCESS);
}


/****************************** Public Functions *****************************/


#pragma warning(disable:4100) // "unreferenced formal parameter" warning

int __cdecl main(int argc, char *argv[])
{
HRESULT hr;

// Initialize OLE on this thread.

hr = CoInitialize(NULL);

if (SUCCEEDED(hr))
{
IRemoteDebugManager *pirdm;

// Create a RemoteJavaDebugManager from JDbgMgr.exe to initiate debugging.

hr = CoCreateInstance(CLSID_RemoteJavaDebugManager, NULL,
CLSCTX_LOCAL_SERVER, IID_IRemoteDebugManager,
(PVOID *)&pirdm);

if (hr == S_OK)
{
JavaDebugger *pjd;

// Create a JavaDebugger object to run a simple debugging session.

pjd = new(JavaDebugger);

if (pjd)
{
hr = pjd->Initialize(pirdm);

if (hr == S_OK)
{
hr = pjd->Debug(s_cszDebugClass);

pjd->Terminate();
}

pjd->Release();
pjd = NULL;
}
else
hr = E_OUTOFMEMORY;

pirdm->Release();
pirdm = NULL;
}

// Uninitialize OLE on this thread.

CoUninitialize();
}

return(hr);
}

#pragma warning(default:4100) // "unreferenced formal parameter" warning


/********************************** Methods **********************************/


JavaDebugger::JavaDebugger(void)
{
m_pirdm = NULL;
m_pirp = NULL;
m_pwszDebugClass = NULL;

return;
}


JavaDebugger::~JavaDebugger(void)
{
Terminate();

return;
}


HRESULT JavaDebugger::Initialize(IRemoteDebugManager *pirdm)
{
HRESULT hr;

// Register this JavaDebugger's callback with the debug manager so it is notified when its debuggee target class is run.

hr = pirdm->RegisterCallback(this);

if (hr == S_OK)
{
m_pirdm = pirdm;
m_pirdm->AddRef();
}

return(hr);
}


void JavaDebugger::Terminate(void)
{
if (m_pirdm)
{
// Detach this JavaDebugger from the debug manager.

m_pirdm->Detach();

m_pirdm->Release();
m_pirdm = NULL;
}

SAFE_DELETE(m_pwszDebugClass);

return;
}


//
// Run a message loop on this thread until a WM_QUIT message is encountered.
//
UINT JavaDebugger::RunMessageLoop(void)
{
MSG msg;

// No accelerators to load.

// Get and dispatch messages until a WM_QUIT message is received.

ZeroMemory(&msg, sizeof(msg));

while (GetMessage(&msg, NULL, 0, 0))
{
// Translate virtual key codes.

TranslateMessage(&msg);

// Dispatch message to target window.

DispatchMessage(&msg);
}

return(msg.wParam);
}


//
// Terminate any message loop running on this thread.
//
void JavaDebugger::QuitMessageLoop(UINT uResult)
{
PostQuitMessage(uResult);

return;
}


//
// Initiate a simple debugging session.
//
HRESULT JavaDebugger::Debug(LPCSTR pcszDebugClass)
{
HRESULT hr;

// Create the Java VM Debug key to enable Java debugging.

if (CreateDebugKey())
{
// Remember the debuggee class name.

SAFE_DELETE(m_pwszDebugClass);

hr = ANSIToUnicode(pcszDebugClass, &m_pwszDebugClass);

if (hr == S_OK)
{
LPCSTR pcszCmdLine;
STARTUPINFO si;
PROCESS_INFORMATION pi;

// Treat our command line arguments as the command line to run the debuggee class.

pcszCmdLine = SkipWhiteSpace(SkipPossiblyQuotedArg(GetCommandLine()));

ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

// Start the debuggee class process suspended so we can get its process ID before it executes.

hr = CreateProcess(NULL, (LPSTR)pcszCmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) ? S_OK : E_FAIL;

if (hr == S_OK)
{
HRESULT hrResume;

// Ask the debug manager to notify us when the debuggee class is run.

hr = m_pirdm->RequestCreateEvent(m_pwszDebugClass, pi.dwProcessId);

// Resume the debuggee class process to begin debugging.

hrResume = (ResumeThread(pi.hThread) != 0xffffffff) ? S_OK : E_FAIL;

if (hr == S_OK)
hr = hrResume;

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

// Run a message loop to dispatch OLE RPC messages.

RunMessageLoop();
}
}

// Delete the Java VM Debug key, if it hasn't already been deleted, to disable Java debugging.

DeleteDebugKey();
}

return(hr);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::QueryInterface(REFIID riid,
PVOID *ppvObject)
{
HRESULT hr = S_OK;

if (riid == IID_IRemoteDebugManagerCallback)
*ppvObject = SAFE_CAST(IRemoteDebugManagerCallback *, this);
else if (riid == IID_IRemoteProcessCallback)
*ppvObject = SAFE_CAST(IRemoteProcessCallback *, this);
else if (riid == IID_IUnknown)
*ppvObject = SAFE_CAST(IUnknown *, (IRemoteDebugManagerCallback *)this);
else
{
*ppvObject = NULL;
hr = E_NOINTERFACE;
}

if (hr == S_OK)
AddRef();

return(hr);
}


//
// Debugger event notification methods return an HRESULT as follows:
//
// S_FALSE Continue execution.
//
// S_OK Suspend execution of all threads in this VM until an
// IRemoteThread method is called on this thread to resume
// execution.
//
// E_... Error.
//

#pragma warning(disable:4100) // "unreferenced formal parameter" warning

HRESULT STDMETHODCALLTYPE JavaDebugger::ProcessCreateEvent(
IRemoteProcess *pirpNew,
IRemoteProcess *pirpParent)
{
HRESULT hr;

// Register this JavaDebugger's callback with the Java VM so it is notified when interesting events occur in the debuggee.

hr = pirpNew->RegisterCallback(this);

if (hr == S_OK)
{
m_pirp = pirpNew;
m_pirp->AddRef();
}

MyMessageBox(NULL, (MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND), "Process created.");

// Delete the Java VM Debug key to disable Java debugging.

DeleteDebugKey();

return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::DebugStringEvent(IRemoteThread *pirth, LPCWSTR pcwszDebugMsg)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::CodeBreakpointEvent(IRemoteThread *pirth)
{
HRESULT hr;
IRemoteStackFrame *pirsf;

// Get the method object from the current thread's current stack frame.

hr = pirth->GetCurrentFrame(&pirsf);

if (hr == S_OK)
{
IRemoteContainerObject *pirco;

hr = pirsf->GetMethodObject(&pirco);

if (hr == S_OK)
{
IRemoteField *pirf;

// Get the method field from the method object.

hr = pirco->GetType(&pirf);

if (hr == S_OK)
{
IRemoteMethodField *pirmf;

hr = pirf->QueryInterface(IID_IRemoteMethodField, (PVOID *)&pirmf);

if (hr == S_OK)
{
// Clear the breakpoint, and continue execution.

hr = pirmf->ClearBreakpoint(s_ulcBreakpointPC);

if (hr == S_OK)
MyMessageBox(NULL, (MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND), "Hit breakpoint %ls.%ls().%lu.",
m_pwszDebugClass,
s_cwszDebugMethod,
s_ulcBreakpointPC);

pirmf->Release();
pirmf = NULL;
}

pirf->Release();
pirf = NULL;
}

pirco->Release();
pirco = NULL;
}

pirsf->Release();
pirsf = NULL;
}

return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::DataBreakpointEvent(IRemoteThread *pirth, IRemoteObject *piro)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ExceptionEvent(IRemoteThread *pirth, IRemoteClassField *pircfException, EXCEPTIONKIND exk)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::StepEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::CanStopEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::BreakEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ThreadCreateEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ThreadDestroyEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ThreadGroupCreateEvent(IRemoteThread *pirth, IRemoteThreadGroup *pirthg)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ThreadGroupDestroyEvent(IRemoteThread *pirth, IRemoteThreadGroup *pirthg)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ClassLoadEvent(IRemoteThread *pirth, IRemoteClassField *pircfClass)
{
LPWSTR pwszClassName;

// Get the name of the loaded class.

if (pircfClass->GetName(&pwszClassName) == S_OK)
{
// Is this the class that a breakpoint is to be set in?

if (! wcscmp(pwszClassName, m_pwszDebugClass))
{
IJavaEnumRemoteField *pijerf;

// Yes. Get the method field for the method to set a breakpoint in.

if (pircfClass->GetFields(&pijerf, FIELD_KIND_METHOD, 0, s_cwszDebugMethod) == S_OK)
{
IRemoteField *pirf;
ULONG ulcFetched;

if (pijerf->Next(1, &pirf, &ulcFetched) == S_OK)
{
IRemoteMethodField *pirmf;

if (pirf->QueryInterface(IID_IRemoteMethodField, (PVOID *)&pirmf) == S_OK)
{
// Set the breakpoint, and continue execution.

pirmf->SetBreakpoint(s_ulcBreakpointPC);

pirmf->Release();
pirmf = NULL;
}

pirf->Release();
pirf = NULL;
}

pijerf->Release();
pijerf = NULL;
}
}

CoTaskMemFree(pwszClassName);
pwszClassName = NULL;
}

return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ClassUnloadEvent(IRemoteThread *pirth, IRemoteClassField *pircfClass)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::ProcessDestroyEvent(IRemoteThread *pirth)
{
// Detach this JavaDebugger from the Java VM.

m_pirp->Detach();

m_pirp->Release();
m_pirp = NULL;

MyMessageBox(NULL, (MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND), "Process destroyed.");

// Quit the message loop, and end the debugging session.

QuitMessageLoop(0);

return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::TraceEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}


HRESULT STDMETHODCALLTYPE JavaDebugger::LoadCompleteEvent(IRemoteThread *pirth)
{
return(S_FALSE);
}

#pragma warning(default:4100) // "unreferenced formal parameter" warning