Files
qt-creator/src/libs/qtcreatorcdbext/gdbmihelpers.cpp

623 lines
21 KiB
C++
Raw Normal View History

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
2010-12-17 17:14:20 +01:00
** No Commercial Usage
**
2010-12-17 17:14:20 +01:00
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
2010-12-17 17:14:20 +01:00
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#include "gdbmihelpers.h"
#include "stringutils.h"
#include "iinterfacepointer.h"
#include "base64.h"
#include <vector>
StackFrame::StackFrame(ULONG64 a) : address(a), line(0) {}
std::wstring StackFrame::fileName() const
{
std::wstring::size_type lastSlash = fullPathName.rfind(L'\\');
if (lastSlash == std::wstring::npos)
return fullPathName;
return fullPathName.substr(lastSlash + 1, fullPathName.size() - lastSlash - 1);
}
void StackFrame::formatGDBMI(std::ostream &str, unsigned level) const
{
str << "frame={level=\"" << level << "\",addr=\"0x"
<< std::hex << address << std::dec << '"';
if (!function.empty()) {
// Split into module/function
const std::wstring::size_type exclPos = function.find('!');
if (exclPos == std::wstring::npos) {
str << ",func=\"" << gdbmiWStringFormat(function) << '"';
} else {
const std::wstring module = function.substr(0, exclPos);
const std::wstring fn = function.substr(exclPos + 1, function.size() - exclPos - 1);
str << ",func=\"" << gdbmiWStringFormat(fn)
<< "\",from=\"" << gdbmiWStringFormat(module) << '"';
}
}
if (!fullPathName.empty()) { // Creator/gdbmi expects 'clean paths'
std::wstring cleanPath = fullPathName;
replace(cleanPath, L'\\', L'/');
str << ",fullname=\"" << gdbmiWStringFormat(fullPathName)
<< "\",file=\"" << gdbmiWStringFormat(fileName()) << "\",line=\"" << line << '"';
}
str << '}';
}
Thread::Thread(ULONG i, ULONG sysId) : id(i), systemId(sysId) {}
void Thread::formatGDBMI(std::ostream &str) const
{
str << "{id=\"" << id << "\",target-id=\"" << systemId << "\",";
frame.formatGDBMI(str);
if (!name.empty())
str << ",name=\"" << gdbmiWStringFormat(name) << '"';
if (!state.empty())
str << ",state=\"" << state << '"';
str << '}';
}
static inline std::string msgGetThreadsFailed(const std::string &why)
{
return std::string("Unable to determine the thread information: ") + why;
}
static inline bool setCurrentThread(CIDebugSystemObjects *debugSystemObjects,
ULONG id, std::string *errorMessage)
{
const HRESULT hr = debugSystemObjects->SetCurrentThreadId(id);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("SetCurrentThreadId", hr);
return false;
}
return true;
}
// Fill in stack frame info
void getFrame(CIDebugSymbols *debugSymbols,
const DEBUG_STACK_FRAME &s, StackFrame *f)
{
WCHAR wBuf[MAX_PATH];
f->address = s.InstructionOffset;
HRESULT hr = debugSymbols->GetNameByOffsetWide(f->address, wBuf, MAX_PATH, 0, 0);
if (SUCCEEDED(hr)) {
f->function = wBuf;
} else {
f->function.clear();
}
ULONG64 ul64Displacement = 0;
hr = debugSymbols->GetLineByOffsetWide(f->address, &(f->line), wBuf, MAX_PATH, 0, &ul64Displacement);
if (SUCCEEDED(hr)) {
f->fullPathName = wBuf;
} else {
f->fullPathName.clear();
f->line = 0;
}
}
// Determine the frames of the threads.
// Note: Current thread is switched as a side effect!
static bool getThreadFrames(CIDebugSymbols *debugSymbols,
CIDebugSystemObjects *debugSystemObjects,
CIDebugControl *debugControl,
Threads *threads, std::string *errorMessage)
{
enum { MaxFrames = 1 };
DEBUG_STACK_FRAME frames[MaxFrames];
const Threads::iterator end = threads->end();
for (Threads::iterator it = threads->begin(); it != end; ++it) {
if (!setCurrentThread(debugSystemObjects, it->id, errorMessage))
return false;
ULONG frameCount;
const HRESULT hr = debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount);
if (SUCCEEDED(hr))
getFrame(debugSymbols, frames[0], &(it->frame));
}
return true;
}
bool getFrame(unsigned n, StackFrame *f, std::string *errorMessage)
{
IInterfacePointer<CIDebugClient> client;
if (!client.create()) {
*errorMessage = "Cannot obtain client.";
return false;
}
IInterfacePointer<CIDebugSymbols> symbols(client.data());
IInterfacePointer<CIDebugControl> control(client.data());
if (!symbols || !control) {
*errorMessage = "Cannot obtain required objects.";
return false;
}
return getFrame(symbols.data(), control.data(), n, f, errorMessage);
}
bool getFrame(CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
unsigned n, StackFrame *f, std::string *errorMessage)
{
DEBUG_STACK_FRAME *frames = new DEBUG_STACK_FRAME[n + 1];
ULONG frameCount;
const HRESULT hr = debugControl->GetStackTrace(0, 0, 0, frames, n + 1, &frameCount);
if (FAILED(hr)) {
delete [] frames;
*errorMessage = msgDebugEngineComFailed("GetStackTrace", hr);
return false;
}
getFrame(debugSymbols, frames[n], f);
delete [] frames;
return true;
}
bool threadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
Threads* threads, ULONG *currentThreadId,
std::string *errorMessage)
{
threads->clear();
ULONG threadCount;
*currentThreadId = 0;
// Get count
HRESULT hr= debugSystemObjects->GetNumberThreads(&threadCount);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetNumberThreads", hr));
return false;
}
// Get index of current
if (!threadCount)
return true;
hr = debugSystemObjects->GetCurrentThreadId(currentThreadId);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetCurrentThreadId", hr));
return false;
}
// Get Identifiers
threads->reserve(threadCount);
ULONG *ids = new ULONG[threadCount];
ULONG *systemIds = new ULONG[threadCount];
hr = debugSystemObjects->GetThreadIdsByIndex(0, threadCount, ids, systemIds);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetThreadIdsByIndex", hr));
return false;
}
// Create entries
static WCHAR name[256];
for (ULONG i= 0; i < threadCount ; i++) {
const ULONG id = ids[i];
Thread thread(id, systemIds[i]);
// Thread name
ULONG bytesReceived = 0;
hr = debugAdvanced->GetSystemObjectInformation(DEBUG_SYSOBJINFO_THREAD_NAME_WIDE,
0, id, name,
sizeof(name), &bytesReceived);
if (SUCCEEDED(hr) && bytesReceived)
thread.name = name;
threads->push_back(thread);
}
delete [] ids;
delete [] systemIds;
// Get frames and at all events,
// restore current thread after switching frames.
const bool framesOk = getThreadFrames(debugSymbols,
debugSystemObjects,
debugControl,
threads, errorMessage);
const bool restoreOk =setCurrentThread(debugSystemObjects, *currentThreadId, errorMessage);
return framesOk && restoreOk;
}
/* Format as: \code
52^done,threads=[{id="1",target-id="Thread 4740.0xb30",
frame={level="0",addr="0x00403487",func="foo",
args=[{name="this",value="0x27fee4"}],file="mainwindow.cpp",fullname="c:\\qt\\projects\\gitguim\\app/mainwindow.cpp",line="298"},state="stopped"}],
current-thread-id="1"
*/
std::string gdbmiThreadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
std::string *errorMessage)
{
Threads threads;
ULONG currentThreadId;
if (!threadList(debugSystemObjects, debugSymbols, debugControl,
debugAdvanced, &threads, &currentThreadId, errorMessage))
return std::string();
std::ostringstream str;
str << "{threads=[";
const Threads::const_iterator cend = threads.end();
for (Threads::const_iterator it = threads.begin(); it != cend; ++it)
it->formatGDBMI(str);
str << "],current-thread-id=\"" << currentThreadId << "\"}";
return str.str();
}
Module::Module() : deferred(false), base(0), size(0)
{
}
Modules getModules(CIDebugSymbols *syms, std::string *errorMessage)
{
enum { BufSize = 1024 };
char nameBuf[BufSize];
char fileBuf[BufSize];
ULONG Loaded;
ULONG Unloaded;
HRESULT hr = syms->GetNumberModules(&Loaded, &Unloaded);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberModules", hr);
return Modules();
}
const ULONG count = Loaded + Unloaded;
Modules rc;
rc.reserve(count);
DEBUG_MODULE_PARAMETERS *parameters = new DEBUG_MODULE_PARAMETERS[count];
hr = syms->GetModuleParameters(count, NULL, 0, parameters);
if (FAILED(hr)) {
delete [] parameters;
*errorMessage = msgDebugEngineComFailed("GetModuleParameters", hr);
return Modules();
}
for (ULONG m = 0; m < count; m++) {
Module module;
module.base = parameters[m].Base;
module.size = parameters[m].Size;
module.deferred = parameters[m].Flags == DEBUG_SYMTYPE_DEFERRED;
hr = syms->GetModuleNames(m, 0, fileBuf, BufSize, NULL,
nameBuf, BufSize, NULL, NULL, NULL, NULL);
if (FAILED(hr))
break; // Fail silently should unloaded modules not work.
module.name = nameBuf;
module.image = fileBuf;
rc.push_back(module);
}
return rc;
}
std::string gdbmiModules(CIDebugSymbols *syms, bool humanReadable, std::string *errorMessage)
{
const Modules modules = getModules(syms, errorMessage);
if (modules.empty())
return std::string();
std::ostringstream str;
str << '[' << std::hex << std::showbase;
const Modules::size_type size = modules.size();
for (Modules::size_type m = 0; m < size; m++) {
const Module &module = modules.at(m);
if (m)
str << ',';
str << "{name=\"" << module.name
<< "\",image=\"" << gdbmiStringFormat(module.image)
<< "\",start=\"" << module.base << "\",end=\""
<< (module.base + module.size - 1) << '"';
if (module.deferred)
str << "{deferred=\"true\"";
str << '}';
if (humanReadable)
str << '\n';
}
str << ']';
return str.str();
}
// Description of a DEBUG_VALUE type field
const wchar_t *valueType(ULONG type)
{
switch (type) {
case DEBUG_VALUE_INT8:
return L"I8";
case DEBUG_VALUE_INT16:
return L"I16";
case DEBUG_VALUE_INT32:
return L"I32";
case DEBUG_VALUE_INT64:
return L"I64";
case DEBUG_VALUE_FLOAT32:
return L"F32";
case DEBUG_VALUE_FLOAT64:
return L"F64";
case DEBUG_VALUE_FLOAT80:
return L"F80";
case DEBUG_VALUE_FLOAT128:
return L"F128";
case DEBUG_VALUE_VECTOR64:
return L"V64";
case DEBUG_VALUE_VECTOR128:
return L"V128";
}
return L"";
}
// Format a 128bit vector register by adding digits in reverse order
void formatVectorRegister(std::ostream &str, const unsigned char *array, int size)
{
const char oldFill = str.fill('0');
str << "0x" << std::hex;
for (int i = size - 1; i >= 0; i--) {
str.width(2);
str << unsigned(array[i]);
}
str << std::dec;
str.fill(oldFill);
}
void formatDebugValue(std::ostream &str, const DEBUG_VALUE &dv, CIDebugControl *ctl)
{
const std::ios::fmtflags savedState = str.flags();
switch (dv.Type) {
// Do not use showbase to get the hex prefix as this omits it for '0'. Grmpf.
case DEBUG_VALUE_INT8:
str << std::hex << "0x" << unsigned(dv.I8);
break;
case DEBUG_VALUE_INT16:
str << std::hex << "0x" << dv.I16;
break;
case DEBUG_VALUE_INT32:
str << std::hex << "0x" << dv.I32;
break;
case DEBUG_VALUE_INT64:
str << std::hex << "0x" << dv.I64;
break;
case DEBUG_VALUE_FLOAT32:
str << dv.F32;
break;
case DEBUG_VALUE_FLOAT64:
str << dv.F64;
break;
case DEBUG_VALUE_FLOAT80:
case DEBUG_VALUE_FLOAT128: { // Convert to double
DEBUG_VALUE doubleValue;
if (ctl && SUCCEEDED(ctl->CoerceValue(const_cast<DEBUG_VALUE*>(&dv), DEBUG_VALUE_FLOAT64, &doubleValue)))
str << dv.F64;
}
break;
case DEBUG_VALUE_VECTOR64:
formatVectorRegister(str, dv.VI8, 8);
break;
case DEBUG_VALUE_VECTOR128:
formatVectorRegister(str, dv.VI8, 16);
break;
}
str.flags(savedState);
}
Register::Register() : subRegister(false), pseudoRegister(false)
{
value.Type = DEBUG_VALUE_INT32;
value.I32 = 0;
}
static inline std::wstring registerDescription(const DEBUG_REGISTER_DESCRIPTION &d)
{
std::wostringstream str;
str << valueType(d.Type);
if (d.Flags & DEBUG_REGISTER_SUB_REGISTER)
str << ", sub-register of " << d.SubregMaster;
return str.str();
}
Registers getRegisters(CIDebugRegisters *regs,
unsigned flags,
std::string *errorMessage)
{
enum { bufSize= 128 };
WCHAR buf[bufSize];
ULONG registerCount = 0;
HRESULT hr = regs->GetNumberRegisters(&registerCount);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberRegisters", hr);
return Registers();
}
ULONG pseudoRegisterCount = 0;
if (flags & IncludePseudoRegisters) {
hr = regs->GetNumberPseudoRegisters(&pseudoRegisterCount);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberPseudoRegisters", hr);
return Registers();
}
}
Registers rc;
rc.reserve(registerCount + pseudoRegisterCount);
// Standard registers
DEBUG_REGISTER_DESCRIPTION description;
DEBUG_VALUE value;
for (ULONG r = 0; r < registerCount; r++) {
hr = regs->GetDescriptionWide(r, buf, bufSize, NULL, &description);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetDescription", hr);
return Registers();
}
hr = regs->GetValue(r, &value);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetValue", hr);
return Registers();
}
const bool isSubRegister = (description.Flags & DEBUG_REGISTER_SUB_REGISTER);
if (!isSubRegister || (flags & IncludeSubRegisters)) {
Register reg;
reg.name = buf;
reg.description = registerDescription(description);
reg.subRegister = isSubRegister;
reg.value = value;
rc.push_back(reg);
}
}
// Pseudo
for (ULONG r = 0; r < pseudoRegisterCount; r++) {
ULONG type;
hr = regs->GetPseudoDescriptionWide(r, buf, bufSize, NULL, NULL, &type);
if (FAILED(hr))
continue; // Fails for some pseudo registers
hr = regs->GetValue(r, &value);
if (FAILED(hr))
continue; // Fails for some pseudo registers
Register reg;
reg.pseudoRegister = true;
reg.name = buf;
reg.description = valueType(type);
reg.value = value;
rc.push_back(reg);
}
return rc;
}
std::string gdbmiRegisters(CIDebugRegisters *regs,
CIDebugControl *control,
bool humanReadable,
unsigned flags,
std::string *errorMessage)
{
if (regs == 0 || control == 0) {
*errorMessage = "Required interfaces missing for registers dump.";
return std::string();
}
const Registers registers = getRegisters(regs, flags, errorMessage);
if (registers.empty())
return std::string();
std::ostringstream str;
str << '[';
if (humanReadable)
str << '\n';
const Registers::size_type size = registers.size();
for (Registers::size_type r = 0; r < size; r++) {
const Register &reg = registers.at(r);
if (r)
str << ',';
str << "{number=\"" << r << "\",name=\"" << gdbmiWStringFormat(reg.name) << '"';
if (!reg.description.empty())
str << ",description=\"" << gdbmiWStringFormat(reg.description) << '"';
if (reg.subRegister)
str << ",subregister=\"true\"";
if (reg.pseudoRegister)
str << ",pseudoregister=\"true\"";
str << ",value=\"";
formatDebugValue(str, reg.value, control);
str << "\"}";
if (humanReadable)
str << '\n';
}
str << ']';
if (humanReadable)
str << '\n';
return str.str();
}
std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length, std::string *errorMessage)
{
unsigned char *buffer = new unsigned char[length];
std::fill(buffer, buffer + length, 0);
ULONG received = 0;
const HRESULT hr = ds->ReadVirtual(address, buffer, length, &received);
if (FAILED(hr)) {
delete [] buffer;
std::ostringstream estr;
estr << "Cannot read " << length << " bytes from " << address << ": "
<< msgDebugEngineComFailed("ReadVirtual", hr);
*errorMessage = estr.str();
return std::string();
}
if (received < length) {
std::ostringstream estr;
estr << "Warning: Received only " << received << " bytes of " << length << " requested at " << address << '.';
*errorMessage = estr.str();
}
std::ostringstream str;
base64Encode(str, buffer, length);
delete [] buffer;
return str.str();
}
// Format stack as GDBMI
static StackFrames getStackTrace(CIDebugControl *debugControl,
CIDebugSymbols *debugSymbols,
unsigned maxFrames,
std::string *errorMessage)
{
if (!maxFrames)
return StackFrames();
DEBUG_STACK_FRAME *frames = new DEBUG_STACK_FRAME[maxFrames];
ULONG frameCount = 0;
const HRESULT hr = debugControl->GetStackTrace(0, 0, 0, frames, maxFrames, &frameCount);
if (FAILED(hr)) {
delete [] frames;
*errorMessage = msgDebugEngineComFailed("GetStackTrace", hr);
}
StackFrames rc(frameCount, StackFrame());
for (ULONG f = 0; f < frameCount; f++)
getFrame(debugSymbols, frames[f], &(rc[f]));
delete [] frames;
return rc;
}
std::string gdbmiStack(CIDebugControl *debugControl,
CIDebugSymbols *debugSymbols,
unsigned maxFrames,
bool humanReadable, std::string *errorMessage)
{
const StackFrames frames = getStackTrace(debugControl, debugSymbols,
maxFrames, errorMessage);
if (frames.empty() && maxFrames > 0)
return std::string();
std::ostringstream str;
str << '[';
const StackFrames::size_type size = frames.size();
for (StackFrames::size_type i = 0; i < size; i++) {
if (i)
str << ',';
frames.at(i).formatGDBMI(str, (int)i);
if (humanReadable)
str << '\n';
}
str << ']';
return str.str();
}