mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-23 21:35:28 +00:00
Add new Analysis section to the Preferences widget (#2332)
This commit is contained in:
parent
724d3f26b4
commit
9e346275c5
BIN
docs/source/images/analysis_dialog.png
Normal file
BIN
docs/source/images/analysis_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
@ -7,6 +7,7 @@ Most configuration is done using the Preferences dialog. It can be opened by cli
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
preferences/analysis
|
||||
preferences/initialization-script
|
||||
preferences/layout
|
||||
preferences/*
|
||||
|
74
docs/source/user-docs/preferences/analysis.rst
Normal file
74
docs/source/user-docs/preferences/analysis.rst
Normal file
@ -0,0 +1,74 @@
|
||||
Analysis Options
|
||||
================
|
||||
|
||||
Cutter will use the underlying radare2 analysis options to analyze a binary. These options are usually
|
||||
configured when the binary is first loaded. However, they can be later changed using the Analysis
|
||||
dialog, and a new analysis that takes these options into account can then be performed.
|
||||
|
||||
Analysis Dialog
|
||||
---------------
|
||||
|
||||
.. image:: ../../images/analysis_dialog.png
|
||||
:alt: Analysis dialog
|
||||
|
||||
**Description:** The Analysis dialog allows setting some radare2's analysis options. The supported options are described
|
||||
below.
|
||||
|
||||
Clicking on the "Analyze Program" button will trigger an analysis of the current binary with radare2's ``aaa``
|
||||
command, taking into account the configured values of the analysis options.
|
||||
|
||||
**Steps to open:** ``Edit -> Preferences -> Analysis``
|
||||
|
||||
Speculatively set a name for the functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Try to name functions without symbols by using artifacts in the functions such as API calls and strings.
|
||||
|
||||
**Configuration variable:** ``anal.autoname``
|
||||
|
||||
|
||||
Search for new functions following already defined functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Cutter will check if there is a candidate for a new function following an already defined one, as the compiler usually
|
||||
state them together. This option is taking the advantages of both Recursive Analysis and Linear Sweep into some kind of a hybrid mode. For each discovered function, the analysis will try to check for a function-prologue, usually following a gap of null bytes or ``cc`` bytes. This will help with discovering functions which are not referenced in the program. As such, it will make the analysis slower and prone to false-positives.
|
||||
|
||||
**Configuration variable:** ``anal.hasnext``
|
||||
|
||||
|
||||
Create references for unconditional jumps
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
When encountering unconditional jumps during the analysis, Cutter will add a code-reference even if it is not guaranteed
|
||||
that the jump will take place.
|
||||
|
||||
**Configuration variable:** ``anal.jmp.ref``
|
||||
|
||||
|
||||
Analyze jump tables in switch statements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
When encountering switch statements during analysis, continue and analyze the target blocks of the jump tables.
|
||||
|
||||
**Configuration variable:** ``anal.jmp.tbl``
|
||||
|
||||
|
||||
Analyze ``push`` + ``ret`` as ``jmp``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
When performing analysis of a function, treat the sequence of ``push`` followed by ``ret`` instruction as a ``jmp``.
|
||||
This can help Cutter to continue the analysis to target of the ``jmp``.
|
||||
|
||||
**Configuration variable:** ``anal.pushret``
|
||||
|
||||
|
||||
Show verbose information when performing analysis
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
When enabled, Cutter will print warnings it encountered while preforming analysis. These warnings can help the user
|
||||
understand if anything went wrong in the analysis. This function is not only helpful when trying to perform a full
|
||||
analysis of the program, but also when trying to analyze and define new functions.
|
||||
|
||||
**Configuration variable:** ``anal.verbose``
|
||||
|
||||
|
||||
Verbose output from type analysis
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Print warnings encountered while preforming type analysis. These warnings can help the user understand if anything went
|
||||
wrong in the analysis.
|
||||
|
||||
**Configuration variable:** ``anal.types.verbose``
|
@ -435,7 +435,8 @@ SOURCES += \
|
||||
widgets/SimpleTextGraphView.cpp \
|
||||
widgets/R2GraphWidget.cpp \
|
||||
widgets/CallGraph.cpp \
|
||||
widgets/AddressableDockWidget.cpp
|
||||
widgets/AddressableDockWidget.cpp \
|
||||
dialogs/preferences/AnalOptionsWidget.cpp
|
||||
|
||||
GRAPHVIZ_SOURCES = \
|
||||
widgets/GraphvizLayout.cpp
|
||||
@ -592,7 +593,8 @@ HEADERS += \
|
||||
widgets/SimpleTextGraphView.h \
|
||||
widgets/R2GraphWidget.h \
|
||||
widgets/CallGraph.h \
|
||||
widgets/AddressableDockWidget.h
|
||||
widgets/AddressableDockWidget.h \
|
||||
dialogs/preferences/AnalOptionsWidget.h
|
||||
|
||||
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h
|
||||
|
||||
@ -660,7 +662,8 @@ FORMS += \
|
||||
dialogs/preferences/ColorThemeEditDialog.ui \
|
||||
widgets/ListDockWidget.ui \
|
||||
dialogs/LayoutManager.ui \
|
||||
widgets/R2GraphWidget.ui
|
||||
widgets/R2GraphWidget.ui \
|
||||
dialogs/preferences/AnalOptionsWidget.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc \
|
||||
|
@ -21,11 +21,17 @@ void AnalTask::interrupt()
|
||||
r_cons_singleton()->context->breaked = true;
|
||||
}
|
||||
|
||||
QString AnalTask::getTitle() {
|
||||
// If no file is loaded we consider it's Initial Analysis
|
||||
QJsonArray openedFiles = Core()->getOpenedFiles();
|
||||
if (!openedFiles.size()) {
|
||||
return tr("Initial Analysis");
|
||||
}
|
||||
return tr("Analyzing Program");
|
||||
}
|
||||
|
||||
void AnalTask::runTask()
|
||||
{
|
||||
log(tr("Loading the file..."));
|
||||
openFailed = false;
|
||||
|
||||
int perms = R_PERM_RX;
|
||||
if (options.writeEnabled) {
|
||||
perms |= R_PERM_W;
|
||||
@ -39,6 +45,8 @@ void AnalTask::runTask()
|
||||
// Do not reload the file if already loaded
|
||||
QJsonArray openedFiles = Core()->getOpenedFiles();
|
||||
if (!openedFiles.size() && options.filename.length()) {
|
||||
log(tr("Loading the file..."));
|
||||
openFailed = false;
|
||||
bool fileLoaded = Core()->loadFile(options.filename,
|
||||
options.binLoadAddr,
|
||||
options.mapAddr,
|
||||
|
@ -17,7 +17,7 @@ public:
|
||||
explicit AnalTask();
|
||||
~AnalTask();
|
||||
|
||||
QString getTitle() override { return tr("Initial Analysis"); }
|
||||
QString getTitle() override;
|
||||
|
||||
void setOptions(const InitialOptions &options) { this->options = options; }
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "ui_MainWindow.h"
|
||||
|
||||
// Common Headers
|
||||
#include "common/AnalTask.h"
|
||||
#include "common/BugReporting.h"
|
||||
#include "common/Highlighter.h"
|
||||
#include "common/Helpers.h"
|
||||
@ -243,6 +244,10 @@ void MainWindow::initUI()
|
||||
|
||||
enableDebugWidgetsMenu(false);
|
||||
readSettings();
|
||||
|
||||
// Display tooltip for the Analyze Program action
|
||||
ui->actionAnalyze->setToolTip("Analyze the program using radare2's \"aaa\" command");
|
||||
ui->menuFile->setToolTipsVisible(true);
|
||||
}
|
||||
|
||||
void MainWindow::initToolBar()
|
||||
@ -1579,9 +1584,24 @@ void MainWindow::on_actionRefresh_Panels_triggered()
|
||||
this->refreshAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A signal that creates an AsyncTask to re-analyze the current file
|
||||
*/
|
||||
void MainWindow::on_actionAnalyze_triggered()
|
||||
{
|
||||
// TODO: implement this, but do NOT open InitialOptionsDialog!!
|
||||
auto *analTask = new AnalTask();
|
||||
InitialOptions options;
|
||||
options.analCmd = { {"aaa", "Auto analysis"} };
|
||||
analTask->setOptions(options);
|
||||
AsyncTask::Ptr analTaskPtr(analTask);
|
||||
|
||||
auto *taskDialog = new AsyncTaskDialog(analTaskPtr);
|
||||
taskDialog->setInterruptOnClose(true);
|
||||
taskDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
taskDialog->show();
|
||||
connect(analTask, &AnalTask::finished, this, &MainWindow::refreshAll);
|
||||
|
||||
Core()->getAsyncTaskManager()->start(analTaskPtr);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionImportPDB_triggered()
|
||||
|
@ -150,6 +150,8 @@ public slots:
|
||||
|
||||
void on_actionTabs_triggered();
|
||||
|
||||
void on_actionAnalyze_triggered();
|
||||
|
||||
void lockUnlock_Docks(bool what);
|
||||
|
||||
void on_actionRun_Script_triggered();
|
||||
@ -192,8 +194,6 @@ private slots:
|
||||
|
||||
void on_actionPreferences_triggered();
|
||||
|
||||
void on_actionAnalyze_triggered();
|
||||
|
||||
void on_actionImportPDB_triggered();
|
||||
|
||||
void on_actionExport_as_code_triggered();
|
||||
|
@ -75,6 +75,7 @@
|
||||
<addaction name="actionMap"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionImportPDB"/>
|
||||
<addaction name="actionAnalyze"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuSetMode"/>
|
||||
<addaction name="actionCommitChanges"/>
|
||||
@ -741,7 +742,7 @@
|
||||
</action>
|
||||
<action name="actionAnalyze">
|
||||
<property name="text">
|
||||
<string>Analyze</string>
|
||||
<string>Analyze Program</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport_as_code">
|
||||
|
52
src/dialogs/preferences/AnalOptionsWidget.cpp
Normal file
52
src/dialogs/preferences/AnalOptionsWidget.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "AnalOptionsWidget.h"
|
||||
#include "ui_AnalOptionsWidget.h"
|
||||
|
||||
#include "PreferencesDialog.h"
|
||||
|
||||
#include "common/Helpers.h"
|
||||
#include "common/Configuration.h"
|
||||
|
||||
#include "core/MainWindow.h"
|
||||
|
||||
AnalOptionsWidget::AnalOptionsWidget(PreferencesDialog *dialog)
|
||||
: QDialog(dialog),
|
||||
ui(new Ui::AnalOptionsWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
checkboxes = {
|
||||
{ ui->autonameCheckbox, "anal.autoname" },
|
||||
{ ui->hasnextCheckbox, "anal.hasnext" },
|
||||
{ ui->jmpRefCheckbox, "anal.jmp.ref" },
|
||||
{ ui->jmpTblCheckbox, "anal.jmp.tbl" },
|
||||
{ ui->pushRetCheckBox, "anal.pushret" },
|
||||
{ ui->typesVerboseCheckBox, "anal.types.verbose" },
|
||||
{ ui->verboseCheckBox, "anal.verbose" }
|
||||
};
|
||||
|
||||
// Connect each checkbox from "checkboxes" to the generic signal "checkboxEnabler"
|
||||
for (ConfigCheckbox &confCheckbox : checkboxes) {
|
||||
QString val = confCheckbox.config;
|
||||
QCheckBox &cb = *confCheckbox.checkBox;
|
||||
connect(confCheckbox.checkBox, &QCheckBox::stateChanged, this, [this, val, &cb]() { checkboxEnabler(&cb, val); });
|
||||
}
|
||||
|
||||
ui->analyzePushButton->setToolTip("Analyze the program using radare2's \"aaa\" command");
|
||||
auto *mainWindow = new MainWindow(this);
|
||||
connect(ui->analyzePushButton, &QPushButton::clicked, mainWindow, &MainWindow::on_actionAnalyze_triggered);
|
||||
updateAnalOptionsFromVars();
|
||||
}
|
||||
|
||||
AnalOptionsWidget::~AnalOptionsWidget() {}
|
||||
|
||||
void AnalOptionsWidget::checkboxEnabler(QCheckBox *checkBox, const QString &config)
|
||||
{
|
||||
Config()->setConfig(config, checkBox->isChecked());
|
||||
}
|
||||
|
||||
void AnalOptionsWidget::updateAnalOptionsFromVars()
|
||||
{
|
||||
for (ConfigCheckbox &confCheckbox : checkboxes) {
|
||||
qhelpers::setCheckedWithoutSignals(confCheckbox.checkBox, Core()->getConfigb(confCheckbox.config));
|
||||
}
|
||||
}
|
46
src/dialogs/preferences/AnalOptionsWidget.h
Normal file
46
src/dialogs/preferences/AnalOptionsWidget.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef ANALOPTIONSWIDGET_H
|
||||
#define ANALOPTIONSWIDGET_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <memory>
|
||||
|
||||
#include "core/Cutter.h"
|
||||
|
||||
class PreferencesDialog;
|
||||
|
||||
namespace Ui {
|
||||
class AnalOptionsWidget;
|
||||
}
|
||||
|
||||
class AnalOptionsWidget : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AnalOptionsWidget(PreferencesDialog *dialog);
|
||||
~AnalOptionsWidget();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::AnalOptionsWidget> ui;
|
||||
struct ConfigCheckbox {
|
||||
QCheckBox *checkBox;
|
||||
QString config;
|
||||
};
|
||||
QList<ConfigCheckbox> checkboxes;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief A slot to display the options in the dialog according to the current anal.* configuration
|
||||
*/
|
||||
void updateAnalOptionsFromVars();
|
||||
|
||||
/**
|
||||
* @brief A generic slot to handle the simple cases where a checkbox is toggled
|
||||
* while it's only responsible for a single independent boolean configuration eval.
|
||||
* @param checkBox - The checkbox which is responsible for the signal
|
||||
* @param config - the configuration string to be toggled
|
||||
*/
|
||||
static void checkboxEnabler(QCheckBox *checkbox, const QString &config);
|
||||
};
|
||||
|
||||
#endif // ANALOPTIONSWIDGET_H
|
174
src/dialogs/preferences/AnalOptionsWidget.ui
Normal file
174
src/dialogs/preferences/AnalOptionsWidget.ui
Normal file
@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AnalOptionsWidget</class>
|
||||
<widget class="QWidget" name="AnalOptionsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>633</width>
|
||||
<height>689</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Analysis</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>611</width>
|
||||
<height>610</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QCheckBox" name="verboseCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>190</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show verbose information when performing analysis (anal.verbose)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="pushRetCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>130</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Analyze push+ret as jmp (anal.pushret)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="typesVerboseCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>160</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Verbose output from type analysis (anal.types.verbose)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="autonameCheckbox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Speculatively set a name for the functions (anal.autoname)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="hasnextCheckbox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search for new functions following already defined functions (anal.hasnext)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="jmpRefCheckbox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>70</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create references for unconditional jumps (anal.jmp.ref)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="jmpTblCheckbox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>100</y>
|
||||
<width>601</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Analyze jump tables in switch statements (anal.jmp.tbl)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QPushButton" name="analyzePushButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Analyze program</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -7,6 +7,7 @@
|
||||
#include "DebugOptionsWidget.h"
|
||||
#include "PluginsOptionsWidget.h"
|
||||
#include "InitializationFileEditor.h"
|
||||
#include "AnalOptionsWidget.h"
|
||||
|
||||
#include "PreferenceCategory.h"
|
||||
|
||||
@ -57,6 +58,11 @@ PreferencesDialog::PreferencesDialog(QWidget *parent)
|
||||
tr("Initialization Script"),
|
||||
new InitializationFileEditor(this),
|
||||
QIcon(":/img/icons/initialization.svg")
|
||||
},
|
||||
{
|
||||
tr("Analysis"),
|
||||
new AnalOptionsWidget(this),
|
||||
QIcon(":/img/icons/cog_light.svg")
|
||||
}
|
||||
};
|
||||
|
||||
@ -119,6 +125,7 @@ void PreferencesDialog::chooseThemeIcons()
|
||||
{ QStringLiteral("Appearance"), QStringLiteral("polar.svg") },
|
||||
{ QStringLiteral("Plugins"), QStringLiteral("plugins.svg") },
|
||||
{ QStringLiteral("Initialization Script"), QStringLiteral("initialization.svg") },
|
||||
{ QStringLiteral("Analysis"), QStringLiteral("cog_light.svg") },
|
||||
};
|
||||
QList<QPair<void*, QString>> supportedIconsNames;
|
||||
|
||||
|
4
src/img/icons/light/cog_light.svg
Normal file
4
src/img/icons/light/cog_light.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg style="enable-background:new 0 0 32 32" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="32px" width="32px" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
|
||||
<path d="m32 18v-4l-4.8-2c-0.13-0.38-0.27-0.74-0.44-1.1l1.9-4.8-3-3-4.8 2c-0.36-0.18-0.73-0.32-1.1-0.46l-2-4.7h-4l-2 4.7c-0.4 0.14-0.78 0.29-1.2 0.47l-4.8-1.9-3 2.8 1.9 4.7c-0.1 0-0.3 1-0.4 1l-4.7 2v4l4.7 2c0.14 0.41 0.3 0.8 0.49 1.2l-1.9 4.7 2.8 2.8 4.7-1.9c0.38 0.18 0.77 0.32 1.2 0.46l2 5h4l2-4.8c0.38-0.14 0.75-0.29 1.1-0.46l4.8 1.9 2.8-2.8-2-4.8c0.17-0.36 0.3-0.72 0.44-1.1l5-2zm-16 4c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-3 6-6 6z" fill="#039be5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 785 B |
@ -63,6 +63,7 @@
|
||||
<file>img/icons/bug.svg</file>
|
||||
<file>img/icons/light/bug.svg</file>
|
||||
<file>img/icons/cog_light.svg</file>
|
||||
<file>img/icons/light/cog_light.svg</file>
|
||||
<file>img/icons/cog_white.svg</file>
|
||||
<file>img/icons/new_light.svg</file>
|
||||
<file>img/icons/run_light.svg</file>
|
||||
|
Loading…
Reference in New Issue
Block a user