forked from qt-creator/qt-creator
Debugger[New CDB]: Fix disassembly.
Introduce GDBMI-based 'stack' extension command instead of the builtin 'k' as this does not print the correct instruction pointer.
This commit is contained in:
@@ -33,7 +33,6 @@
|
|||||||
#include "gdbmihelpers.h"
|
#include "gdbmihelpers.h"
|
||||||
|
|
||||||
static const char eventContextC[] = "event";
|
static const char eventContextC[] = "event";
|
||||||
static const char moduleContextC[] = "module";
|
|
||||||
|
|
||||||
// Special exception codes (see dbgwinutils.cpp).
|
// Special exception codes (see dbgwinutils.cpp).
|
||||||
enum { winExceptionCppException = 0xe06d7363,
|
enum { winExceptionCppException = 0xe06d7363,
|
||||||
@@ -239,9 +238,6 @@ STDMETHODIMP EventCallback::LoadModule(
|
|||||||
__in ULONG TimeDateStamp
|
__in ULONG TimeDateStamp
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ExtensionContext::instance().report('E', 0, moduleContextC, "L:%s:%s:0x%llx:0x%llx\n",
|
|
||||||
ModuleName, ImageName, BaseOffset, ModuleSize);
|
|
||||||
|
|
||||||
return m_wrapped ? m_wrapped->LoadModule(ImageFileHandle, BaseOffset,
|
return m_wrapped ? m_wrapped->LoadModule(ImageFileHandle, BaseOffset,
|
||||||
ModuleSize, ModuleName, ImageName,
|
ModuleSize, ModuleName, ImageName,
|
||||||
CheckSum, TimeDateStamp) : S_OK;
|
CheckSum, TimeDateStamp) : S_OK;
|
||||||
@@ -253,9 +249,6 @@ STDMETHODIMP EventCallback::UnloadModule(
|
|||||||
__in ULONG64 BaseOffset
|
__in ULONG64 BaseOffset
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ExtensionContext::instance().report('U', 0, moduleContextC, "U:%s\n",
|
|
||||||
ImageBaseName);
|
|
||||||
|
|
||||||
return m_wrapped ? m_wrapped->UnloadModule(ImageBaseName, BaseOffset) : S_OK;
|
return m_wrapped ? m_wrapped->UnloadModule(ImageBaseName, BaseOffset) : S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,18 @@ void StackFrame::formatGDBMI(std::ostream &str, unsigned level) const
|
|||||||
{
|
{
|
||||||
str << "frame={level=\"" << level << "\",addr=\"0x"
|
str << "frame={level=\"" << level << "\",addr=\"0x"
|
||||||
<< std::hex << address << std::dec << '"';
|
<< std::hex << address << std::dec << '"';
|
||||||
if (!function.empty())
|
if (!function.empty()) {
|
||||||
str << ",func=\"" << gdbmiWStringFormat(function) << '"';
|
// 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'
|
if (!fullPathName.empty()) { // Creator/gdbmi expects 'clean paths'
|
||||||
std::wstring cleanPath = fullPathName;
|
std::wstring cleanPath = fullPathName;
|
||||||
replace(cleanPath, L'\\', L'/');
|
replace(cleanPath, L'\\', L'/');
|
||||||
@@ -559,3 +569,50 @@ std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length,
|
|||||||
delete [] buffer;
|
delete [] buffer;
|
||||||
return str.str();
|
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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ struct StackFrame
|
|||||||
ULONG line;
|
ULONG line;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::vector<StackFrame> StackFrames;
|
||||||
|
|
||||||
bool getFrame(unsigned n, StackFrame *f, std::string *errorMessage);
|
bool getFrame(unsigned n, StackFrame *f, std::string *errorMessage);
|
||||||
bool getFrame(CIDebugSymbols *debugSymbols, CIDebugControl *debugControl,
|
bool getFrame(CIDebugSymbols *debugSymbols, CIDebugControl *debugControl,
|
||||||
unsigned n, StackFrame *f, std::string *errorMessage);
|
unsigned n, StackFrame *f, std::string *errorMessage);
|
||||||
@@ -145,4 +147,12 @@ std::string gdbmiRegisters(CIDebugRegisters *regs,
|
|||||||
|
|
||||||
std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length, std::string *errorMessage);
|
std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length, std::string *errorMessage);
|
||||||
|
|
||||||
|
// Stack helpers
|
||||||
|
StackFrames getStackTrace(CIDebugControl *debugControl, CIDebugSymbols *debugSymbols,
|
||||||
|
unsigned maxFrames, std::string *errorMessage);
|
||||||
|
|
||||||
|
std::string gdbmiStack(CIDebugControl *debugControl, CIDebugSymbols *debugSymbols,
|
||||||
|
unsigned maxFrames, bool humanReadable,
|
||||||
|
std::string *errorMessage);
|
||||||
|
|
||||||
#endif // THREADLIST_H
|
#endif // THREADLIST_H
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ idle
|
|||||||
help
|
help
|
||||||
memory
|
memory
|
||||||
shutdownex
|
shutdownex
|
||||||
|
stack
|
||||||
KnownStructOutput
|
KnownStructOutput
|
||||||
|
|||||||
@@ -351,6 +351,37 @@ extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extension command 'stack'
|
||||||
|
// Report stack correctly as 'k' does not list instruction pointer
|
||||||
|
// correctly.
|
||||||
|
extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
|
||||||
|
{
|
||||||
|
ExtensionCommandContext exc(Client);
|
||||||
|
std::string errorMessage;
|
||||||
|
|
||||||
|
int token;
|
||||||
|
bool humanReadable = false;
|
||||||
|
unsigned maxFrames = 1000;
|
||||||
|
|
||||||
|
StringList tokens = commandTokens<StringList>(argsIn, &token);
|
||||||
|
if (!tokens.empty() && tokens.front() == "-h") {
|
||||||
|
humanReadable = true;
|
||||||
|
tokens.pop_front();
|
||||||
|
}
|
||||||
|
if (!tokens.empty())
|
||||||
|
integerFromString(tokens.front(), &maxFrames);
|
||||||
|
|
||||||
|
const std::string stack = gdbmiStack(exc.control(), exc.symbols(),
|
||||||
|
maxFrames, humanReadable, &errorMessage);
|
||||||
|
|
||||||
|
if (stack.empty()) {
|
||||||
|
ExtensionContext::instance().report('N', token, "stack", errorMessage.c_str());
|
||||||
|
} else {
|
||||||
|
ExtensionContext::instance().report('R', token, "stack", stack.c_str());
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
// Extension command 'shutdownex' (shutdown is reserved):
|
// Extension command 'shutdownex' (shutdown is reserved):
|
||||||
// Unhook the output callbacks. This is normally done by the session
|
// Unhook the output callbacks. This is normally done by the session
|
||||||
// inaccessible notification, however, this does not work for remote-controlled sessions.
|
// inaccessible notification, however, this does not work for remote-controlled sessions.
|
||||||
|
|||||||
@@ -308,8 +308,22 @@ CdbEngine::~CdbEngine()
|
|||||||
|
|
||||||
void CdbEngine::operateByInstructionTriggered(bool operateByInstruction)
|
void CdbEngine::operateByInstructionTriggered(bool operateByInstruction)
|
||||||
{
|
{
|
||||||
// To be set next time session becomes accessible
|
if (state() == InferiorStopOk) {
|
||||||
m_operateByInstructionPending = operateByInstruction;
|
syncOperateByInstruction(operateByInstruction);
|
||||||
|
} else {
|
||||||
|
// To be set next time session becomes accessible
|
||||||
|
m_operateByInstructionPending = operateByInstruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CdbEngine::syncOperateByInstruction(bool operateByInstruction)
|
||||||
|
{
|
||||||
|
if (m_operateByInstruction == operateByInstruction)
|
||||||
|
return;
|
||||||
|
QTC_ASSERT(m_accessible, return; )
|
||||||
|
m_operateByInstruction = operateByInstruction;
|
||||||
|
postCommand(m_operateByInstruction ? QByteArray("l-t") : QByteArray("l+t"), 0);
|
||||||
|
postCommand(m_operateByInstruction ? QByteArray("l-s") : QByteArray("l+s"), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
|
void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
|
||||||
@@ -1197,11 +1211,7 @@ void CdbEngine::handleSessionIdle(const QByteArray &message)
|
|||||||
stateName(state()), m_specialStopMode);
|
stateName(state()), m_specialStopMode);
|
||||||
|
|
||||||
// Switch source level debugging
|
// Switch source level debugging
|
||||||
if (m_operateByInstructionPending != m_operateByInstruction) {
|
syncOperateByInstruction(m_operateByInstructionPending);
|
||||||
m_operateByInstruction = m_operateByInstructionPending;
|
|
||||||
postCommand(m_operateByInstruction ? QByteArray("l-t") : QByteArray("l+t"), 0);
|
|
||||||
postCommand(m_operateByInstruction ? QByteArray("l-s") : QByteArray("l+s"), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SpecialStopMode specialStopMode = m_specialStopMode;
|
const SpecialStopMode specialStopMode = m_specialStopMode;
|
||||||
m_specialStopMode = NoSpecialStop;
|
m_specialStopMode = NoSpecialStop;
|
||||||
@@ -1704,21 +1714,58 @@ QString CdbEngine::normalizeFileName(const QString &f)
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CdbEngine::handleStackTrace(const CdbBuiltinCommandPtr &command)
|
// Parse frame from GDBMI. Duplicate of the gdb code, but that
|
||||||
|
// has more processing.
|
||||||
|
static StackFrames parseFrames(const QByteArray &data)
|
||||||
{
|
{
|
||||||
StackFrames frames;
|
GdbMi gdbmi;
|
||||||
const int current = parseCdbStackTrace(command->reply, &frames);
|
gdbmi.fromString(data);
|
||||||
if (debug)
|
if (!gdbmi.isValid())
|
||||||
qDebug("handleStackTrace %d of %d", current, frames.size());
|
return StackFrames();
|
||||||
const StackFrames::iterator end = frames.end();
|
|
||||||
for (StackFrames::iterator it = frames.begin(); it != end; ++it) {
|
|
||||||
if (!it->file.isEmpty())
|
|
||||||
it->file = QDir::cleanPath(normalizeFileName(it->file));
|
|
||||||
}
|
|
||||||
|
|
||||||
stackHandler()->setFrames(frames);
|
StackFrames rc;
|
||||||
activateFrame(current);
|
const int count = gdbmi.childCount();
|
||||||
postCommandSequence(command->commandSequence);
|
rc.reserve(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const GdbMi &frameMi = gdbmi.childAt(i);
|
||||||
|
StackFrame frame;
|
||||||
|
frame.level = i;
|
||||||
|
const GdbMi fullName = frameMi.findChild("fullname");
|
||||||
|
if (fullName.isValid()) {
|
||||||
|
frame.file = QFile::decodeName(fullName.data());
|
||||||
|
frame.line = frameMi.findChild("line").data().toInt();
|
||||||
|
}
|
||||||
|
frame.function = QLatin1String(frameMi.findChild("func").data());
|
||||||
|
frame.from = QLatin1String(frameMi.findChild("from").data());
|
||||||
|
frame.address = frameMi.findChild("addr").data().toULongLong(0, 16);
|
||||||
|
rc.push_back(frame);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CdbEngine::handleStackTrace(const CdbExtensionCommandPtr &command)
|
||||||
|
{
|
||||||
|
// Parse frames, find current.
|
||||||
|
if (command->success) {
|
||||||
|
int current = -1;
|
||||||
|
StackFrames frames = parseFrames(command->reply);
|
||||||
|
const int count = frames.size();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (!frames.at(i).file.isEmpty()) {
|
||||||
|
frames[i].file = QDir::cleanPath(normalizeFileName(frames.at(i).file));
|
||||||
|
if (current == -1)
|
||||||
|
current = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count && current == -1) // No usable frame, use assembly.
|
||||||
|
current = 0;
|
||||||
|
// Set
|
||||||
|
stackHandler()->setFrames(frames);
|
||||||
|
activateFrame(current);
|
||||||
|
postCommandSequence(command->commandSequence);
|
||||||
|
} else {
|
||||||
|
showMessage(command->errorMessage, LogError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CdbEngine::dummyHandler(const CdbBuiltinCommandPtr &command)
|
void CdbEngine::dummyHandler(const CdbBuiltinCommandPtr &command)
|
||||||
@@ -1739,7 +1786,7 @@ void CdbEngine::postCommandSequence(unsigned mask)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mask & CommandListStack) {
|
if (mask & CommandListStack) {
|
||||||
postBuiltinCommand("k", 0, &CdbEngine::handleStackTrace, mask & ~CommandListStack);
|
postExtensionCommand("stack", QByteArray(), 0, &CdbEngine::handleStackTrace, mask & ~CommandListStack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mask & CommandListRegisters) {
|
if (mask & CommandListRegisters) {
|
||||||
|
|||||||
@@ -152,10 +152,11 @@ private:
|
|||||||
inline void parseOutputLine(QByteArray line);
|
inline void parseOutputLine(QByteArray line);
|
||||||
inline bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; }
|
inline bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; }
|
||||||
bool canInterruptInferior() const;
|
bool canInterruptInferior() const;
|
||||||
|
void syncOperateByInstruction(bool operateByInstruction);
|
||||||
|
|
||||||
// Builtin commands
|
// Builtin commands
|
||||||
void dummyHandler(const CdbBuiltinCommandPtr &);
|
void dummyHandler(const CdbBuiltinCommandPtr &);
|
||||||
void handleStackTrace(const CdbBuiltinCommandPtr &);
|
void handleStackTrace(const CdbExtensionCommandPtr &);
|
||||||
void handleRegisters(const CdbBuiltinCommandPtr &);
|
void handleRegisters(const CdbBuiltinCommandPtr &);
|
||||||
void handleDisassembler(const CdbBuiltinCommandPtr &);
|
void handleDisassembler(const CdbBuiltinCommandPtr &);
|
||||||
void handleJumpToLineAddressResolution(const CdbBuiltinCommandPtr &);
|
void handleJumpToLineAddressResolution(const CdbBuiltinCommandPtr &);
|
||||||
|
|||||||
@@ -154,75 +154,6 @@ QVariant cdbIntegerValue(const QByteArray &t)
|
|||||||
return converted;
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse: 64bit:
|
|
||||||
\code
|
|
||||||
Child-SP RetAddr Call Site
|
|
||||||
00000000`0012a290 00000000`70deb844 QtCored4!QString::QString+0x18 [c:\qt\src\corelib\tools\qstring.h @ 729]
|
|
||||||
\endcode 32bit:
|
|
||||||
\code
|
|
||||||
ChildEBP RetAddr
|
|
||||||
0012cc68 6714d114 QtCored4!QString::QString+0xf [d:\dev\qt4.7-vs8\qt\src\corelib\tools\qstring.h @ 729]
|
|
||||||
\endcode */
|
|
||||||
|
|
||||||
static inline bool isHexDigit(char c)
|
|
||||||
{
|
|
||||||
return std::isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool parseStackFrame(QByteArray line, Debugger::Internal::StackFrame *frame)
|
|
||||||
{
|
|
||||||
frame->clear();
|
|
||||||
if (line.isEmpty() || line.startsWith("Child") || !isHexDigit(line.at(0)))
|
|
||||||
return false;
|
|
||||||
if (line.endsWith(']')) {
|
|
||||||
const int sourceFilePos = line.lastIndexOf('[');
|
|
||||||
const int sepPos = line.lastIndexOf(" @ ");
|
|
||||||
if (sourceFilePos != -1 && sepPos != -1) {
|
|
||||||
const QString fileName = QString::fromLocal8Bit(line.mid(sourceFilePos + 1, sepPos - sourceFilePos - 1));
|
|
||||||
frame->file = QDir::cleanPath(fileName);
|
|
||||||
frame->line = line.mid(sepPos + 3, line.size() - sepPos - 4).toInt();
|
|
||||||
line.truncate(sourceFilePos - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Split address tokens
|
|
||||||
const int retAddrPos = line.indexOf(' ');
|
|
||||||
const int symbolPos = retAddrPos != -1 ? line.indexOf(' ', retAddrPos + 1) : -1;
|
|
||||||
if (symbolPos == -1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Remove offset off symbol
|
|
||||||
const int offsetPos = line.lastIndexOf("+0x");
|
|
||||||
if (offsetPos != -1)
|
|
||||||
line.truncate(offsetPos);
|
|
||||||
|
|
||||||
frame->address = cdbIntegerValue(line.mid(0, retAddrPos)).toULongLong();
|
|
||||||
// Module!foo
|
|
||||||
frame->function = QString::fromAscii(line.mid(symbolPos));
|
|
||||||
const int moduleSep = frame->function.indexOf(QLatin1Char('!'));
|
|
||||||
if (moduleSep != -1) {
|
|
||||||
frame->from = frame->function.left(moduleSep);
|
|
||||||
frame->function.remove(0, moduleSep + 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int parseCdbStackTrace(const QList<QByteArray> &in, QList<Debugger::Internal::StackFrame> *frames)
|
|
||||||
{
|
|
||||||
frames->clear();
|
|
||||||
Debugger::Internal::StackFrame frame;
|
|
||||||
frames->reserve(in.size());
|
|
||||||
int level = 0;
|
|
||||||
int current = -1;
|
|
||||||
foreach(const QByteArray &line, in)
|
|
||||||
if (parseStackFrame(line, &frame)) {
|
|
||||||
frame.level = level++;
|
|
||||||
if (current == -1 && frame.isUsable())
|
|
||||||
current = frames->size();
|
|
||||||
frames->push_back(frame);
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* \code
|
/* \code
|
||||||
0:002> ~ [Debugger-Id] Id: <hex pid> <hex tid> Suspends count thread environment block add state name
|
0:002> ~ [Debugger-Id] Id: <hex pid> <hex tid> Suspends count thread environment block add state name
|
||||||
0 Id: 133c.1374 Suspend: 1 Teb: 000007ff`fffdd000 Unfrozen
|
0 Id: 133c.1374 Suspend: 1 Teb: 000007ff`fffdd000 Unfrozen
|
||||||
|
|||||||
@@ -63,9 +63,6 @@ QByteArray fixCdbIntegerValue(QByteArray t, bool stripLeadingZeros = false, int
|
|||||||
// Convert a CDB integer value into quint64 or int64
|
// Convert a CDB integer value into quint64 or int64
|
||||||
QVariant cdbIntegerValue(const QByteArray &t);
|
QVariant cdbIntegerValue(const QByteArray &t);
|
||||||
|
|
||||||
// Parse stack frames and return current
|
|
||||||
int parseCdbStackTrace(const QList<QByteArray> &in, QList<Debugger::Internal::StackFrame> *frames);
|
|
||||||
|
|
||||||
QString debugByteArray(const QByteArray &a);
|
QString debugByteArray(const QByteArray &a);
|
||||||
QString StringFromBase64EncodedUtf16(const QByteArray &a);
|
QString StringFromBase64EncodedUtf16(const QByteArray &a);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user