mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-18 10:56:11 +00:00
Heap bins view (#2710)
* Added bins dialog box * Bins dialog box working * Add size column * Add newline * Make code more readable * Make headers better * Add tooltip for bins widget * Added easy access to detailed chunk info from bins dialog * Experimenting with CutterGraphView * Added Basic Graph View for bins using `simpleTextgraphView` * Added Bins button * Bug fix * Add bin message to the last chunk in the list * Add addresses and addressablecontextmenu to graphs * Add support multi line graph blocks * Fix indent * Dont clear dialog box * Add `detailed chunk info` in Graphs context menu * Minor changes * Update Rizin
This commit is contained in:
parent
2d778c01d7
commit
2d7b6d15c0
2
rizin
2
rizin
@ -1 +1 @@
|
||||
Subproject commit 92ad8bef001843df5882e71c23761987cfa3d9f4
|
||||
Subproject commit 381f22d7cc81bf2eb6663a690a715ff4f8f09373
|
@ -144,6 +144,8 @@ set(SOURCES
|
||||
dialogs/GlibcHeapInfoDialog.cpp
|
||||
widgets/HeapDockWidget.cpp
|
||||
widgets/GlibcHeapWidget.cpp
|
||||
dialogs/GlibcHeapBinsDialog.cpp
|
||||
widgets/HeapBinsGraphView.cpp
|
||||
)
|
||||
set(HEADER_FILES
|
||||
core/Cutter.h
|
||||
@ -300,6 +302,8 @@ set(HEADER_FILES
|
||||
dialogs/GlibcHeapInfoDialog.h
|
||||
widgets/HeapDockWidget.h
|
||||
widgets/GlibcHeapWidget.h
|
||||
dialogs/GlibcHeapBinsDialog.h
|
||||
widgets/HeapBinsGraphView.h
|
||||
)
|
||||
set(UI_FILES
|
||||
dialogs/AboutDialog.ui
|
||||
@ -369,6 +373,7 @@ set(UI_FILES
|
||||
dialogs/GlibcHeapInfoDialog.ui
|
||||
widgets/HeapDockWidget.ui
|
||||
widgets/GlibcHeapWidget.ui
|
||||
dialogs/GlibcHeapBinsDialog.ui
|
||||
)
|
||||
set(QRC_FILES
|
||||
resources.qrc
|
||||
|
@ -1619,6 +1619,44 @@ RzHeapChunkSimple *CutterCore::getHeapChunk(ut64 addr)
|
||||
return rz_heap_chunk(core, addr);
|
||||
}
|
||||
|
||||
QVector<RzHeapBin *> CutterCore::getHeapBins(ut64 arena_addr)
|
||||
{
|
||||
CORE_LOCK();
|
||||
QVector<RzHeapBin *> bins_vector;
|
||||
|
||||
MallocState *arena = rz_heap_get_arena(core, arena_addr);
|
||||
if (!arena) {
|
||||
return bins_vector;
|
||||
}
|
||||
|
||||
// get small, large, unsorted bins
|
||||
for (int i = 0; i <= NBINS - 2; i++) {
|
||||
RzHeapBin *bin = rz_heap_bin_content(core, arena, i, arena_addr);
|
||||
if (!bin) {
|
||||
continue;
|
||||
}
|
||||
if (!rz_list_length(bin->chunks)) {
|
||||
rz_heap_bin_free_64(bin);
|
||||
continue;
|
||||
}
|
||||
bins_vector.append(bin);
|
||||
}
|
||||
// get fastbins
|
||||
for (int i = 0; i < 10; i++) {
|
||||
RzHeapBin *bin = rz_heap_fastbin_content(core, arena, i);
|
||||
if (!bin) {
|
||||
continue;
|
||||
}
|
||||
if (!rz_list_length(bin->chunks)) {
|
||||
rz_heap_bin_free_64(bin);
|
||||
continue;
|
||||
}
|
||||
bins_vector.append(bin);
|
||||
}
|
||||
|
||||
return bins_vector;
|
||||
}
|
||||
|
||||
QJsonDocument CutterCore::getChildProcesses(int pid)
|
||||
{
|
||||
// Return the currently debugged process and it's children
|
||||
|
@ -419,6 +419,7 @@ public:
|
||||
* @return RzHeapChunkSimple struct pointer for the heap chunk
|
||||
*/
|
||||
RzHeapChunkSimple *getHeapChunk(ut64 addr);
|
||||
QVector<RzHeapBin *> getHeapBins(ut64 arena_addr);
|
||||
void startDebug();
|
||||
void startEmulation();
|
||||
/**
|
||||
|
210
src/dialogs/GlibcHeapBinsDialog.cpp
Normal file
210
src/dialogs/GlibcHeapBinsDialog.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
#include <HeapBinsGraphView.h>
|
||||
#include "GlibcHeapBinsDialog.h"
|
||||
#include "ui_GlibcHeapBinsDialog.h"
|
||||
#include "GlibcHeapInfoDialog.h"
|
||||
|
||||
GlibcHeapBinsDialog::GlibcHeapBinsDialog(RVA m_state, MainWindow *main, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
ui(new Ui::GlibcHeapBinsDialog),
|
||||
m_state(m_state),
|
||||
binsModel(new BinsModel(m_state, this)),
|
||||
main(main)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->viewBins->setModel(binsModel);
|
||||
ui->viewBins->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
ui->viewBins->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
ui->viewBins->verticalHeader()->hide();
|
||||
ui->viewBins->resizeColumnsToContents();
|
||||
|
||||
connect(ui->viewBins->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
&GlibcHeapBinsDialog::onCurrentChanged);
|
||||
connect(ui->lineEdit, &QLineEdit::returnPressed, this,
|
||||
&GlibcHeapBinsDialog::showHeapInfoDialog);
|
||||
|
||||
binsModel->reload();
|
||||
ui->viewBins->resizeColumnsToContents();
|
||||
graphView = nullptr;
|
||||
this->setWindowTitle(tr("Bins info for arena @ ") + RAddressString(m_state));
|
||||
}
|
||||
|
||||
GlibcHeapBinsDialog::~GlibcHeapBinsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void GlibcHeapBinsDialog::onCurrentChanged(const QModelIndex ¤t, const QModelIndex &prev)
|
||||
{
|
||||
Q_UNUSED(current);
|
||||
Q_UNUSED(prev);
|
||||
auto currentIndex = ui->viewBins->selectionModel()->currentIndex();
|
||||
setChainInfo(currentIndex.row());
|
||||
setGraphView(currentIndex.row());
|
||||
}
|
||||
|
||||
void GlibcHeapBinsDialog::setChainInfo(int index)
|
||||
{
|
||||
// get chunks for the selected bin and construct chain info string
|
||||
RzListIter *iter;
|
||||
RzHeapChunkListItem *item;
|
||||
RzList *chunks = binsModel->getChunks(index);
|
||||
QString chainInfo;
|
||||
CutterRListForeach(chunks, iter, RzHeapChunkListItem, item)
|
||||
{
|
||||
chainInfo += " → " + RAddressString(item->addr);
|
||||
}
|
||||
|
||||
// Add bin message at the end of the list
|
||||
// responsible for messages like corrupted list, double free
|
||||
QString message = binsModel->getBinMessage(index);
|
||||
if (!message.isEmpty()) {
|
||||
chainInfo += " " + message;
|
||||
}
|
||||
|
||||
ui->chainInfoEdit->setPlainText(chainInfo);
|
||||
}
|
||||
|
||||
void GlibcHeapBinsDialog::showHeapInfoDialog()
|
||||
{
|
||||
QString str = ui->lineEdit->text();
|
||||
if (!str.isEmpty()) {
|
||||
// summon glibcHeapInfoDialog box with the offset entered
|
||||
RVA offset = Core()->math(str);
|
||||
if (!offset) {
|
||||
ui->lineEdit->setText(QString());
|
||||
return;
|
||||
}
|
||||
|
||||
GlibcHeapInfoDialog dialog(offset, QString(), this);
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void GlibcHeapBinsDialog::setGraphView(int index)
|
||||
{
|
||||
if (graphView) {
|
||||
ui->horizontalLayout->removeWidget(graphView);
|
||||
delete graphView;
|
||||
}
|
||||
graphView = new HeapBinsGraphView(this, binsModel->values[index], main);
|
||||
ui->horizontalLayout->addWidget(graphView);
|
||||
graphView->refreshView();
|
||||
}
|
||||
|
||||
BinsModel::BinsModel(RVA arena_addr, QObject *parent)
|
||||
: QAbstractTableModel(parent), arena_addr(arena_addr)
|
||||
{
|
||||
}
|
||||
|
||||
void BinsModel::reload()
|
||||
{
|
||||
beginResetModel();
|
||||
clearData();
|
||||
values = Core()->getHeapBins(arena_addr);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
int BinsModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return values.size();
|
||||
}
|
||||
|
||||
int BinsModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return ColumnCount;
|
||||
}
|
||||
|
||||
QVariant BinsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= values.count())
|
||||
return QVariant();
|
||||
|
||||
const auto &item = values.at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column()) {
|
||||
case BinNumColumn:
|
||||
return item->bin_num;
|
||||
case FdColumn:
|
||||
return (item->fd == 0) ? tr("N/A") : RAddressString(item->fd);
|
||||
case BkColumn:
|
||||
return (item->bk == 0) ? tr("N/A") : RAddressString(item->bk);
|
||||
case TypeColumn:
|
||||
return tr(item->type);
|
||||
case CountColumn:
|
||||
return rz_list_length(item->chunks);
|
||||
case SizeColumn:
|
||||
return (item->size == 0) ? tr("N/A") : RHexString(item->size);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
QVariant BinsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
Q_UNUSED(orientation);
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case BinNumColumn:
|
||||
return tr("#");
|
||||
case FdColumn:
|
||||
return tr("Fd");
|
||||
case BkColumn:
|
||||
return tr("Bk");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case CountColumn:
|
||||
return tr("Chunks count");
|
||||
case SizeColumn:
|
||||
return tr("Chunks size");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section) {
|
||||
case BinNumColumn:
|
||||
return tr("Bin number in NBINS or fastbinsY array");
|
||||
case FdColumn:
|
||||
return tr("Pointer to first chunk of the bin");
|
||||
case BkColumn:
|
||||
return tr("Pointer to last chunk of the bin");
|
||||
case TypeColumn:
|
||||
return tr("Type of bin");
|
||||
case CountColumn:
|
||||
return tr("Number of chunks in the bin");
|
||||
case SizeColumn:
|
||||
return tr("Size of all chunks in the bin");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void BinsModel::clearData()
|
||||
{
|
||||
for (auto item : values) {
|
||||
rz_heap_bin_free_64(item);
|
||||
}
|
||||
}
|
||||
|
||||
RzList *BinsModel::getChunks(int index)
|
||||
{
|
||||
return values[index]->chunks;
|
||||
}
|
||||
|
||||
QString BinsModel::getBinMessage(int index)
|
||||
{
|
||||
if (values[index]->message) {
|
||||
return QString(values[index]->message);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
63
src/dialogs/GlibcHeapBinsDialog.h
Normal file
63
src/dialogs/GlibcHeapBinsDialog.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef GLIBCHEAPBINSDIALOG_H
|
||||
#define GLIBCHEAPBINSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QAbstractTableModel>
|
||||
#include "core/Cutter.h"
|
||||
#include <MainWindow.h>
|
||||
#include <HeapBinsGraphView.h>
|
||||
|
||||
namespace Ui {
|
||||
class GlibcHeapBinsDialog;
|
||||
}
|
||||
|
||||
class BinsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BinsModel(RVA arena_addr, QObject *parent = nullptr);
|
||||
enum Column {
|
||||
TypeColumn = 0,
|
||||
BinNumColumn,
|
||||
FdColumn,
|
||||
BkColumn,
|
||||
CountColumn,
|
||||
SizeColumn,
|
||||
ColumnCount
|
||||
};
|
||||
void reload();
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
void clearData();
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
RVA arena_addr = 0;
|
||||
RzList *getChunks(int index);
|
||||
QString getBinMessage(int index);
|
||||
QVector<RzHeapBin *> values;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
class GlibcHeapBinsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlibcHeapBinsDialog(RVA i, MainWindow *main, QWidget *parent);
|
||||
~GlibcHeapBinsDialog();
|
||||
void onCurrentChanged(const QModelIndex ¤t, const QModelIndex &prev);
|
||||
void setChainInfo(int index);
|
||||
void setGraphView(int index);
|
||||
private slots:
|
||||
void showHeapInfoDialog();
|
||||
|
||||
private:
|
||||
Ui::GlibcHeapBinsDialog *ui;
|
||||
RVA m_state;
|
||||
BinsModel *binsModel {};
|
||||
HeapBinsGraphView *graphView;
|
||||
MainWindow *main;
|
||||
};
|
||||
|
||||
#endif // GLIBCHEAPBINSDIALOG_H
|
56
src/dialogs/GlibcHeapBinsDialog.ui
Normal file
56
src/dialogs/GlibcHeapBinsDialog.ui
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GlibcHeapBinsDialog</class>
|
||||
<widget class="QDialog" name="GlibcHeapBinsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>883</width>
|
||||
<height>544</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableView" name="viewBins"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Chain info:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="chainInfoEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Detailed chunk info:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Enter chunk base address and press enter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -13,8 +13,13 @@ GlibcHeapInfoDialog::GlibcHeapInfoDialog(RVA offset, QString status, QWidget *pa
|
||||
this->ui->rbNMA->setEnabled(false);
|
||||
this->ui->rbPI->setEnabled(false);
|
||||
|
||||
this->setWindowTitle(QString("Chunk @ ") + RAddressString(offset)
|
||||
+ QString("(" + this->status + ")"));
|
||||
// set window title
|
||||
QString windowTitle = tr("Chunk @ ") + RAddressString(offset);
|
||||
if (!this->status.isEmpty()) {
|
||||
windowTitle += QString("(" + this->status + ")");
|
||||
}
|
||||
this->setWindowTitle(windowTitle);
|
||||
|
||||
updateFields();
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <dialogs/GlibcHeapBinsDialog.h>
|
||||
#include "GlibcHeapWidget.h"
|
||||
#include "ui_GlibcHeapWidget.h"
|
||||
#include "core/MainWindow.h"
|
||||
@ -11,6 +12,8 @@ GlibcHeapWidget::GlibcHeapWidget(MainWindow *main, QWidget *parent)
|
||||
main(main)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
viewHeap = ui->tableView;
|
||||
arenaSelectorView = ui->arenaSelector;
|
||||
|
||||
viewHeap->setFont(Config()->getFont());
|
||||
viewHeap->setModel(modelHeap);
|
||||
@ -18,11 +21,11 @@ GlibcHeapWidget::GlibcHeapWidget(MainWindow *main, QWidget *parent)
|
||||
// change the scroll mode to ScrollPerPixel
|
||||
viewHeap->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
viewHeap->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
ui->verticalLayout->addWidget(viewHeap);
|
||||
ui->verticalLayout->addWidget(arenaSelectorView);
|
||||
chunkInfoAction = new QAction(tr("Detailed Chunk Info"), this);
|
||||
viewHeap->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
chunkInfoAction = new QAction(tr("Detailed Chunk Info"), this);
|
||||
binInfoAction = new QAction(tr("Bins Info"), this);
|
||||
|
||||
connect(Core(), &CutterCore::refreshAll, this, &GlibcHeapWidget::updateContents);
|
||||
connect(Core(), &CutterCore::debugTaskStateChanged, this, &GlibcHeapWidget::updateContents);
|
||||
connect(viewHeap, &QAbstractItemView::doubleClicked, this, &GlibcHeapWidget::onDoubleClicked);
|
||||
@ -33,8 +36,11 @@ GlibcHeapWidget::GlibcHeapWidget(MainWindow *main, QWidget *parent)
|
||||
connect(viewHeap->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
&GlibcHeapWidget::onCurrentChanged);
|
||||
connect(chunkInfoAction, &QAction::triggered, this, &GlibcHeapWidget::viewChunkInfo);
|
||||
connect(binInfoAction, &QAction::triggered, this, &GlibcHeapWidget::viewBinInfo);
|
||||
connect(ui->binsButton, &QPushButton::clicked, this, &GlibcHeapWidget::viewBinInfo);
|
||||
|
||||
addressableItemContextMenu.addAction(chunkInfoAction);
|
||||
addressableItemContextMenu.addAction(binInfoAction);
|
||||
addActions(addressableItemContextMenu.actions());
|
||||
|
||||
refreshDeferrer = dynamic_cast<CutterDockWidget *>(parent)->createRefreshDeferrer(
|
||||
@ -202,3 +208,9 @@ void GlibcHeapWidget::viewChunkInfo()
|
||||
GlibcHeapInfoDialog heapInfoDialog(Core()->math(offsetString), status, this);
|
||||
heapInfoDialog.exec();
|
||||
}
|
||||
|
||||
void GlibcHeapWidget::viewBinInfo()
|
||||
{
|
||||
GlibcHeapBinsDialog heapBinsDialog(modelHeap->arena_addr, main, this);
|
||||
heapBinsDialog.exec();
|
||||
}
|
||||
|
@ -43,16 +43,18 @@ private slots:
|
||||
void customMenuRequested(QPoint pos);
|
||||
void onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void viewChunkInfo();
|
||||
void viewBinInfo();
|
||||
|
||||
private:
|
||||
void updateArenas();
|
||||
void updateChunks();
|
||||
Ui::GlibcHeapWidget *ui;
|
||||
QTableView *viewHeap = new QTableView(this);
|
||||
QComboBox *arenaSelectorView = new QComboBox(this);
|
||||
QTableView *viewHeap;
|
||||
QComboBox *arenaSelectorView;
|
||||
GlibcHeapModel *modelHeap = new GlibcHeapModel(this);
|
||||
QVector<Arena> arenas;
|
||||
QAction *chunkInfoAction;
|
||||
QAction *binInfoAction;
|
||||
AddressableItemContextMenu addressableItemContextMenu;
|
||||
RefreshDeferrer *refreshDeferrer {};
|
||||
MainWindow *main;
|
||||
|
@ -13,7 +13,34 @@
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="arenaSelector"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="binsButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>View bins info for an arena</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bins</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
214
src/widgets/HeapBinsGraphView.cpp
Normal file
214
src/widgets/HeapBinsGraphView.cpp
Normal file
@ -0,0 +1,214 @@
|
||||
#include <Configuration.h>
|
||||
#include <dialogs/GlibcHeapInfoDialog.h>
|
||||
#include "HeapBinsGraphView.h"
|
||||
|
||||
HeapBinsGraphView::HeapBinsGraphView(QWidget *parent, RzHeapBin *bin, MainWindow *main)
|
||||
: SimpleTextGraphView(parent, main), heapBin(bin)
|
||||
{
|
||||
chunkInfoAction = new QAction(tr("Detailed Chunk Info"), this);
|
||||
addressableItemContextMenu.addAction(chunkInfoAction);
|
||||
addAction(chunkInfoAction);
|
||||
|
||||
connect(chunkInfoAction, &QAction::triggered, this, &HeapBinsGraphView::viewChunkInfo);
|
||||
|
||||
enableAddresses(true);
|
||||
}
|
||||
|
||||
void HeapBinsGraphView::viewChunkInfo()
|
||||
{
|
||||
GlibcHeapInfoDialog heapInfoDialog(selectedBlock, QString(), this);
|
||||
heapInfoDialog.exec();
|
||||
}
|
||||
|
||||
void HeapBinsGraphView::loadCurrentGraph()
|
||||
{
|
||||
blockContent.clear();
|
||||
blocks.clear();
|
||||
|
||||
RzListIter *iter;
|
||||
RzHeapChunkListItem *item;
|
||||
QVector<GraphHeapChunk> chunks;
|
||||
|
||||
// if the bin is a fastbin or not
|
||||
bool fast = QString(heapBin->type) == QString("Fast");
|
||||
|
||||
// store info about the chunks in a vector for easy access
|
||||
CutterRListForeach(heapBin->chunks, iter, RzHeapChunkListItem, item)
|
||||
{
|
||||
GraphHeapChunk graphHeapChunk;
|
||||
graphHeapChunk.addr = item->addr;
|
||||
RzHeapChunkSimple *chunkInfo = Core()->getHeapChunk(item->addr);
|
||||
if (!chunkInfo) {
|
||||
break;
|
||||
}
|
||||
QString content = "Chunk @ " + RAddressString(chunkInfo->addr) + "\nSize: "
|
||||
+ RHexString(chunkInfo->size) + "\nFd: " + RAddressString(chunkInfo->fd);
|
||||
|
||||
// fastbins lack bk pointer
|
||||
if (!fast) {
|
||||
content += "\nBk: " + RAddressString(chunkInfo->bk);
|
||||
}
|
||||
graphHeapChunk.fd = chunkInfo->fd;
|
||||
graphHeapChunk.bk = chunkInfo->bk;
|
||||
graphHeapChunk.content = content;
|
||||
chunks.append(graphHeapChunk);
|
||||
free(chunkInfo);
|
||||
}
|
||||
|
||||
// fast bins have single linked list and other bins have double linked list
|
||||
if (fast) {
|
||||
display_single_linked_list(chunks);
|
||||
} else {
|
||||
display_double_linked_list(chunks);
|
||||
}
|
||||
|
||||
cleanupEdges(blocks);
|
||||
computeGraphPlacement();
|
||||
}
|
||||
|
||||
void HeapBinsGraphView::display_single_linked_list(QVector<GraphHeapChunk> chunks)
|
||||
{
|
||||
// add the graph block for the bin
|
||||
GraphLayout::GraphBlock gbBin;
|
||||
gbBin.entry = 1;
|
||||
gbBin.edges.emplace_back(heapBin->fd);
|
||||
QString content = tr(heapBin->type) + tr("bin ") + QString::number(heapBin->bin_num);
|
||||
content += "\nFd: " + RAddressString(heapBin->fd);
|
||||
addBlock(gbBin, content);
|
||||
|
||||
// add the graph blocks for the chunks
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
GraphLayout::GraphBlock gbChunk;
|
||||
gbChunk.entry = chunks[i].addr;
|
||||
gbChunk.edges.emplace_back(chunks[i].fd);
|
||||
|
||||
if (i == chunks.size() - 1 && heapBin->message) {
|
||||
chunks[i].content += "\n" + QString(heapBin->message);
|
||||
}
|
||||
|
||||
addBlock(gbChunk, chunks[i].content, chunks[i].addr);
|
||||
}
|
||||
|
||||
// add the END block if no message
|
||||
if (!heapBin->message) {
|
||||
GraphLayout::GraphBlock gbEnd;
|
||||
gbEnd.entry = 0;
|
||||
addBlock(gbEnd, "END", 0);
|
||||
}
|
||||
}
|
||||
|
||||
void HeapBinsGraphView::display_double_linked_list(QVector<GraphHeapChunk> chunks)
|
||||
{
|
||||
// add the graph block for the bin
|
||||
GraphLayout::GraphBlock gbBin;
|
||||
gbBin.entry = heapBin->addr;
|
||||
gbBin.edges.emplace_back(heapBin->fd);
|
||||
gbBin.edges.emplace_back(heapBin->bk);
|
||||
QString content = tr(heapBin->type) + tr("bin ") + QString::number(heapBin->bin_num) + tr(" @ ")
|
||||
+ RAddressString(heapBin->addr);
|
||||
content += "\nFd: " + RAddressString(heapBin->fd);
|
||||
content += "\nBk: " + RAddressString(heapBin->bk);
|
||||
|
||||
addBlock(gbBin, content, heapBin->addr);
|
||||
|
||||
// add the blocks for the chunks
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
GraphLayout::GraphBlock gbChunk;
|
||||
gbChunk.entry = chunks[i].addr;
|
||||
gbChunk.edges.emplace_back(chunks[i].fd);
|
||||
gbChunk.edges.emplace_back(chunks[i].bk);
|
||||
|
||||
// if last chunk and there is message then show it in the chunk
|
||||
if (i == chunks.size() - 1 && heapBin->message) {
|
||||
chunks[i].content += "\n" + QString(heapBin->message);
|
||||
}
|
||||
|
||||
addBlock(gbChunk, chunks[i].content, chunks[i].addr);
|
||||
}
|
||||
}
|
||||
|
||||
// overriding this function from SimpleTextGraphView to support multiline text in graph block
|
||||
// most code is shared from that implementation
|
||||
void HeapBinsGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
|
||||
{
|
||||
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.setBrush(Qt::gray);
|
||||
p.setFont(Config()->getFont());
|
||||
p.drawRect(blockRect);
|
||||
|
||||
// Render node
|
||||
auto &content = blockContent[block.entry];
|
||||
|
||||
p.setPen(QColor(0, 0, 0, 0));
|
||||
p.setBrush(QColor(0, 0, 0, 100));
|
||||
p.setPen(QPen(graphNodeColor, 1));
|
||||
|
||||
bool blockSelected = interactive && (block.entry == selectedBlock);
|
||||
if (blockSelected) {
|
||||
p.setBrush(disassemblySelectedBackgroundColor);
|
||||
} else {
|
||||
p.setBrush(disassemblyBackgroundColor);
|
||||
}
|
||||
// Draw basic block background
|
||||
p.drawRect(blockRect);
|
||||
|
||||
// Stop rendering text when it's too small
|
||||
auto transform = p.combinedTransform();
|
||||
QRect screenChar = transform.mapRect(QRect(0, 0, ACharWidth, charHeight));
|
||||
|
||||
if (screenChar.width() < Config()->getGraphMinFontSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.setPen(palette().color(QPalette::WindowText));
|
||||
|
||||
// Render node text
|
||||
// the only change from SimpleTextGraphView implementation
|
||||
p.drawText(blockRect, Qt::AlignCenter, content.text);
|
||||
}
|
||||
|
||||
// overriding this function to support multiline text in graph blocks
|
||||
void HeapBinsGraphView::addBlock(GraphLayout::GraphBlock block, const QString &text, RVA address)
|
||||
{
|
||||
auto &content = blockContent[block.entry];
|
||||
content.text = text;
|
||||
content.address = address;
|
||||
|
||||
int height = 1;
|
||||
double width = 0;
|
||||
|
||||
// split text into different lines
|
||||
auto lines = text.split(QRegExp("[\n]"), QString::SkipEmptyParts);
|
||||
|
||||
// width of the block is the maximum width of a line
|
||||
for (QString &line : lines) {
|
||||
width = std::max(mFontMetrics->width(line), width);
|
||||
}
|
||||
block.width = static_cast<int>(width + padding);
|
||||
block.height = (height * charHeight) * lines.length() + padding;
|
||||
GraphView::addBlock(std::move(block));
|
||||
}
|
||||
|
||||
// overriding to support detailed heap info action in context menu
|
||||
void HeapBinsGraphView::blockContextMenuRequested(GraphView::GraphBlock &block,
|
||||
QContextMenuEvent *event, QPoint /*pos*/)
|
||||
{
|
||||
if (haveAddresses) {
|
||||
const auto &content = blockContent[block.entry];
|
||||
selectedBlock = content.address;
|
||||
addressableItemContextMenu.setTarget(content.address, content.text);
|
||||
QPoint pos = event->globalPos();
|
||||
|
||||
if (event->reason() != QContextMenuEvent::Mouse) {
|
||||
QPoint blockPosition(block.x + block.width / 2, block.y + block.height / 2);
|
||||
blockPosition = logicalToViewCoordinates(blockPosition);
|
||||
if (viewport()->rect().contains(blockPosition)) {
|
||||
pos = mapToGlobal(blockPosition);
|
||||
}
|
||||
}
|
||||
addressableItemContextMenu.exec(pos);
|
||||
event->accept();
|
||||
}
|
||||
}
|
38
src/widgets/HeapBinsGraphView.h
Normal file
38
src/widgets/HeapBinsGraphView.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef CUTTER_HEAPBINSGRAPHVIEW_H
|
||||
#define CUTTER_HEAPBINSGRAPHVIEW_H
|
||||
#include "SimpleTextGraphView.h"
|
||||
|
||||
class HeapBinsGraphView : public SimpleTextGraphView
|
||||
{
|
||||
Q_OBJECT
|
||||
struct GraphHeapChunk
|
||||
{
|
||||
QString content;
|
||||
ut64 addr;
|
||||
ut64 fd;
|
||||
ut64 bk;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit HeapBinsGraphView(QWidget *parent, RzHeapBin *bin, MainWindow *main);
|
||||
|
||||
protected:
|
||||
void loadCurrentGraph() override;
|
||||
void drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive) override;
|
||||
void addBlock(GraphLayout::GraphBlock block, const QString &text,
|
||||
RVA address = RVA_INVALID) override;
|
||||
void blockContextMenuRequested(GraphView::GraphBlock &block, QContextMenuEvent *event,
|
||||
QPoint) override;
|
||||
|
||||
private slots:
|
||||
void viewChunkInfo();
|
||||
|
||||
private:
|
||||
RzHeapBin *heapBin;
|
||||
void display_single_linked_list(QVector<GraphHeapChunk>);
|
||||
void display_double_linked_list(QVector<GraphHeapChunk>);
|
||||
QAction *chunkInfoAction;
|
||||
RVA selectedBlock;
|
||||
};
|
||||
|
||||
#endif // CUTTER_HEAPBINSGRAPHVIEW_H
|
@ -55,7 +55,8 @@ protected:
|
||||
* Needs to cleanup the old graph and use addBlock() to create new nodes.
|
||||
*/
|
||||
virtual void loadCurrentGraph() = 0;
|
||||
void addBlock(GraphLayout::GraphBlock block, const QString &content, RVA address = RVA_INVALID);
|
||||
virtual void addBlock(GraphLayout::GraphBlock block, const QString &content,
|
||||
RVA address = RVA_INVALID);
|
||||
/**
|
||||
* @brief Enable or disable address interactions for nodes.
|
||||
* If enabled node addresses need to be specified when calling addBlock(). Adds address related
|
||||
|
Loading…
Reference in New Issue
Block a user