/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
// JS debugger temporarily disabled during the SpiderMonkey upgrade (check trac ticket #2348 for details)
#include "precompiled.h" // needs to be here for Windows builds
/*
#include "DebuggingServer.h"
#include "ThreadDebugger.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
CDebuggingServer* g_DebuggingServer = NULL;
const char* CDebuggingServer::header400 =
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain; charset=utf-8\r\n\r\n"
"Invalid request";
void CDebuggingServer::GetAllCallstacks(std::stringstream& response)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::stringstream stream;
uint nbrCallstacksWritten = 0;
std::list::iterator itr;
if (!m_ThreadDebuggers.empty())
{
response << "[";
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetIsInBreak())
{
stream.str(std::string());
(*itr)->GetCallstack(stream);
if ((int)stream.tellp() != 0)
{
if (nbrCallstacksWritten != 0)
response << ",";
response << "{" << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ", \"CallStack\" : " << stream.str() << "}";
nbrCallstacksWritten++;
}
}
}
response << "]";
}
}
void CDebuggingServer::GetStackFrameData(std::stringstream& response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::stringstream stream;
std::list::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetID() == threadDebuggerID && (*itr)->GetIsInBreak())
{
(*itr)->GetStackFrameData(stream, nestingLevel, stackInfoKind);
if ((int)stream.tellp() != 0)
{
response << stream.str();
}
}
}
}
CDebuggingServer::CDebuggingServer() :
m_MgContext(NULL)
{
m_BreakPointsSem = SDL_CreateSemaphore(0);
ENSURE(m_BreakPointsSem);
SDL_SemPost(m_BreakPointsSem);
m_LastThreadDebuggerID = 0; // Next will be 1, 0 is reserved
m_BreakRequestedByThread = false;
m_BreakRequestedByUser = false;
m_SettingSimultaneousThreadBreak = true;
m_SettingBreakOnException = true;
EnableHTTP();
LOGWARNING("Javascript debugging webserver enabled.");
}
CDebuggingServer::~CDebuggingServer()
{
SDL_DestroySemaphore(m_BreakPointsSem);
if (m_MgContext)
{
mg_stop(m_MgContext);
m_MgContext = NULL;
}
}
bool CDebuggingServer::SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd)
{
CScopeLock lock(m_Mutex);
std::list::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetID() == threadDebuggerID || threadDebuggerID == 0)
{
if (DBG_CMD_NONE == (*itr)->GetNextDbgCmd() && (*itr)->GetIsInBreak())
{
SetBreakRequestedByThread(false);
SetBreakRequestedByUser(false);
(*itr)->SetNextDbgCmd(dbgCmd);
}
}
}
return true;
}
void CDebuggingServer::SetBreakRequestedByThread(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_BreakRequestedByThread = Enabled;
}
bool CDebuggingServer::GetBreakRequestedByThread()
{
CScopeLock lock(m_Mutex1);
return m_BreakRequestedByThread;
}
void CDebuggingServer::SetBreakRequestedByUser(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_BreakRequestedByUser = Enabled;
}
bool CDebuggingServer::GetBreakRequestedByUser()
{
CScopeLock lock(m_Mutex1);
return m_BreakRequestedByUser;
}
void CDebuggingServer::SetSettingBreakOnException(bool Enabled)
{
CScopeLock lock(m_Mutex);
m_SettingBreakOnException = Enabled;
}
void CDebuggingServer::SetSettingSimultaneousThreadBreak(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_SettingSimultaneousThreadBreak = Enabled;
}
bool CDebuggingServer::GetSettingBreakOnException()
{
CScopeLock lock(m_Mutex);
return m_SettingBreakOnException;
}
bool CDebuggingServer::GetSettingSimultaneousThreadBreak()
{
CScopeLock lock(m_Mutex1);
return m_SettingSimultaneousThreadBreak;
}
static Status AddFileResponse(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& templates = *(std::vector*)cbData;
std::wstring str(pathname.string());
templates.push_back(std::string(str.begin(), str.end()));
return INFO::OK;
}
void CDebuggingServer::EnumVfsJSFiles(std::stringstream& response)
{
VfsPath path = L"";
response.str(std::string());
std::vector templates;
vfs::ForEachFile(g_VFS, "", AddFileResponse, (uintptr_t)&templates, L"*.js", vfs::DIR_RECURSIVE);
std::vector::iterator itr;
response << "[";
for (itr = templates.begin(); itr != templates.end(); ++itr)
{
if (itr != templates.begin())
response << ",";
response << "\"" << *itr << "\"";
}
response << "]";
}
void CDebuggingServer::GetFile(std::string filename, std::stringstream& response)
{
CVFSFile file;
if (file.Load(g_VFS, filename) != PSRETURN_OK)
{
response << "Failed to load the file contents";
return;
}
std::string code = file.DecodeUTF8(); // assume it's UTF-8
response << code;
}
static void* MgDebuggingServerCallback_(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
{
CDebuggingServer* debuggingServer = (CDebuggingServer*)request_info->user_data;
ENSURE(debuggingServer);
return debuggingServer->MgDebuggingServerCallback(event, conn, request_info);
}
void* CDebuggingServer::MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
{
void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
const char* header200 =
"HTTP/1.1 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
"Content-Type: text/plain; charset=utf-8\r\n\r\n";
const char* header404 =
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/plain; charset=utf-8\r\n\r\n"
"Unrecognised URI";
switch (event)
{
case MG_NEW_REQUEST:
{
std::stringstream stream;
std::string uri = request_info->uri;
if (uri == "/GetThreadDebuggerStatus")
{
GetThreadDebuggerStatus(stream);
}
else if (uri == "/EnumVfsJSFiles")
{
EnumVfsJSFiles(stream);
}
else if (uri == "/GetAllCallstacks")
{
GetAllCallstacks(stream);
}
else if (uri == "/Continue")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_CONTINUE);
}
else if (uri == "/Break")
{
SetBreakRequestedByUser(true);
}
else if (uri == "/SetSettingSimultaneousThreadBreak")
{
std::string strEnabled;
bool bEnabled = false;
if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
return handled;
// TODO: handle the return value
if (strEnabled == "true")
bEnabled = true;
else if (strEnabled == "false")
bEnabled = false;
else
return handled; // TODO: return an error state
SetSettingSimultaneousThreadBreak(bEnabled);
}
else if (uri == "/GetSettingSimultaneousThreadBreak")
{
stream << "{ \"Enabled\" : " << (GetSettingSimultaneousThreadBreak() ? "true" : "false") << " } ";
}
else if (uri == "/SetSettingBreakOnException")
{
std::string strEnabled;
bool bEnabled = false;
if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
return handled;
// TODO: handle the return value
if (strEnabled == "true")
bEnabled = true;
else if (strEnabled == "false")
bEnabled = false;
else
return handled; // TODO: return an error state
SetSettingBreakOnException(bEnabled);
}
else if (uri == "/GetSettingBreakOnException")
{
stream << "{ \"Enabled\" : " << (GetSettingBreakOnException() ? "true" : "false") << " } ";
}
else if (uri == "/Step")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_SINGLESTEP);
}
else if (uri == "/StepInto")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPINTO);
}
else if (uri == "/StepOut")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPOUT);
}
else if (uri == "/GetStackFrame")
{
uint nestingLevel;
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_LOCALS);
}
else if (uri == "/GetStackFrameThis")
{
uint nestingLevel;
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_THIS);
}
else if (uri == "/GetCurrentGlobalObject")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, 0, threadDebuggerID, STACK_INFO_GLOBALOBJECT);
}
else if (uri == "/ToggleBreakpoint")
{
std::string filename;
uint line;
if (!GetWebArgs(conn, request_info, "filename", filename) ||
!GetWebArgs(conn, request_info, "line", line))
{
return handled;
}
ToggleBreakPoint(filename, line);
}
else if (uri == "/GetFile")
{
std::string filename;
if (!GetWebArgs(conn, request_info, "filename", filename))
return handled;
GetFile(filename, stream);
}
else
{
mg_printf(conn, "%s", header404);
return handled;
}
mg_printf(conn, "%s", header200);
std::string str = stream.str();
mg_write(conn, str.c_str(), str.length());
return handled;
}
case MG_HTTP_ERROR:
return NULL;
case MG_EVENT_LOG:
// Called by Mongoose's cry()
LOGERROR("Mongoose error: %s", request_info->log_message);
return NULL;
case MG_INIT_SSL:
return NULL;
default:
debug_warn(L"Invalid Mongoose event type");
return NULL;
}
};
void CDebuggingServer::EnableHTTP()
{
// Ignore multiple enablings
if (m_MgContext)
return;
const char *options[] = {
"listening_ports", "127.0.0.1:9000", // bind to localhost for security
"num_threads", "6", // enough for the browser's parallel connection limit
NULL
};
m_MgContext = mg_start(MgDebuggingServerCallback_, this, options);
ENSURE(m_MgContext);
}
bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, uint& arg)
{
if (!request_info->query_string)
{
mg_printf(conn, "%s (no query string)", header400);
return false;
}
char buf[256];
int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
if (len < 0)
{
mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
return false;
}
arg = atoi(buf);
return true;
}
bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, std::string& arg)
{
if (!request_info->query_string)
{
mg_printf(conn, "%s (no query string)", header400);
return false;
}
char buf[256];
int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
if (len < 0)
{
mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
return false;
}
arg = buf;
return true;
}
void CDebuggingServer::RegisterScriptinterface(std::string name, ScriptInterface* pScriptInterface)
{
CScopeLock lock(m_Mutex);
CThreadDebugger* pThreadDebugger = new CThreadDebugger;
// ThreadID 0 is reserved
pThreadDebugger->Initialize(++m_LastThreadDebuggerID, name, pScriptInterface, this);
m_ThreadDebuggers.push_back(pThreadDebugger);
}
void CDebuggingServer::UnRegisterScriptinterface(ScriptInterface* pScriptInterface)
{
CScopeLock lock(m_Mutex);
std::list::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->CompareScriptInterfacePtr(pScriptInterface))
{
delete (*itr);
m_ThreadDebuggers.erase(itr);
break;
}
}
}
void CDebuggingServer::GetThreadDebuggerStatus(std::stringstream& response)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::list::iterator itr;
response << "[";
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if (itr == m_ThreadDebuggers.begin())
response << "{ ";
else
response << ",{ ";
response << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ",";
response << "\"ScriptInterfaceName\" : \"" << (*itr)->GetName() << "\",";
response << "\"ThreadInBreak\" : " << ((*itr)->GetIsInBreak() ? "true" : "false") << ",";
response << "\"BreakFileName\" : \"" << (*itr)->GetBreakFileName() << "\",";
response << "\"BreakLine\" : " << (*itr)->GetLastBreakLine();
response << " }";
}
response << "]";
}
void CDebuggingServer::ToggleBreakPoint(std::string filename, uint line)
{
// First, pass the message to all associated CThreadDebugger objects and check if one returns true (handled);
{
CScopeLock lock(m_Mutex);
std::list::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->ToggleBreakPoint(filename, line))
return;
}
}
// If the breakpoint isn't handled yet search the breakpoints registered in this class
std::list* pBreakPoints = NULL;
double breakPointsLockID = AquireBreakPointAccess(&pBreakPoints);
// If set, delete
bool deleted = false;
for (std::list::iterator itr = pBreakPoints->begin(); itr != pBreakPoints->end(); ++itr)
{
if ((*itr).m_Filename == filename && (*itr).m_UserLine == line)
{
itr = pBreakPoints->erase(itr);
deleted = true;
break;
}
}
// If not set, set
if (!deleted)
{
CBreakPoint bP;
bP.m_Filename = filename;
bP.m_UserLine = line;
pBreakPoints->push_back(bP);
}
ReleaseBreakPointAccess(breakPointsLockID);
return;
}
double CDebuggingServer::AquireBreakPointAccess(std::list** breakPoints)
{
int ret;
ret = SDL_SemWait(m_BreakPointsSem);
ENSURE(ret == 0);
(*breakPoints) = &m_BreakPoints;
m_BreakPointsLockID = timer_Time();
return m_BreakPointsLockID;
}
void CDebuggingServer::ReleaseBreakPointAccess(double breakPointsLockID)
{
ENSURE(m_BreakPointsLockID == breakPointsLockID);
SDL_SemPost(m_BreakPointsSem);
}
*/