Fix graph export commands to use c api

This commit is contained in:
wargio 2023-02-18 13:35:49 +08:00 committed by Anton Kochkov
parent 20801f7fe6
commit 7a96fad546
6 changed files with 168 additions and 89 deletions

View File

@ -4515,3 +4515,68 @@ QStringList CutterCore::getConfigVariableSpaces(const QString &key)
rz_list_free(list); rz_list_free(list);
return stringList; return stringList;
} }
char *CutterCore::getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat format, RVA address)
{
CORE_LOCK();
char *string = nullptr;
RzGraph *graph = rz_core_graph(core, type, address);
if (!graph) {
if (address == RVA_INVALID) {
qWarning() << "Cannot get global graph";
} else {
qWarning() << "Cannot get graph at " << RzAddressString(address);
}
return nullptr;
}
core->graph->is_callgraph = type == RZ_CORE_GRAPH_TYPE_FUNCALL;
switch (format) {
case RZ_CORE_GRAPH_FORMAT_CMD: {
string = rz_graph_drawable_to_cmd(graph);
break;
}
case RZ_CORE_GRAPH_FORMAT_DOT: {
string = rz_core_graph_to_dot_str(core, graph);
break;
}
case RZ_CORE_GRAPH_FORMAT_JSON:
/* fall-thru */
case RZ_CORE_GRAPH_FORMAT_JSON_DISASM: {
string = rz_graph_drawable_to_json_str(graph, true);
break;
}
case RZ_CORE_GRAPH_FORMAT_GML: {
string = rz_graph_drawable_to_gml(graph);
break;
}
default:
break;
}
rz_graph_free(graph);
if (!string) {
qWarning() << "Failed to generate graph";
}
return string;
}
void CutterCore::writeGraphvizGraphToFile(QString path, QString format, RzCoreGraphType type,
RVA address)
{
TempConfig tempConfig;
tempConfig.set("scr.color", false);
tempConfig.set("graph.gv.format", format);
CORE_LOCK();
auto filepath = path.toUtf8();
if (!rz_core_graph_write(core, address, type, filepath)) {
if (address == RVA_INVALID) {
qWarning() << "Cannot get global graph";
} else {
qWarning() << "Cannot get graph at " << RzAddressString(address);
}
}
}

View File

@ -720,6 +720,25 @@ public:
*/ */
bool isWriteModeEnabled(); bool isWriteModeEnabled();
/**
* @brief Returns the textual version of global or specific graph.
* @param type Graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT
* @param format Graph format, example RZ_CORE_GRAPH_FORMAT_DOT or RZ_CORE_GRAPH_FORMAT_GML
* @param address The object address (if global set it to RVA_INVALID)
* @return The textual graph string.
*/
char *getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat format, RVA address);
/**
* @brief Writes a graphviz graph to a file.
* @param path The file output path
* @param format The output format (see graph.gv.format)
* @param type The graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or
* RZ_CORE_GRAPH_TYPE_IMPORT
* @param address The object address (if global set it to RVA_INVALID)
*/
void writeGraphvizGraphToFile(QString path, QString format, RzCoreGraphType type, RVA address);
signals: signals:
void refreshAll(); void refreshAll();

View File

