mirror of
https://github.com/rizinorg/cutter.git
synced 2025-01-18 18:38:51 +00:00
Convert commands(agJ
, anj
, pi
) to rizin APIs (#3005)
This commit is contained in:
parent
c263d4bd1b
commit
a5b0dd3ed2
@ -201,7 +201,6 @@ void Configuration::resetAll()
|
||||
{
|
||||
// Don't reset all rizin vars, that currently breaks a bunch of stuff.
|
||||
// settingsFile.remove()+loadInitials() should reset all settings configurable using Cutter GUI.
|
||||
// Core()->cmdRaw("e-");
|
||||
|
||||
Core()->setSettings();
|
||||
// Delete the file so no extra configuration is in it.
|
||||
|
@ -19,7 +19,6 @@
|
||||
* {
|
||||
* TempConfig tempConfig;
|
||||
* tempConfig.set("asm.arch", "x86").set("asm.comments", false);
|
||||
* return Core()->cmdRaw("pd");
|
||||
* // config automatically restored at the end of scope
|
||||
* }
|
||||
* \endcode
|
||||
|
@ -777,34 +777,32 @@ void CutterCore::delFlag(const QString &name)
|
||||
emit flagsChanged();
|
||||
}
|
||||
|
||||
PRzAnalysisBytes CutterCore::getRzAnalysisBytesSingle(RVA addr)
|
||||
{
|
||||
CORE_LOCK();
|
||||
ut8 buf[128];
|
||||
rz_io_read_at(core->io, addr, buf, sizeof(buf));
|
||||
std::unique_ptr<RzPVector, decltype(rz_pvector_free) *> vec {
|
||||
returnAtSeek<RzPVector *>(
|
||||
[&]() { return rz_core_analysis_bytes(core, buf, sizeof(buf), 1); }, addr),
|
||||
rz_pvector_free
|
||||
};
|
||||
auto ab = vec && rz_pvector_len(vec.get()) > 0
|
||||
? reinterpret_cast<RzAnalysisBytes *>(rz_pvector_pop_front(vec.get()))
|
||||
: nullptr;
|
||||
return { ab, rz_analysis_bytes_free };
|
||||
}
|
||||
|
||||
QString CutterCore::getInstructionBytes(RVA addr)
|
||||
{
|
||||
auto ret = (char *)Core()->returnAtSeek(
|
||||
[&]() {
|
||||
CORE_LOCK();
|
||||
RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1);
|
||||
auto *ab = static_cast<RzAnalysisBytes *>(rz_pvector_head(vec));
|
||||
char *str = strdup(ab->bytes);
|
||||
rz_pvector_free(vec);
|
||||
return str;
|
||||
},
|
||||
addr);
|
||||
return fromOwnedCharPtr(ret);
|
||||
auto ab = getRzAnalysisBytesSingle(addr);
|
||||
return ab ? ab->bytes : "";
|
||||
}
|
||||
|
||||
QString CutterCore::getInstructionOpcode(RVA addr)
|
||||
{
|
||||
auto ret = (char *)Core()->returnAtSeek(
|
||||
[&]() {
|
||||
CORE_LOCK();
|
||||
RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1);
|
||||
auto *ab = static_cast<RzAnalysisBytes *>(rz_pvector_head(vec));
|
||||
char *str = strdup(ab->opcode);
|
||||
rz_pvector_free(vec);
|
||||
return str;
|
||||
},
|
||||
addr);
|
||||
return fromOwnedCharPtr(ret);
|
||||
auto ab = getRzAnalysisBytesSingle(addr);
|
||||
return ab ? ab->opcode : "";
|
||||
}
|
||||
|
||||
void CutterCore::editInstruction(RVA addr, const QString &inst, bool fillWithNops)
|
||||
@ -910,7 +908,7 @@ QString CutterCore::getString(RVA addr, uint64_t len, RzStrEnc encoding, bool es
|
||||
opt.length = len;
|
||||
opt.encoding = encoding;
|
||||
opt.escape_nl = escape_nl;
|
||||
char *s = (char *)returnAtSeek([&]() { return rz_str_stringify_raw_buffer(&opt, NULL); }, addr);
|
||||
char *s = returnAtSeek<char *>([&]() { return rz_str_stringify_raw_buffer(&opt, NULL); }, addr);
|
||||
return fromOwnedCharPtr(s);
|
||||
}
|
||||
|
||||
@ -1086,11 +1084,11 @@ RVA CutterCore::prevOpAddr(RVA startAddr, int count)
|
||||
RVA CutterCore::nextOpAddr(RVA startAddr, int count)
|
||||
{
|
||||
CORE_LOCK();
|
||||
auto vec = reinterpret_cast<RzPVector *>(returnAtSeek(
|
||||
auto vec = returnAtSeek<RzPVector *>(
|
||||
[&]() {
|
||||
return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, count + 1);
|
||||
},
|
||||
startAddr));
|
||||
startAddr);
|
||||
RVA addr = startAddr + 1;
|
||||
if (!vec) {
|
||||
return addr;
|
||||
@ -1326,7 +1324,8 @@ QString CutterCore::disassemble(const QByteArray &data)
|
||||
|
||||
QString CutterCore::disassembleSingleInstruction(RVA addr)
|
||||
{
|
||||
return cmdRawAt("pi 1", addr).simplified();
|
||||
auto ab = getRzAnalysisBytesSingle(addr);
|
||||
return QString(ab->disasm).simplified();
|
||||
}
|
||||
|
||||
RzAnalysisFunction *CutterCore::functionIn(ut64 addr)
|
||||
@ -1430,19 +1429,8 @@ void CutterCore::createFunctionAt(RVA addr, QString name)
|
||||
|
||||
RVA CutterCore::getOffsetJump(RVA addr)
|
||||
{
|
||||
auto rva = (RVA *)Core()->returnAtSeek(
|
||||
[&]() {
|
||||
CORE_LOCK();
|
||||
RzPVector *vec = rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1);
|
||||
auto *ab = static_cast<RzAnalysisBytes *>(rz_pvector_head(vec));
|
||||
RVA *rva = new RVA(ab->op->jump);
|
||||
rz_pvector_free(vec);
|
||||
return rva;
|
||||
},
|
||||
addr);
|
||||
RVA ret = *rva;
|
||||
delete rva;
|
||||
return ret;
|
||||
auto ab = getRzAnalysisBytesSingle(addr);
|
||||
return ab && ab->op ? ab->op->jump : RVA_INVALID;
|
||||
}
|
||||
|
||||
QList<Decompiler *> CutterCore::getDecompilers()
|
||||
|
@ -32,6 +32,7 @@ class RizinTaskDialog;
|
||||
#include "common/Helpers.h"
|
||||
|
||||
#include <rz_project.h>
|
||||
#include <memory>
|
||||
|
||||
#define Core() (CutterCore::instance())
|
||||
|
||||
@ -60,6 +61,8 @@ struct CUTTER_EXPORT RegisterRef
|
||||
QString name;
|
||||
};
|
||||
|
||||
using PRzAnalysisBytes = std::unique_ptr<RzAnalysisBytes, decltype(rz_analysis_bytes_free) *>;
|
||||
|
||||
class CUTTER_EXPORT CutterCore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -153,7 +156,7 @@ public:
|
||||
return cmdRawAt(str.toUtf8().constData(), address);
|
||||
}
|
||||
|
||||
void applyAtSeek(std::function<void()> fn, RVA address)
|
||||
void applyAtSeek(const std::function<void()> &fn, RVA address)
|
||||
{
|
||||
RVA oldOffset = getOffset();
|
||||
seekSilent(address);
|
||||
@ -161,11 +164,12 @@ public:
|
||||
seekSilent(oldOffset);
|
||||
}
|
||||
|
||||
void *returnAtSeek(std::function<void *()> fn, RVA address)
|
||||
template<typename T>
|
||||
T returnAtSeek(const std::function<T()> &fn, RVA address)
|
||||
{
|
||||
RVA oldOffset = getOffset();
|
||||
seekSilent(address);
|
||||
void *ret = fn();
|
||||
T ret = fn();
|
||||
seekSilent(oldOffset);
|
||||
return ret;
|
||||
}
|
||||
@ -273,6 +277,7 @@ public:
|
||||
void triggerFlagsChanged();
|
||||
|
||||
/* Edition functions */
|
||||
PRzAnalysisBytes getRzAnalysisBytesSingle(RVA addr);
|
||||
QString getInstructionBytes(RVA addr);
|
||||
QString getInstructionOpcode(RVA addr);
|
||||
void editInstruction(RVA addr, const QString &inst, bool fillWithNops = false);
|
||||
|
@ -313,34 +313,34 @@ void DisassemblyContextMenu::addDebugMenu()
|
||||
|
||||
QVector<DisassemblyContextMenu::ThingUsedHere> DisassemblyContextMenu::getThingUsedHere(RVA offset)
|
||||
{
|
||||
QVector<ThingUsedHere> result;
|
||||
const CutterJson array = Core()->cmdj("anj @ " + QString::number(offset));
|
||||
result.reserve(array.size());
|
||||
for (const auto &thing : array) {
|
||||
auto obj = thing;
|
||||
RVA offset = obj["offset"].toRVA();
|
||||
QString name;
|
||||
|
||||
// If real names display is enabled, show flag's real name instead of full flag name
|
||||
if (Config()->getConfigBool("asm.flags.real") && obj["realname"].valid()) {
|
||||
name = obj["realname"].toString();
|
||||
} else {
|
||||
name = obj["name"].toString();
|
||||
}
|
||||
|
||||
QString typeString = obj["type"].toString();
|
||||
ThingUsedHere::Type type = ThingUsedHere::Type::Address;
|
||||
if (typeString == "var") {
|
||||
type = ThingUsedHere::Type::Var;
|
||||
} else if (typeString == "flag") {
|
||||
type = ThingUsedHere::Type::Flag;
|
||||
} else if (typeString == "function") {
|
||||
type = ThingUsedHere::Type::Function;
|
||||
} else if (typeString == "address") {
|
||||
type = ThingUsedHere::Type::Address;
|
||||
}
|
||||
result.push_back(ThingUsedHere { name, offset, type });
|
||||
RzCoreLocked core(Core());
|
||||
auto p = std::unique_ptr<RzCoreAnalysisName, decltype(rz_core_analysis_name_free) *> {
|
||||
rz_core_analysis_name(core, offset), rz_core_analysis_name_free
|
||||
};
|
||||
if (!p) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QVector<ThingUsedHere> result;
|
||||
ThingUsedHere th;
|
||||
th.offset = p->offset;
|
||||
th.name = Config()->getConfigBool("asm.flags.real") && p->realname ? p->realname : p->name;
|
||||
switch (p->type) {
|
||||
case RZ_CORE_ANALYSIS_NAME_TYPE_FLAG:
|
||||
th.type = ThingUsedHere::Type::Flag;
|
||||
break;
|
||||
case RZ_CORE_ANALYSIS_NAME_TYPE_FUNCTION:
|
||||
th.type = ThingUsedHere::Type::Function;
|
||||
break;
|
||||
case RZ_CORE_ANALYSIS_NAME_TYPE_VAR:
|
||||
th.type = ThingUsedHere::Type::Var;
|
||||
break;
|
||||
case RZ_CORE_ANALYSIS_NAME_TYPE_ADDRESS:
|
||||
default:
|
||||
th.type = ThingUsedHere::Type::Address;
|
||||
break;
|
||||
}
|
||||
result.push_back(th);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -482,13 +482,7 @@ void DisassemblyContextMenu::setupRenaming()
|
||||
void DisassemblyContextMenu::aboutToShowSlot()
|
||||
{
|
||||
// check if set immediate base menu makes sense
|
||||
RzPVector *vec = (RzPVector *)Core()->returnAtSeek(
|
||||
[&]() {
|
||||
RzCoreLocked core(Core());
|
||||
return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1);
|
||||
},
|
||||
offset);
|
||||
auto *ab = static_cast<RzAnalysisBytes *>(rz_pvector_head(vec));
|
||||
auto ab = Core()->getRzAnalysisBytesSingle(offset);
|
||||
|
||||
bool immBase = ab && ab->op && (ab->op->val || ab->op->ptr);
|
||||
setBaseMenu->menuAction()->setVisible(immBase);
|
||||
@ -514,7 +508,6 @@ void DisassemblyContextMenu::aboutToShowSlot()
|
||||
}
|
||||
}
|
||||
}
|
||||
rz_pvector_free(vec);
|
||||
|
||||
if (memBaseReg.isEmpty()) {
|
||||
// hide structure offset menu
|
||||
@ -727,22 +720,13 @@ void DisassemblyContextMenu::on_actionNopInstruction_triggered()
|
||||
void DisassemblyContextMenu::showReverseJmpQuery()
|
||||
{
|
||||
actionJmpReverse.setVisible(false);
|
||||
RzCoreLocked core(Core());
|
||||
auto vec = reinterpret_cast<RzPVector *>(Core()->returnAtSeek(
|
||||
[&]() { return rz_core_analysis_bytes(core, core->block, (int)core->blocksize, 1); },
|
||||
offset));
|
||||
if (!vec) {
|
||||
return;
|
||||
}
|
||||
auto ab = reinterpret_cast<RzAnalysisBytes *>(rz_pvector_head(vec));
|
||||
auto ab = Core()->getRzAnalysisBytesSingle(offset);
|
||||
if (!(ab && ab->op)) {
|
||||
rz_pvector_free(vec);
|
||||
return;
|
||||
}
|
||||
if (ab->op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
|
||||
actionJmpReverse.setVisible(true);
|
||||
}
|
||||
rz_pvector_free(vec);
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::on_actionJmpReverse_triggered()
|
||||
|
@ -109,14 +109,14 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
|
||||
if (c.isValid()) {
|
||||
bbh->highlight(currBlockEntry, c);
|
||||
}
|
||||
Config()->colorsUpdated();
|
||||
emit Config()->colorsUpdated();
|
||||
});
|
||||
|
||||
actionUnhighlight.setText(tr("Unhighlight block"));
|
||||
connect(&actionUnhighlight, &QAction::triggered, this, [this]() {
|
||||
auto bbh = Core()->getBBHighlighter();
|
||||
bbh->clear(blockForAddress(this->seekable->getOffset())->entry);
|
||||
Config()->colorsUpdated();
|
||||
emit Config()->colorsUpdated();
|
||||
});
|
||||
|
||||
QAction *highlightBI = new QAction(this);
|
||||
@ -162,9 +162,8 @@ void DisassemblerGraphView::connectSeekChanged(bool disconn)
|
||||
|
||||
DisassemblerGraphView::~DisassemblerGraphView()
|
||||
{
|
||||
for (QShortcut *shortcut : shortcuts) {
|
||||
delete shortcut;
|
||||
}
|
||||
qDeleteAll(shortcuts);
|
||||
shortcuts.clear();
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::refreshView()
|
||||
@ -182,13 +181,6 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
.set("asm.lines", false)
|
||||
.set("asm.lines.fcn", false);
|
||||
|
||||
CutterJson functions;
|
||||
RzAnalysisFunction *fcn = Core()->functionIn(seekable->getOffset());
|
||||
if (fcn) {
|
||||
currentFcnAddr = fcn->addr;
|
||||
functions = Core()->cmdj("agJ " + RzAddressString(fcn->addr));
|
||||
}
|
||||
|
||||
disassembly_blocks.clear();
|
||||
blocks.clear();
|
||||
|
||||
@ -197,7 +189,20 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
highlight_token = nullptr;
|
||||
}
|
||||
|
||||
emptyGraph = !functions.size();
|
||||
RzAnalysisFunction *fcn = Core()->functionIn(seekable->getOffset());
|
||||
|
||||
windowTitle = tr("Graph");
|
||||
if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) {
|
||||
std::unique_ptr<char, decltype(std::free) *> fcnName {
|
||||
rz_str_escape_utf8_for_json(fcn->name, -1), std::free
|
||||
};
|
||||
windowTitle += QString("(%0)").arg(fcnName.get());
|
||||
} else {
|
||||
windowTitle += "(Empty)";
|
||||
}
|
||||
emit nameChanged(windowTitle);
|
||||
|
||||
emptyGraph = !fcn;
|
||||
if (emptyGraph) {
|
||||
// If there's no function to print, just add a message
|
||||
if (!emptyText) {
|
||||
@ -213,31 +218,20 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
}
|
||||
// Refresh global "empty graph" variable so other widget know there is nothing to show here
|
||||
Core()->setGraphEmpty(emptyGraph);
|
||||
setEntry(fcn ? fcn->addr : RVA_INVALID);
|
||||
|
||||
CutterJson func = functions.first();
|
||||
|
||||
windowTitle = tr("Graph");
|
||||
QString funcName = func["name"].toString().trimmed();
|
||||
if (emptyGraph) {
|
||||
windowTitle += " (Empty)";
|
||||
} else if (!funcName.isEmpty()) {
|
||||
windowTitle += " (" + funcName + ")";
|
||||
if (!fcn) {
|
||||
return;
|
||||
}
|
||||
emit nameChanged(windowTitle);
|
||||
|
||||
RVA entry = func["offset"].toRVA();
|
||||
|
||||
setEntry(entry);
|
||||
for (CutterJson block : func["blocks"]) {
|
||||
RVA block_entry = block["offset"].toRVA();
|
||||
RVA block_size = block["size"].toRVA();
|
||||
RVA block_fail = block["fail"].toRVA();
|
||||
RVA block_jump = block["jump"].toRVA();
|
||||
for (const auto &bbi : CutterRzList<RzAnalysisBlock>(fcn->bbs)) {
|
||||
RVA bbiFail = bbi->fail;
|
||||
RVA bbiJump = bbi->jump;
|
||||
|
||||
DisassemblyBlock db;
|
||||
GraphBlock gb;
|
||||
gb.entry = block_entry;
|
||||
db.entry = block_entry;
|
||||
gb.entry = bbi->addr;
|
||||
db.entry = bbi->addr;
|
||||
if (Config()->getGraphBlockEntryOffset()) {
|
||||
// QColor(0,0,0,0) is transparent
|
||||
db.header_text = Text("[" + RzAddressString(db.entry) + "]", ConfigColor("offset"),
|
||||
@ -245,50 +239,69 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
}
|
||||
db.true_path = RVA_INVALID;
|
||||
db.false_path = RVA_INVALID;
|
||||
if (block_fail) {
|
||||
db.false_path = block_fail;
|
||||
gb.edges.emplace_back(block_fail);
|
||||
if (bbiFail) {
|
||||
db.false_path = bbiFail;
|
||||
gb.edges.emplace_back(bbiFail);
|
||||
}
|
||||
if (block_jump) {
|
||||
if (block_fail) {
|
||||
db.true_path = block_jump;
|
||||
if (bbiJump) {
|
||||
if (bbiFail) {
|
||||
db.true_path = bbiJump;
|
||||
}
|
||||
gb.edges.emplace_back(block_jump);
|
||||
gb.edges.emplace_back(bbiJump);
|
||||
}
|
||||
|
||||
CutterJson switchOp = block["switchop"];
|
||||
if (switchOp.size()) {
|
||||
for (CutterJson caseOp : switchOp["cases"]) {
|
||||
RVA caseJump = caseOp["jump"].toRVA();
|
||||
if (caseJump == RVA_INVALID) {
|
||||
RzAnalysisSwitchOp *switchOp = bbi->switch_op;
|
||||
if (switchOp) {
|
||||
for (const auto &caseOp : CutterRzList<RzAnalysisCaseOp>(switchOp->cases)) {
|
||||
if (caseOp->jump == RVA_INVALID) {
|
||||
continue;
|
||||
}
|
||||
gb.edges.emplace_back(caseJump);
|
||||
gb.edges.emplace_back(caseOp->jump);
|
||||
}
|
||||
}
|
||||
|
||||
CutterJson opArray = block["ops"];
|
||||
CutterJson::iterator iterator = opArray.begin();
|
||||
while (iterator != opArray.end()) {
|
||||
CutterJson op = *iterator;
|
||||
Instr i;
|
||||
i.addr = op["offset"].toUt64();
|
||||
RzCoreLocked core(Core());
|
||||
std::unique_ptr<ut8[]> buf { new ut8[bbi->size] };
|
||||
if (!buf) {
|
||||
break;
|
||||
}
|
||||
rz_io_read_at(core->io, bbi->addr, buf.get(), (int)bbi->size);
|
||||
|
||||
++iterator;
|
||||
std::unique_ptr<RzPVector, decltype(rz_pvector_free) *> vec {
|
||||
rz_pvector_new(reinterpret_cast<RzPVectorFree>(rz_analysis_disasm_text_free)),
|
||||
rz_pvector_free
|
||||
};
|
||||
if (!vec) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (iterator != opArray.end()) {
|
||||
RzCoreDisasmOptions options = {};
|
||||
options.vec = vec.get();
|
||||
options.cbytes = 1;
|
||||
rz_core_print_disasm(core, bbi->addr, buf.get(), (int)bbi->size, (int)bbi->size, NULL,
|
||||
&options);
|
||||
|
||||
auto vecVisitor = CutterPVector<RzAnalysisDisasmText>(vec.get());
|
||||
auto iter = vecVisitor.begin();
|
||||
while (iter != vecVisitor.end()) {
|
||||
RzAnalysisDisasmText *op = *iter;
|
||||
Instr instr;
|
||||
instr.addr = op->offset;
|
||||
|
||||
++iter;
|
||||
if (iter != vecVisitor.end()) {
|
||||
// get instruction size from distance to next instruction ...
|
||||
RVA nextOffset = (*iterator)["offset"].toRVA();
|
||||
i.size = nextOffset - i.addr;
|
||||
RVA nextOffset = (*iter)->offset;
|
||||
instr.size = nextOffset - instr.addr;
|
||||
} else {
|
||||
// or to the end of the block.
|
||||
i.size = (block_entry + block_size) - i.addr;
|
||||
instr.size = (bbi->addr + bbi->size) - instr.addr;
|
||||
}
|
||||
|
||||
QTextDocument textDoc;
|
||||
textDoc.setHtml(CutterCore::ansiEscapeToHtml(op["text"].toString()));
|
||||
textDoc.setHtml(CutterCore::ansiEscapeToHtml(op->text));
|
||||
|
||||
i.plainText = textDoc.toPlainText();
|
||||
instr.plainText = textDoc.toPlainText();
|
||||
|
||||
RichTextPainter::List richText = RichTextPainter::fromTextDocument(textDoc);
|
||||
// Colors::colorizeAssembly(richText, textDoc.toPlainText(), 0);
|
||||
@ -296,23 +309,19 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
bool cropped;
|
||||
int blockLength = Config()->getGraphBlockMaxChars()
|
||||
+ Core()->getConfigb("asm.bytes") * 24 + Core()->getConfigb("asm.emu") * 10;
|
||||
i.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped));
|
||||
instr.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped));
|
||||
if (cropped)
|
||||
i.fullText = richText;
|
||||
instr.fullText = richText;
|
||||
else
|
||||
i.fullText = Text();
|
||||
db.instrs.push_back(i);
|
||||
instr.fullText = Text();
|
||||
db.instrs.push_back(instr);
|
||||
}
|
||||
disassembly_blocks[db.entry] = db;
|
||||
prepareGraphNode(gb);
|
||||
|
||||
addBlock(gb);
|
||||
}
|
||||
cleanupEdges(blocks);
|
||||
|
||||
if (func["blocks"].size()) {
|
||||
computeGraphPlacement();
|
||||
}
|
||||
computeGraphPlacement();
|
||||
}
|
||||
|
||||
DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeConfigurations()
|
||||
|
Loading…
Reference in New Issue
Block a user