blob: b5038dbdecf462c334d92fa819a6508b7266d7a6 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "stdafx.h"
#define MAX_BASELINE_SIZE (1024*1024*200)
void CHAKRA_CALLBACK Debugger::DebugEventHandler(_In_ JsDiagDebugEvent debugEvent, _In_ JsValueRef eventData, _In_opt_ void* callbackState)
{
Debugger* debugger = (Debugger*)callbackState;
debugger->HandleDebugEvent(debugEvent, eventData);
}
JsValueRef Debugger::GetSource(JsValueRef callee, bool isConstructCall, JsValueRef * arguments, unsigned short argumentCount, void * callbackState)
{
int scriptId;
JsValueRef source = JS_INVALID_REFERENCE;
if (argumentCount > 1)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &scriptId));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetSource(scriptId, &source));
}
return source;
}
JsValueRef Debugger::SetBreakpoint(JsValueRef callee, bool isConstructCall, JsValueRef * arguments, unsigned short argumentCount, void * callbackState)
{
int scriptId;
int line;
int column;
JsValueRef bpObject = JS_INVALID_REFERENCE;
if (argumentCount > 3)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &scriptId));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[2], &line));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[3], &column));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagSetBreakpoint(scriptId, line, column, &bpObject));
}
return bpObject;
}
JsValueRef Debugger::GetStackTrace(JsValueRef callee, bool isConstructCall, JsValueRef * arguments, unsigned short argumentCount, void * callbackState)
{
JsValueRef stackInfo = JS_INVALID_REFERENCE;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetStackTrace(&stackInfo));
return stackInfo;
}
JsValueRef Debugger::GetBreakpoints(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsValueRef breakpoints = JS_INVALID_REFERENCE;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetBreakpoints(&breakpoints));
return breakpoints;
}
JsValueRef Debugger::RemoveBreakpoint(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
int bpId;
if (argumentCount > 1)
{
JsValueRef numberValue;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsConvertValueToNumber(arguments[1], &numberValue));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(numberValue, &bpId));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagRemoveBreakpoint(bpId));
}
return JS_INVALID_REFERENCE;
}
JsValueRef Debugger::SetBreakOnException(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
int exceptionAttributes;
if (argumentCount > 1)
{
JsValueRef breakOnException;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsConvertValueToNumber(arguments[1], &breakOnException));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(breakOnException, &exceptionAttributes));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagSetBreakOnException(Debugger::GetRuntime(), (JsDiagBreakOnExceptionAttributes)exceptionAttributes));
}
return JS_INVALID_REFERENCE;
}
JsValueRef Debugger::GetBreakOnException(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsDiagBreakOnExceptionAttributes exceptionAttributes;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetBreakOnException(Debugger::GetRuntime(), &exceptionAttributes));
JsValueRef exceptionAttributesRef;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDoubleToNumber((double)exceptionAttributes, &exceptionAttributesRef));
return exceptionAttributesRef;
}
JsValueRef Debugger::SetStepType(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
int stepType;
if (argumentCount > 1)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &stepType));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagSetStepType((JsDiagStepType)stepType));
}
return JS_INVALID_REFERENCE;
}
JsValueRef Debugger::GetScripts(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsValueRef sourcesList = JS_INVALID_REFERENCE;
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetScripts(&sourcesList));
return sourcesList;
}
JsValueRef Debugger::GetStackProperties(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsValueRef properties = JS_INVALID_REFERENCE;
int stackFrameIndex;
if (argumentCount > 1)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &stackFrameIndex));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetStackProperties(stackFrameIndex, &properties));
}
return properties;
}
JsValueRef Debugger::GetProperties(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsValueRef properties = JS_INVALID_REFERENCE;
int objectHandle;
int fromCount;
int totalCount;
if (argumentCount > 3)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &objectHandle));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[2], &fromCount));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[3], &totalCount));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetProperties(objectHandle, fromCount, totalCount, &properties));
}
return properties;
}
JsValueRef Debugger::GetObjectFromHandle(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsValueRef properties = JS_INVALID_REFERENCE;
int objectHandle;
if (argumentCount > 1)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &objectHandle));
IfJsErrorFailLogAndRet(ChakraRTInterface::JsDiagGetObjectFromHandle(objectHandle, &properties));
}
return properties;
}
JsValueRef Debugger::Evaluate(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
int stackFrameIndex;
JsValueRef result = JS_INVALID_REFERENCE;
if (argumentCount > 2)
{
IfJsErrorFailLogAndRet(ChakraRTInterface::JsNumberToInt(arguments[1], &stackFrameIndex));
ChakraRTInterface::JsDiagEvaluate(arguments[2], stackFrameIndex, JsParseScriptAttributeNone, /* forceSetValueProp */ false, &result);
}
return result;
}
Debugger::Debugger(JsRuntimeHandle runtime)
{
this->m_runtime = runtime;
this->m_context = JS_INVALID_REFERENCE;
this->m_isDetached = true;
}
Debugger::~Debugger()
{
if (this->m_context != JS_INVALID_REFERENCE)
{
ChakraRTInterface::JsRelease(this->m_context, nullptr);
this->m_context = JS_INVALID_REFERENCE;
}
this->m_runtime = JS_INVALID_RUNTIME_HANDLE;
}
Debugger * Debugger::GetDebugger(JsRuntimeHandle runtime)
{
if (Debugger::debugger == nullptr)
{
Debugger::debugger = new Debugger(runtime);
Debugger::debugger->Initialize();
}
return Debugger::debugger;
}
void Debugger::CloseDebugger()
{
if (Debugger::debugger != nullptr)
{
delete Debugger::debugger;
Debugger::debugger = nullptr;
}
}
JsRuntimeHandle Debugger::GetRuntime()
{
return Debugger::debugger != nullptr ? Debugger::debugger->m_runtime : JS_INVALID_RUNTIME_HANDLE;
}
bool Debugger::Initialize()
{
// Create a new context and run dbgcontroller.js in that context
// setup dbgcontroller.js callbacks
Assert(this->m_context == JS_INVALID_REFERENCE);
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsCreateContext(this->m_runtime, &this->m_context));
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsAddRef(this->m_context, nullptr)); // Pin context
AutoRestoreContext autoRestoreContext(this->m_context);
JsValueRef globalFunc = JS_INVALID_REFERENCE;
JsValueRef scriptSource;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsCreateExternalArrayBuffer(
(void*)controllerScript, (unsigned int)strlen(controllerScript),
nullptr, nullptr, &scriptSource));
JsValueRef fname;
ChakraRTInterface::JsCreateString(
"DbgController.js", strlen("DbgController.js"), &fname);
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsParse(scriptSource,
JS_SOURCE_CONTEXT_NONE, fname, JsParseScriptAttributeLibraryCode,
&globalFunc));
JsValueRef undefinedValue;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetUndefinedValue(&undefinedValue));
JsValueRef args[] = { undefinedValue };
JsValueRef result = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsCallFunction(globalFunc, args, _countof(args), &result));
JsValueRef globalObj = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetGlobalObject(&globalObj));
JsPropertyIdRef hostDebugObjectPropId;
IfJsrtErrorFailLogAndRetFalse(CreatePropertyIdFromString("hostDebugObject", &hostDebugObjectPropId));
JsPropertyIdRef hostDebugObject;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetProperty(globalObj, hostDebugObjectPropId, &hostDebugObject));
this->InstallDebugCallbacks(hostDebugObject);
if (!WScriptJsrt::Initialize())
{
return false;
}
if (HostConfigFlags::flags.dbgbaselineIsEnabled)
{
this->SetBaseline();
}
if (HostConfigFlags::flags.InspectMaxStringLengthIsEnabled)
{
this->SetInspectMaxStringLength();
}
return true;
}
bool Debugger::InstallDebugCallbacks(JsValueRef hostDebugObject)
{
HRESULT hr = S_OK;
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetSource", Debugger::GetSource));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagSetBreakpoint", Debugger::SetBreakpoint));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetStackTrace", Debugger::GetStackTrace));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetBreakpoints", Debugger::GetBreakpoints));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagRemoveBreakpoint", Debugger::RemoveBreakpoint));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagSetBreakOnException", Debugger::SetBreakOnException));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetBreakOnException", Debugger::GetBreakOnException));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagSetStepType", Debugger::SetStepType));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetScripts", Debugger::GetScripts));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetStackProperties", Debugger::GetStackProperties));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetProperties", Debugger::GetProperties));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagGetObjectFromHandle", Debugger::GetObjectFromHandle));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(hostDebugObject, "JsDiagEvaluate", Debugger::Evaluate));
Error:
return hr != S_OK;
}
bool Debugger::SetBaseline()
{
#ifdef _WIN32
LPSTR script = nullptr;
FILE *file = nullptr;
size_t numChars = 0;
HRESULT hr = S_OK;
if (_wfopen_s(&file, HostConfigFlags::flags.dbgbaseline, _u("rb")) != 0)
{
Helpers::LogError(_u("opening baseline file '%s'"), HostConfigFlags::flags.dbgbaseline);
}
if(file != nullptr)
{
long fileSize = _filelength(_fileno(file));
if (0 <= fileSize && fileSize <= MAX_BASELINE_SIZE)
{
script = new char[(size_t)fileSize + 1];
numChars = fread(script, sizeof(script[0]), fileSize, file);
if (numChars == (size_t)fileSize)
{
script[numChars] = '\0';
JsValueRef wideScriptRef;
IfJsErrorFailLogAndHR(ChakraRTInterface::JsCreateString(
script, strlen(script), &wideScriptRef));
this->CallFunctionNoResult("SetBaseline", wideScriptRef);
}
else
{
Helpers::LogError(_u("failed to read from baseline file"));
IfFailGo(E_FAIL);
}
}
else
{
Helpers::LogError(_u("baseline file too large"));
IfFailGo(E_FAIL);
}
}
Error:
if (script)
{
delete[] script;
}
if (file)
{
fclose(file);
}
return hr == S_OK;
#else
// xplat-todo: Implement this on Linux
return false;
#endif
}
bool Debugger::SetInspectMaxStringLength()
{
Assert(HostConfigFlags::flags.InspectMaxStringLength > 0);
JsValueRef maxStringRef;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsDoubleToNumber(HostConfigFlags::flags.InspectMaxStringLength, &maxStringRef));
return this->CallFunctionNoResult("SetInspectMaxStringLength", maxStringRef);
}
bool Debugger::CallFunction(char const * functionName, JsValueRef *result, JsValueRef arg1, JsValueRef arg2)
{
AutoRestoreContext autoRestoreContext(this->m_context);
// Get the global object
JsValueRef globalObj = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetGlobalObject(&globalObj));
// Get a script string for the function name
JsPropertyIdRef targetFuncId = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(CreatePropertyIdFromString(functionName, &targetFuncId));
// Get the target function
JsValueRef targetFunc = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetProperty(globalObj, targetFuncId, &targetFunc));
static const unsigned short MaxArgs = 2;
JsValueRef args[MaxArgs + 1];
// Pass in undefined for 'this'
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetUndefinedValue(&args[0]));
unsigned short argCount = 1;
if (arg1 != JS_INVALID_REFERENCE)
{
args[argCount++] = arg1;
}
Assert(arg2 == JS_INVALID_REFERENCE || argCount != 1);
if (arg2 != JS_INVALID_REFERENCE)
{
args[argCount++] = arg2;
}
// Call the function
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsCallFunction(targetFunc, args, argCount, result));
return true;
}
bool Debugger::CallFunctionNoResult(char const * functionName, JsValueRef arg1, JsValueRef arg2)
{
JsValueRef result = JS_INVALID_REFERENCE;
return this->CallFunction(functionName, &result, arg1, arg2);
}
bool Debugger::DumpFunctionPosition(JsValueRef functionPosition)
{
return this->CallFunctionNoResult("DumpFunctionPosition", functionPosition);
}
bool Debugger::StartDebugging(JsRuntimeHandle runtime)
{
JsErrorCode errorCode = ChakraRTInterface::JsDiagStartDebugging(runtime, Debugger::DebugEventHandler, this);
if (errorCode == JsErrorCode::JsErrorDiagAlreadyInDebugMode)
{
return false;
}
IfJsrtErrorFailLogAndRetFalse(errorCode);
this->m_isDetached = false;
return true;
}
bool Debugger::StopDebugging(JsRuntimeHandle runtime)
{
void* callbackState = nullptr;
JsErrorCode errorCode = ChakraRTInterface::JsDiagStopDebugging(runtime, &callbackState);
if (errorCode == JsErrorCode::JsErrorDiagNotInDebugMode)
{
return false;
}
IfJsrtErrorFailLogAndRetFalse(errorCode);
Assert(callbackState == this);
this->m_isDetached = true;
return true;
}
bool Debugger::HandleDebugEvent(JsDiagDebugEvent debugEvent, JsValueRef eventData)
{
JsValueRef debugEventRef;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsDoubleToNumber(debugEvent, &debugEventRef));
return this->CallFunctionNoResult("HandleDebugEvent", debugEventRef, eventData);
}
bool Debugger::CompareOrWriteBaselineFile(LPCSTR fileName)
{
AutoRestoreContext autoRestoreContext(this->m_context);
// Pass in undefined for 'this'
JsValueRef undefinedRef;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsGetUndefinedValue(&undefinedRef));
JsValueRef result = JS_INVALID_REFERENCE;
bool testPassed = false;
if (HostConfigFlags::flags.dbgbaselineIsEnabled)
{
this->CallFunction("Verify", &result);
JsValueRef numberVal;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsConvertValueToNumber(result, &numberVal));
int val;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsNumberToInt(numberVal, &val));
testPassed = (val == 0);
}
if (!testPassed)
{
this->CallFunction("GetOutputJson", &result);
AutoString baselineData;
IfJsrtErrorFailLogAndRetFalse(baselineData.Initialize(result));
char16 baselineFilename[256];
swprintf_s(baselineFilename, HostConfigFlags::flags.dbgbaselineIsEnabled ? _u("%S.dbg.baseline.rebase") : _u("%S.dbg.baseline"), fileName);
FILE *file = nullptr;
if (_wfopen_s(&file, baselineFilename, _u("wt")) != 0)
{
return false;
}
if (file != nullptr)
{
int countWritten = static_cast<int>(fwrite(baselineData.GetString(), sizeof(char), baselineData.GetLength(), file));
Assert(baselineData.GetLength() <= INT_MAX);
if (countWritten != (int)baselineData.GetLength())
{
Assert(false);
return false;
}
fclose(file);
}
if (!HostConfigFlags::flags.dbgbaselineIsEnabled)
{
wprintf(_u("No baseline file specified, baseline created at '%s'\n"), baselineFilename);
}
else
{
Helpers::LogError(_u("Rebase file created at '%s'\n"), baselineFilename);
}
}
return true;
}
bool Debugger::SourceRunDown()
{
AutoRestoreContext autoRestoreContext(this->m_context);
JsValueRef sourcesList = JS_INVALID_REFERENCE;
IfJsrtErrorFailLogAndRetFalse(ChakraRTInterface::JsDiagGetScripts(&sourcesList));
return this->CallFunctionNoResult("HandleSourceRunDown", sourcesList);
}