@ -7,7 +7,9 @@
#include <QJsonObject> #include <QJsonObject>
CallGraphWidget::CallGraphWidget(MainWindow *main, bool global) CallGraphWidget::CallGraphWidget(MainWindow *main, bool global)
: MemoryDockWidget(MemoryWidgetType::CallGraph, main), graphView(new CallGraphView(this, main, global)), global(global) : MemoryDockWidget(MemoryWidgetType::CallGraph, main),
graphView(new CallGraphView(this, main, global)),
global(global)
{ {
setObjectName(main ? main->getUniqueObjectName(getWidgetType()) : getWidgetType()); setObjectName(main ? main->getUniqueObjectName(getWidgetType()) : getWidgetType());
this->setWindowTitle(getWindowTitle()); this->setWindowTitle(getWindowTitle());
@ -53,7 +55,7 @@ void CallGraphView::showExportDialog()
} else { } else {
defaultName = QString("callgraph_%1").arg(RzAddressString(address)); defaultName = QString("callgraph_%1").arg(RzAddressString(address));
} }
showExportGraphDialog(defaultName, global ? "agC" : "agc", address); showExportGraphDialog(defaultName, RZ_CORE_GRAPH_TYPE_FUNCALL, global ? RVA_INVALID : address);
} }
void CallGraphView::showAddress(RVA address) void CallGraphView::showAddress(RVA address)
@ -80,7 +82,6 @@ static inline bool isBetween(ut64 a, ut64 x, ut64 b)
return (a == UT64_MAX || a <= x) && (b == UT64_MAX || x <= b); return (a == UT64_MAX || a <= x) && (b == UT64_MAX || x <= b);
} }
void CallGraphView::loadCurrentGraph() void CallGraphView::loadCurrentGraph()
{ {
blockContent.clear(); blockContent.clear();

View File

@ -152,7 +152,7 @@ void CutterGraphView::zoomReset()
void CutterGraphView::showExportDialog() void CutterGraphView::showExportDialog()
{ {
showExportGraphDialog("graph", "", RVA_INVALID); showExportGraphDialog("global_funcall", RZ_CORE_GRAPH_TYPE_FUNCALL, RVA_INVALID);
} }
void CutterGraphView::updateColors() void CutterGraphView::updateColors()
@ -318,12 +318,12 @@ void CutterGraphView::mouseMoveEvent(QMouseEvent *event)
emit graphMoved(); emit graphMoved();
} }
void CutterGraphView::exportGraph(QString filePath, GraphExportType type, QString graphCommand, void CutterGraphView::exportGraph(QString filePath, GraphExportType exportType,
RVA address) RzCoreGraphType graphType, RVA address)
{ {
bool graphTransparent = Config()->getBitmapTransparentState(); bool graphTransparent = Config()->getBitmapTransparentState();
double graphScaleFactor = Config()->getBitmapExportScaleFactor(); double graphScaleFactor = Config()->getBitmapExportScaleFactor();
switch (type) { switch (exportType) {
case GraphExportType::Png: case GraphExportType::Png:
this->saveAsBitmap(filePath, "png", graphScaleFactor, graphTransparent); this->saveAsBitmap(filePath, "png", graphScaleFactor, graphTransparent);
break; break;
@ -335,56 +335,55 @@ void CutterGraphView::exportGraph(QString filePath, GraphExportType type, QStrin
break; break;
case GraphExportType::GVDot: case GraphExportType::GVDot:
exportRzTextGraph(filePath, graphCommand + "d", address); exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_DOT, address);
break; break;
case GraphExportType::RzJson: case GraphExportType::RzJson:
exportRzTextGraph(filePath, graphCommand + "j", address); exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_JSON, address);
break; break;
case GraphExportType::RzGml: case GraphExportType::RzGml:
exportRzTextGraph(filePath, graphCommand + "g", address); exportRzTextGraph(filePath, graphType, RZ_CORE_GRAPH_FORMAT_GML, address);
break;
case GraphExportType::RzSDBKeyValue:
exportRzTextGraph(filePath, graphCommand + "k", address);
break; break;
case GraphExportType::GVJson: case GraphExportType::GVJson:
exportRizinGraphvizGraph(filePath, "json", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "json", graphType, address);
break; break;
case GraphExportType::GVGif: case GraphExportType::GVGif:
exportRizinGraphvizGraph(filePath, "gif", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "gif", graphType, address);
break; break;
case GraphExportType::GVPng: case GraphExportType::GVPng:
exportRizinGraphvizGraph(filePath, "png", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "png", graphType, address);
break; break;
case GraphExportType::GVJpeg: case GraphExportType::GVJpeg:
exportRizinGraphvizGraph(filePath, "jpg", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "jpg", graphType, address);
break; break;
case GraphExportType::GVPostScript: case GraphExportType::GVPostScript:
exportRizinGraphvizGraph(filePath, "ps", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "ps", graphType, address);
break; break;
case GraphExportType::GVSvg: case GraphExportType::GVSvg:
exportRizinGraphvizGraph(filePath, "svg", graphCommand, address); Core()->writeGraphvizGraphToFile(filePath, "svg", graphType, address);
break;
case GraphExportType::GVPdf:
Core()->writeGraphvizGraphToFile(filePath, "pdf", graphType, address);
break; break;
} }
} }
void CutterGraphView::exportRizinGraphvizGraph(QString filePath, QString type, QString graphCommand, void CutterGraphView::exportRzTextGraph(QString filePath, RzCoreGraphType type,
RVA address) RzCoreGraphFormat format, RVA address)
{ {
TempConfig tempConfig; char *string = Core()->getTextualGraphAt(type, format, address);
tempConfig.set("graph.gv.format", type); if (!string) {
qWarning() << Core()->cmdRawAt(QString("%0w \"%1\"").arg(graphCommand).arg(filePath), address);
}
void CutterGraphView::exportRzTextGraph(QString filePath, QString graphCommand, RVA address)
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Can't open file";
return; return;
} }
QTextStream fileOut(&file);
fileOut << Core()->cmdRawAt(QString("%0").arg(graphCommand), address); QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream fileOut(&file);
fileOut << string;
} else {
qWarning() << "Can't open or create file: " << filePath;
}
free(string);
} }
bool CutterGraphView::graphIsBitamp(CutterGraphView::GraphExportType type) bool CutterGraphView::graphIsBitamp(CutterGraphView::GraphExportType type)
@ -403,40 +402,39 @@ bool CutterGraphView::graphIsBitamp(CutterGraphView::GraphExportType type)
Q_DECLARE_METATYPE(CutterGraphView::GraphExportType); Q_DECLARE_METATYPE(CutterGraphView::GraphExportType);
void CutterGraphView::showExportGraphDialog(QString defaultName, QString graphCommand, RVA address) void CutterGraphView::showExportGraphDialog(QString defaultName, RzCoreGraphType type, RVA address)
{ {
qWarning() << defaultName << " - " << type << " addr " << RzAddressString(address);
QVector<MultitypeFileSaveDialog::TypeDescription> types = { QVector<MultitypeFileSaveDialog::TypeDescription> types = {
{ tr("PNG (*.png)"), "png", QVariant::fromValue(GraphExportType::Png) }, { tr("PNG (*.png)"), "png", QVariant::fromValue(GraphExportType::Png) },
{ tr("JPEG (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::Jpeg) }, { tr("JPEG (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::Jpeg) },
{ tr("SVG (*.svg)"), "svg", QVariant::fromValue(GraphExportType::Svg) } { tr("SVG (*.svg)"), "svg", QVariant::fromValue(GraphExportType::Svg) }
}; };
bool rzGraphExports = !graphCommand.isEmpty(); types.append({
if (rzGraphExports) { { tr("Graphviz dot (*.dot)"), "dot", QVariant::fromValue(GraphExportType::GVDot) },
types.append({ { tr("Graph Modelling Language (*.gml)"), "gml",
{ tr("Graphviz dot (*.dot)"), "dot", QVariant::fromValue(GraphExportType::GVDot) }, QVariant::fromValue(GraphExportType::RzGml) },
{ tr("Graph Modelling Language (*.gml)"), "gml", { tr("RZ JSON (*.json)"), "json", QVariant::fromValue(GraphExportType::RzJson) },
QVariant::fromValue(GraphExportType::RzGml) }, });
{ tr("RZ JSON (*.json)"), "json", QVariant::fromValue(GraphExportType::RzJson) },
{ tr("SDB key-value (*.txt)"), "txt", bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty()
QVariant::fromValue(GraphExportType::RzSDBKeyValue) }, || !QStandardPaths::findExecutable("xdot").isEmpty();
}); if (hasGraphviz) {
bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty() types.append({ { tr("Graphviz json (*.json)"), "json",
|| !QStandardPaths::findExecutable("xdot").isEmpty(); QVariant::fromValue(GraphExportType::GVJson) },
if (hasGraphviz) { { tr("Graphviz gif (*.gif)"), "gif",
types.append({ { tr("Graphviz json (*.json)"), "json", QVariant::fromValue(GraphExportType::GVGif) },
QVariant::fromValue(GraphExportType::GVJson) }, { tr("Graphviz png (*.png)"), "png",
{ tr("Graphviz gif (*.gif)"), "gif", QVariant::fromValue(GraphExportType::GVPng) },
QVariant::fromValue(GraphExportType::GVGif) }, { tr("Graphviz jpg (*.jpg)"), "jpg",
{ tr("Graphviz png (*.png)"), "png", QVariant::fromValue(GraphExportType::GVJpeg) },
QVariant::fromValue(GraphExportType::GVPng) }, { tr("Graphviz PostScript (*.ps)"), "ps",
{ tr("Graphviz jpg (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::GVPostScript) },
QVariant::fromValue(GraphExportType::GVJpeg) }, { tr("Graphviz svg (*.svg)"), "svg",
{ tr("Graphviz PostScript (*.ps)"), "ps", QVariant::fromValue(GraphExportType::GVSvg) },
QVariant::fromValue(GraphExportType::GVPostScript) }, { tr("Graphviz pdf (*.pdf)"), "pdf",
{ tr("Graphviz svg (*.svg)"), "svg", QVariant::fromValue(GraphExportType::GVPdf) } });
QVariant::fromValue(GraphExportType::GVSvg) } });
}
} }
MultitypeFileSaveDialog dialog(this, tr("Export Graph")); MultitypeFileSaveDialog dialog(this, tr("Export Graph"));
@ -470,5 +468,5 @@ void CutterGraphView::showExportGraphDialog(QString defaultName, QString graphCo
} }
QString filePath = dialog.selectedFiles().first(); QString filePath = dialog.selectedFiles().first();
exportGraph(filePath, exportType, graphCommand, address); exportGraph(filePath, exportType, type, address);
} }

View File

@ -32,48 +32,38 @@ public:
GVJpeg, GVJpeg,
GVPostScript, GVPostScript,
GVSvg, GVSvg,
GVPdf,
RzGml, RzGml,
RzSDBKeyValue,
RzJson RzJson
}; };
/** /**
* @brief Export graph to a file in the specified format * @brief Export graph to a file in the specified format
* @param filePath * @param filePath - output file path
* @param type export type, GV* and Rz* types require \p graphCommand * @param exportType - export type, GV* and Rz* types require \p graphCommand
* @param graphCommand rizin graph printing command without type, not required for direct image * @param graphType - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or
* export * RZ_CORE_GRAPH_TYPE_IMPORT
* @param address object address for commands like agf * @param address - object address (if global set it to RVA_INVALID)
*/ */
void exportGraph(QString filePath, GraphExportType type, QString graphCommand = "", void exportGraph(QString filePath, GraphExportType exportType, RzCoreGraphType graphType,
RVA address = RVA_INVALID); RVA address = RVA_INVALID);
/**
* @brief Export image using rizin ag*w command and graphviz.
* Requires graphviz dot executable in the path.
*
* @param filePath output file path
* @param type image format as expected by "e graph.gv.format"
* @param graphCommand rizin command without type, for example agf
* @param address object address if required by command
*/
void exportRizinGraphvizGraph(QString filePath, QString type, QString graphCommand,
RVA address);
/** /**
* @brief Export graph in one of the text formats supported by rizin json, gml, SDB key-value * @brief Export graph in one of the text formats supported by rizin json, gml, SDB key-value
* @param filePath output file path * @param filePath - output file path
* @param graphCommand graph command including the format, example "agfd" or "agfg" * @param type - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT
* @param address object address if required by command * @param format - graph format, example RZ_CORE_GRAPH_FORMAT_DOT or RZ_CORE_GRAPH_FORMAT_GML
* @param address - object address (if global set it to RVA_INVALID)
*/ */
void exportRzTextGraph(QString filePath, QString graphCommand, RVA address); void exportRzTextGraph(QString filePath, RzCoreGraphType type, RzCoreGraphFormat format,
RVA address);
static bool graphIsBitamp(GraphExportType type); static bool graphIsBitamp(GraphExportType type);
/** /**
* @brief Show graph export dialog. * @brief Show graph export dialog.
* @param defaultName - default file name in the export dialog * @param defaultName - default file name in the export dialog
* @param graphCommand - rizin graph commmand with graph type and without export type, for * @param type - graph type, example RZ_CORE_GRAPH_TYPE_FUNCALL or RZ_CORE_GRAPH_TYPE_IMPORT
* example afC. Leave empty for non-rizin graphs. In such case only direct image export will be * @param address - object address (if global set it to RVA_INVALID)
* available.
* @param address - object address if relevant for \p graphCommand
*/ */
void showExportGraphDialog(QString defaultName, QString graphCommand = "", void showExportGraphDialog(QString defaultName, RzCoreGraphType type,
RVA address = RVA_INVALID); RVA address = RVA_INVALID);
public slots: public slots:

