DBG+GUI: implement auto completion for exports in goto dialog
closes #1987
This commit is contained in:
parent
593b5f1752
commit
8a07bd2d7e
|
@ -383,6 +383,10 @@ static int SymAutoComplete(const char* Search, char** Buffer, int MaxSymbols)
|
|||
mods.push_back(info.base);
|
||||
});
|
||||
|
||||
std::unordered_set<std::string> visited;
|
||||
|
||||
static const bool caseSensitiveAutoComplete = settingboolget("Gui", "CaseSensitiveAutoComplete");
|
||||
|
||||
int count = 0;
|
||||
std::string prefix(Search);
|
||||
for(duint base : mods)
|
||||
|
@ -395,16 +399,38 @@ static int SymAutoComplete(const char* Search, char** Buffer, int MaxSymbols)
|
|||
if(!modInfo)
|
||||
continue;
|
||||
|
||||
auto addName = [Buffer, MaxSymbols, &visited, &count](const std::string & name)
|
||||
{
|
||||
if(visited.count(name))
|
||||
return true;
|
||||
visited.insert(name);
|
||||
Buffer[count] = (char*)BridgeAlloc(name.size() + 1);
|
||||
memcpy(Buffer[count], name.c_str(), name.size() + 1);
|
||||
return ++count < MaxSymbols;
|
||||
};
|
||||
|
||||
NameIndex::findByPrefix(modInfo->exportsByName, prefix, [modInfo, &addName](const NameIndex & index)
|
||||
{
|
||||
return addName(modInfo->exports[index.index].name);
|
||||
}, caseSensitiveAutoComplete);
|
||||
|
||||
if(count == MaxSymbols)
|
||||
break;
|
||||
|
||||
if(modInfo->symbols->isOpen())
|
||||
{
|
||||
modInfo->symbols->findSymbolsByPrefix(prefix, [Buffer, MaxSymbols, &count](const SymbolInfo & symInfo)
|
||||
modInfo->symbols->findSymbolsByPrefix(prefix, [&addName](const SymbolInfo & symInfo)
|
||||
{
|
||||
Buffer[count] = (char*)BridgeAlloc(symInfo.decoratedName.size() + 1);
|
||||
memcpy(Buffer[count], symInfo.decoratedName.c_str(), symInfo.decoratedName.size() + 1);
|
||||
return ++count < MaxSymbols;
|
||||
}, true); //TODO: support case insensitive in the GUI
|
||||
return addName(symInfo.decoratedName);
|
||||
}, caseSensitiveAutoComplete);
|
||||
}
|
||||
}
|
||||
|
||||
std::stable_sort(Buffer, Buffer + count, [](const char* a, const char* b)
|
||||
{
|
||||
return (caseSensitiveAutoComplete ? strcmp : StringUtils::hackicmp)(a, b) < 0;
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
|
@ -196,14 +196,12 @@ static void ReadExportDirectory(MODINFO & Info, ULONG_PTR FileMapVA)
|
|||
Info.exportsByRva.resize(Info.exports.size());
|
||||
for(size_t i = 0; i < Info.exports.size(); i++)
|
||||
{
|
||||
Info.exportsByName[i] = i;
|
||||
Info.exportsByName[i].index = i;
|
||||
Info.exportsByName[i].name = Info.exports[i].name.c_str(); //NOTE: DO NOT MODIFY name is any way!
|
||||
Info.exportsByRva[i] = i;
|
||||
}
|
||||
|
||||
std::sort(Info.exportsByName.begin(), Info.exportsByName.end(), [&Info](size_t a, size_t b)
|
||||
{
|
||||
return Info.exports.at(a).name < Info.exports.at(b).name;
|
||||
});
|
||||
std::sort(Info.exportsByName.begin(), Info.exportsByName.end());
|
||||
|
||||
std::sort(Info.exportsByRva.begin(), Info.exportsByRva.end(), [&Info](size_t a, size_t b)
|
||||
{
|
||||
|
|
|
@ -95,7 +95,7 @@ struct MODINFO
|
|||
|
||||
std::vector<MODEXPORT> exports;
|
||||
DWORD exportOrdinalBase = 0; //ordinal - 'exportOrdinalBase' = index in 'exports'
|
||||
std::vector<size_t> exportsByName; //index in 'exports', sorted by export name
|
||||
std::vector<NameIndex> exportsByName; //index in 'exports', sorted by export name
|
||||
std::vector<size_t> exportsByRva; //index in 'exports', sorted by rva
|
||||
|
||||
std::vector<String> importModules;
|
||||
|
|
|
@ -602,4 +602,17 @@ bool StringUtils::FromCompressedHex(const String & text, std::vector<unsigned ch
|
|||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int StringUtils::hackicmp(const char* s1, const char* s2)
|
||||
{
|
||||
unsigned char c1, c2;
|
||||
while((c1 = *s1++) == (c2 = *s2++))
|
||||
if(c1 == '\0')
|
||||
return 0;
|
||||
s1--, s2--;
|
||||
while((c1 = tolower(*s1++)) == (c2 = tolower(*s2++)))
|
||||
if(c1 == '\0')
|
||||
return 0;
|
||||
return c1 - c2;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
static String ToHex(const unsigned char* buffer, size_t size, bool reverse = false);
|
||||
static String ToCompressedHex(const unsigned char* buffer, size_t size);
|
||||
static bool FromCompressedHex(const String & text, std::vector<unsigned char> & data);
|
||||
static int hackicmp(const char* s1, const char* s2);
|
||||
|
||||
template<typename T>
|
||||
static String ToFloatingString(void* buffer)
|
||||
|
|
|
@ -1,6 +1,82 @@
|
|||
#include "symbolsourcebase.h"
|
||||
#include <algorithm>
|
||||
|
||||
//http://en.cppreference.com/w/cpp/algorithm/lower_bound
|
||||
template<class ForwardIt, class T, class Compare = std::less<>>
|
||||
static ForwardIt binary_find(ForwardIt first, ForwardIt last, const T & value, Compare comp = {})
|
||||
{
|
||||
// Note: BOTH type T and the type after ForwardIt is dereferenced
|
||||
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
|
||||
// This is stricter than lower_bound requirement (see above)
|
||||
|
||||
first = std::lower_bound(first, last, value, comp);
|
||||
return first != last && !comp(value, *first) ? first : last;
|
||||
}
|
||||
|
||||
bool NameIndex::findByPrefix(const std::vector<NameIndex> & byName, const std::string & prefix, const std::function<bool(const NameIndex &)> & cbFound, bool caseSensitive)
|
||||
{
|
||||
struct PrefixCmp
|
||||
{
|
||||
PrefixCmp(size_t n) : n(n) { }
|
||||
|
||||
bool operator()(const NameIndex & a, const NameIndex & b)
|
||||
{
|
||||
return cmp(a, b, false) < 0;
|
||||
}
|
||||
|
||||
int cmp(const NameIndex & a, const NameIndex & b, bool caseSensitive)
|
||||
{
|
||||
return (caseSensitive ? strncmp : _strnicmp)(a.name, b.name, n);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t n;
|
||||
} prefixCmp(prefix.size());
|
||||
|
||||
if(byName.empty())
|
||||
return false;
|
||||
|
||||
NameIndex find;
|
||||
find.name = prefix.c_str();
|
||||
auto found = binary_find(byName.begin(), byName.end(), find, prefixCmp);
|
||||
if(found == byName.end())
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
for(; found != byName.end() && prefixCmp.cmp(find, *found, false) == 0; ++found)
|
||||
{
|
||||
if(!caseSensitive || prefixCmp.cmp(find, *found, true) == 0)
|
||||
{
|
||||
result = true;
|
||||
if(!cbFound(*found))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NameIndex::findByName(const std::vector<NameIndex> & byName, const std::string & name, NameIndex & foundIndex, bool caseSensitive)
|
||||
{
|
||||
NameIndex find;
|
||||
find.name = name.c_str();
|
||||
auto found = binary_find(byName.begin(), byName.end(), find);
|
||||
if(found != byName.end())
|
||||
{
|
||||
do
|
||||
{
|
||||
if(find.cmp(*found, find, caseSensitive) == 0)
|
||||
{
|
||||
foundIndex = *found;
|
||||
return true;
|
||||
}
|
||||
++found;
|
||||
}
|
||||
while(found != byName.end() && find.cmp(find, *found, false) == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolSourceBase::mapSourceFilePdbToDisk(const std::string & pdb, const std::string & disk)
|
||||
{
|
||||
std::string pdblower = pdb, disklower = disk;
|
||||
|
@ -56,4 +132,4 @@ bool SymbolSourceBase::getSourceFilePdbToDisk(const std::string & pdb, std::stri
|
|||
return false;
|
||||
disk = found->second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <vector>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
struct SymbolInfoGui
|
||||
{
|
||||
|
@ -42,6 +43,25 @@ struct LineInfo
|
|||
String sourceFile;
|
||||
};
|
||||
|
||||
struct NameIndex
|
||||
{
|
||||
const char* name;
|
||||
size_t index;
|
||||
|
||||
bool operator<(const NameIndex & b) const
|
||||
{
|
||||
return cmp(*this, b, false) < 0;
|
||||
}
|
||||
|
||||
static int cmp(const NameIndex & a, const NameIndex & b, bool caseSensitive)
|
||||
{
|
||||
return (caseSensitive ? strcmp : StringUtils::hackicmp)(a.name, b.name);
|
||||
}
|
||||
|
||||
static bool findByPrefix(const std::vector<NameIndex> & byName, const std::string & prefix, const std::function<bool(const NameIndex &)> & cbFound, bool caseSensitive);
|
||||
static bool findByName(const std::vector<NameIndex> & byName, const std::string & name, NameIndex & foundIndex, bool caseSensitive);
|
||||
};
|
||||
|
||||
using CbEnumSymbol = std::function<bool(const SymbolInfo &)>;
|
||||
|
||||
class SymbolSourceBase
|
||||
|
|
|
@ -477,85 +477,29 @@ bool SymbolSourceDIA::findSourceLineInfo(const std::string & file, int line, Lin
|
|||
return true;
|
||||
}
|
||||
|
||||
//http://en.cppreference.com/w/cpp/algorithm/lower_bound
|
||||
template<class ForwardIt, class T, class Compare = std::less<>>
|
||||
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T & value, Compare comp = {})
|
||||
{
|
||||
// Note: BOTH type T and the type after ForwardIt is dereferenced
|
||||
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
|
||||
// This is stricter than lower_bound requirement (see above)
|
||||
|
||||
first = std::lower_bound(first, last, value, comp);
|
||||
return first != last && !comp(value, *first) ? first : last;
|
||||
}
|
||||
|
||||
bool SymbolSourceDIA::findSymbolByName(const std::string & name, SymbolInfo & symInfo, bool caseSensitive)
|
||||
{
|
||||
ScopedSpinLock lock(_lockSymbols);
|
||||
if(!_symbolsLoaded)
|
||||
return false;
|
||||
|
||||
NameIndex find;
|
||||
find.name = name.c_str();
|
||||
auto found = binary_find(_symNameMap.begin(), _symNameMap.end(), find);
|
||||
if(found != _symNameMap.end())
|
||||
{
|
||||
do
|
||||
{
|
||||
if(find.cmp(*found, find, caseSensitive) == 0)
|
||||
{
|
||||
symInfo = _symData.at(found->index);
|
||||
return true;
|
||||
}
|
||||
++found;
|
||||
}
|
||||
while(found != _symNameMap.end() && find.cmp(find, *found, false) == 0);
|
||||
}
|
||||
return false;
|
||||
NameIndex found;
|
||||
if(!NameIndex::findByName(_symNameMap, name, found, caseSensitive))
|
||||
return false;
|
||||
symInfo = _symData[found.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolSourceDIA::findSymbolsByPrefix(const std::string & prefix, const std::function<bool(const SymbolInfo &)> & cbSymbol, bool caseSensitive)
|
||||
{
|
||||
struct PrefixCmp
|
||||
{
|
||||
PrefixCmp(size_t n) : n(n) { }
|
||||
|
||||
bool operator()(const NameIndex & a, const NameIndex & b)
|
||||
{
|
||||
return cmp(a, b, false) < 0;
|
||||
}
|
||||
|
||||
int cmp(const NameIndex & a, const NameIndex & b, bool caseSensitive)
|
||||
{
|
||||
return (caseSensitive ? strncmp : _strnicmp)(a.name, b.name, n);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t n;
|
||||
} prefixCmp(prefix.size());
|
||||
|
||||
ScopedSpinLock lock(_lockSymbols);
|
||||
if(!_symbolsLoaded)
|
||||
return false;
|
||||
|
||||
NameIndex find;
|
||||
find.name = prefix.c_str();
|
||||
auto found = binary_find(_symNameMap.begin(), _symNameMap.end(), find, prefixCmp);
|
||||
if(found == _symNameMap.end())
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
for(; found != _symNameMap.end() && prefixCmp.cmp(find, *found, false) == 0; ++found)
|
||||
return NameIndex::findByPrefix(_symNameMap, prefix, [this, &cbSymbol](const NameIndex & index)
|
||||
{
|
||||
if(!caseSensitive || prefixCmp.cmp(find, *found, true) == 0)
|
||||
{
|
||||
result = true;
|
||||
if(!cbSymbol(_symData.at(found->index)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return cbSymbol(_symData[index.index]);
|
||||
}, caseSensitive);
|
||||
}
|
||||
|
||||
std::string SymbolSourceDIA::loadedSymbolPath() const
|
||||
|
|
|
@ -68,22 +68,6 @@ private: //symbols
|
|||
}
|
||||
};
|
||||
std::vector<AddrIndex> _symAddrMap; //rva -> data index (sorted on rva)
|
||||
|
||||
struct NameIndex
|
||||
{
|
||||
const char* name;
|
||||
size_t index;
|
||||
|
||||
bool operator<(const NameIndex & b) const
|
||||
{
|
||||
return cmp(*this, b, false) < 0;
|
||||
}
|
||||
|
||||
static int cmp(const NameIndex & a, const NameIndex & b, bool caseSensitive)
|
||||
{
|
||||
return (caseSensitive ? strcmp : hackicmp)(a.name, b.name);
|
||||
}
|
||||
};
|
||||
std::vector<NameIndex> _symNameMap; //name -> data index (sorted on name)
|
||||
//Symbol addresses to index in _symNames (TODO: refactor to std::vector)
|
||||
std::map<duint, size_t> _symAddrs;
|
||||
|
|
|
@ -29,6 +29,7 @@ GotoDialog::GotoDialog(QWidget* parent, bool allowInvalidExpression, bool allowI
|
|||
{
|
||||
return mCompletionText;
|
||||
}, completer));
|
||||
completer->setCaseSensitivity(Config()->getBool("Gui", "CaseSensitiveAutoComplete") ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
||||
if(!Config()->getBool("Gui", "DisableAutoComplete"))
|
||||
ui->editExpression->setCompleter(completer);
|
||||
validRangeStart = 0;
|
||||
|
|
|
@ -284,6 +284,7 @@ Configuration::Configuration() : QObject(), noMoreMsgbox(false)
|
|||
guiBool.insert("GraphZoomMode", false);
|
||||
guiBool.insert("ShowExitConfirmation", true);
|
||||
guiBool.insert("DisableAutoComplete", false);
|
||||
guiBool.insert("CaseSensitiveAutoComplete", false);
|
||||
//Named menu settings
|
||||
insertMenuBuilderBools(&guiBool, "CPUDisassembly", 50); //CPUDisassembly
|
||||
insertMenuBuilderBools(&guiBool, "CPUDump", 50); //CPUDump
|
||||
|
|
Loading…
Reference in New Issue