View File

@ -194,6 +194,7 @@ void DisassemblerGraphView::loadCurrentGraph()
windowTitle = tr("Graph"); windowTitle = tr("Graph");
if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) { if (fcn && RZ_STR_ISNOTEMPTY(fcn->name)) {
currentFcnAddr = fcn->addr;
auto fcnName = fromOwned(rz_str_escape_utf8_for_json(fcn->name, -1)); auto fcnName = fromOwned(rz_str_escape_utf8_for_json(fcn->name, -1));
windowTitle += QString("(%0)").arg(fcnName.get()); windowTitle += QString("(%0)").arg(fcnName.get());
} else { } else {
@ -891,6 +892,11 @@ void DisassemblerGraphView::contextMenuEvent(QContextMenuEvent *event)
void DisassemblerGraphView::showExportDialog() void DisassemblerGraphView::showExportDialog()
{ {
if (currentFcnAddr == RVA_INVALID) {
qWarning() << "Cannot find current function.";
return;
}
QString defaultName = "graph"; QString defaultName = "graph";
if (auto f = Core()->functionIn(currentFcnAddr)) { if (auto f = Core()->functionIn(currentFcnAddr)) {
QString functionName = f->name; QString functionName = f->name;
@ -901,7 +907,7 @@ void DisassemblerGraphView::showExportDialog()
defaultName = functionName; defaultName = functionName;
} }
} }
showExportGraphDialog(defaultName, "agf", currentFcnAddr); showExportGraphDialog(defaultName, RZ_CORE_GRAPH_TYPE_BLOCK_FUN, currentFcnAddr);
} }
void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event,