File indexing completed on 2024-05-12 05:43:34

0001 /*
0002     Copyright (C) 2015 Volker Krause <vkrause@kde.org>
0003 
0004     This program is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program.  If not, see <https://www.gnu.org/licenses/>.
0016 */
0017 
0018 #include "sizetreemapview.h"
0019 #include "ui_sizetreemapview.h"
0020 
0021 #include <treemap/treemap.h>
0022 #include <colorizer.h>
0023 #include <elfmodel/elfmodel.h>
0024 #include <elfmodel/sectionproxymodel.h>
0025 
0026 #include <elf/elffile.h>
0027 #include <elf/elfsymboltablesection.h>
0028 #include <demangle/demangler.h>
0029 
0030 #include <QMenu>
0031 #include <QSettings>
0032 
0033 #include <elf.h>
0034 
0035 SizeTreeMapView::SizeTreeMapView(QWidget* parent) :
0036     QWidget(parent),
0037     ui(new Ui::SizeTreeMapView),
0038     m_sectionProxy(new SectionProxyModel(this))
0039 {
0040     ui->setupUi(this);
0041 
0042     ui->sectionView->setModel(m_sectionProxy);
0043     m_sectionProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
0044     connect(ui->searchLineEdit, &QLineEdit::textChanged, this, [this](const QString &text) {
0045         m_sectionProxy->setFilterFixedString(text);
0046     });
0047 
0048     ui->actionHideDebugInformation->setData("HideDebugInfo");
0049     ui->actionHideOccupiesMemory->setData("HideOccupiesNoMemory");
0050     ui->actionHideNonWritable->setData("HideNonWritable");
0051     ui->actionHideNonExecutable->setData("HideNonExecutable");
0052 
0053     ui->actionNoColorization->setData("NoColorize");
0054     ui->actionColorizeSections->setData("ColorizeSections");
0055     ui->actionColorizeSymbols->setData("ColorizeSymbols");
0056     ui->actionRelocationHeatmap->setData("RelocationHeatmap");
0057 
0058     auto colorizeGroup = new QActionGroup(this);
0059     colorizeGroup->setExclusive(true);
0060     colorizeGroup->addAction(ui->actionNoColorization);
0061     colorizeGroup->addAction(ui->actionColorizeSections);
0062     colorizeGroup->addAction(ui->actionColorizeSymbols);
0063     colorizeGroup->addAction(ui->actionRelocationHeatmap);
0064 
0065     auto separator = new QAction(this);
0066     separator->setSeparator(true);
0067     addActions({
0068         ui->actionHideDebugInformation,
0069         ui->actionHideOccupiesMemory,
0070         ui->actionHideNonWritable,
0071         ui->actionHideNonExecutable,
0072         separator,
0073         ui->actionNoColorization,
0074         ui->actionColorizeSections,
0075         ui->actionColorizeSymbols,
0076         ui->actionRelocationHeatmap
0077     });
0078 
0079     foreach (auto action, actions())
0080         connect(action, &QAction::toggled, this, &SizeTreeMapView::viewActionToggled);
0081 
0082     restoreSettings();
0083 }
0084 
0085 SizeTreeMapView::~SizeTreeMapView() = default;
0086 
0087 static double relocRatio(ElfSymbolTableEntry *symbol)
0088 {
0089     if (symbol->size() <= 0)
0090         return 0;
0091     return (double)(symbol->symbolTable()->file()->reverseRelocator()->relocationCount(symbol->value(), symbol->size()) * symbol->symbolTable()->file()->addressSize()) / symbol->size();
0092 }
0093 
0094 static double relocRatio(ElfSectionHeader *shdr)
0095 {
0096     if (shdr->size() <= 0)
0097         return 0;
0098     return (double)(shdr->file()->reverseRelocator()->relocationCount(shdr->virtualAddress(), shdr->size()) * shdr->file()->addressSize()) / shdr->size();
0099 }
0100 
0101 static QColor relocColor(double ratio)
0102 {
0103     return QColor(
0104         std::min(255.0, ratio * 510),
0105         std::min(255.0, (1-ratio) * 510),
0106         0
0107     );
0108 }
0109 
0110 void SizeTreeMapView::setModel(QAbstractItemModel* model)
0111 {
0112     m_sectionProxy->setSourceModel(model);
0113     ui->sectionView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
0114 
0115     connect(ui->sectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this]() {
0116         m_viewDirty = true;
0117         reloadTreeMap();
0118     });
0119 }
0120 
0121 void SizeTreeMapView::reloadTreeMap()
0122 {
0123     if (!m_viewDirty)
0124         return;
0125     m_viewDirty = false;
0126 
0127     const auto selection = ui->sectionView->selectionModel()->selectedRows();
0128     if (selection.isEmpty())
0129         return;
0130 
0131     const auto idx = selection.first();
0132     auto file = idx.data(ElfModel::FileRole).value<ElfFile*>();
0133     const auto section = idx.data(ElfModel::SectionRole).value<ElfSection*>();
0134     if (!file && section)
0135         file = section->file();
0136     if (!file)
0137         return;
0138 
0139     // TODO inefficient and leacky...
0140     auto baseItem = new TreeMapItem;
0141     baseItem->setText(0, file->displayName());
0142     baseItem->setSum(file->size());
0143 
0144     QList<int> prevSplitterSize;
0145     if (m_treeMap)
0146         prevSplitterSize = ui->splitter->sizes();
0147 
0148     delete m_treeMap;
0149     m_treeMap = new TreeMapWidget(baseItem, this);
0150 
0151     m_treeMap->setBorderWidth(3);
0152 //     m_treeMap->setMinimalArea(200);
0153 //     m_treeMap->setVisibleWidth(10, false);
0154     // looks weird, but this forces m_treeMap->_attrs to be resided correctly for text to be drawn
0155     m_treeMap->setFieldForced(1, true);
0156     m_treeMap->setFieldForced(1, false);
0157     m_treeMap->setContextMenuPolicy(Qt::CustomContextMenu);
0158     connect(m_treeMap, &TreeMapWidget::customContextMenuRequested, this, &SizeTreeMapView::treeMapContextMenu);
0159 
0160     ui->splitter->addWidget(m_treeMap);
0161     if (!prevSplitterSize.isEmpty())
0162         ui->splitter->setSizes(prevSplitterSize);
0163     else
0164         ui->splitter->setSizes({ ui->sectionView->header()->sectionSize(0), width() - ui->sectionView->header()->sectionSize(0) });
0165 
0166     QSettings settings;
0167     m_treeMap->setSplitMode(settings.value(QStringLiteral("TreeMap/SplitMode"), "Bisection").toString());
0168 
0169     struct SymbolNode {
0170         TreeMapItem *item;
0171         QHash<QByteArray, SymbolNode*> children;
0172     };
0173 
0174     QVector<SymbolNode*> sectionItems;
0175     sectionItems.resize(file->sectionHeaders().size());
0176 
0177     if (!section) {
0178         Colorizer sectionColorizer(96, 255);
0179         foreach (const auto shdr, file->sectionHeaders()) {
0180             if (isSectionHidden(shdr)) {
0181                 baseItem->setSum(baseItem->sum() - shdr->size());
0182                 continue;
0183             }
0184             auto item = new TreeMapItem(baseItem, shdr->size(), shdr->name(), QString::number(shdr->size()));
0185             item->setSum(shdr->size());
0186             if (ui->actionColorizeSections->isChecked())
0187                 item->setBackColor(sectionColorizer.nextColor());
0188             if (ui->actionRelocationHeatmap->isChecked() && shdr->flags() & SHF_WRITE)
0189                 item->setBackColor(relocColor(relocRatio(shdr)));
0190             sectionItems[shdr->sectionIndex()] = new SymbolNode;
0191             sectionItems[shdr->sectionIndex()]->item = item;
0192         }
0193     } else {
0194         baseItem->setSum(section->header()->size());
0195         auto item = new TreeMapItem(baseItem, section->header()->size(), section->header()->name(), QString::number(section->header()->size()));
0196         item->setSum(section->header()->size());
0197         if (ui->actionRelocationHeatmap->isChecked() && section->header()->flags() & SHF_WRITE)
0198             item->setBackColor(relocColor(relocRatio(section->header())));
0199         sectionItems[section->header()->sectionIndex()] = new SymbolNode;
0200         sectionItems[section->header()->sectionIndex()]->item = item;
0201     }
0202 
0203     Colorizer symbolColorizer;
0204     Demangler demangler;
0205 
0206     const auto symtab = file->symbolTable();
0207     if (symtab) {
0208         for (unsigned int j = 0; j < symtab->header()->entryCount(); ++j) {
0209             const auto entry = symtab->entry(j);
0210             if (entry->size() == 0 || !sectionItems.at(entry->sectionIndex()))
0211                 continue;
0212             SymbolNode *parentNode = sectionItems.at(entry->sectionIndex());
0213             const QVector<QByteArray> demangledNames = demangler.demangle(entry->name());
0214             for (const QByteArray &demangledName : demangledNames) {
0215                 SymbolNode *node = parentNode->children.value(demangledName);
0216                 if (!node) {
0217                     node = new SymbolNode;
0218                     node->item = new TreeMapItem(parentNode->item);
0219                     node->item->setField(0, demangledName);
0220                     if (ui->actionColorizeSymbols->isChecked() && parentNode->item->parent() == baseItem) {
0221                         node->item->setBackColor(symbolColorizer.nextColor());
0222                     } else {
0223                         node->item->setBackColor(parentNode->item->backColor());
0224                     }
0225                     parentNode->children.insert(demangledName, node);
0226                 }
0227                 node->item->setSum(node->item->sum() + entry->size());
0228                 node->item->setValue(node->item->sum());
0229                 node->item->setField(1, QString::number(node->item->sum()));
0230                 parentNode = node;
0231             }
0232             if (ui->actionRelocationHeatmap->isChecked() && entry->sectionHeader()->flags() & SHF_WRITE)
0233                 parentNode->item->setBackColor(relocColor(relocRatio(entry)));
0234         }
0235     }
0236 
0237     baseItem->setSorting(-2, true, true); // sort recursively by value
0238 }
0239 
0240 void SizeTreeMapView::treeMapContextMenu(const QPoint& pos)
0241 {
0242     QMenu menu;
0243     m_treeMap->addSplitDirectionItems(&menu);
0244     menu.exec(m_treeMap->mapToGlobal(pos));
0245 
0246     QSettings settings;
0247     settings.setValue(QStringLiteral("TreeMap/SplitMode"), m_treeMap->splitModeString());
0248 }
0249 
0250 static void readCheckedState(QAction *action, bool def)
0251 {
0252     const auto data = action->data().toString();
0253     if (data.isEmpty())
0254         return;
0255     QSettings settings;
0256     settings.beginGroup(QStringLiteral("View"));
0257     action->setChecked(settings.value(data, def).toBool());
0258 }
0259 
0260 void SizeTreeMapView::restoreSettings()
0261 {
0262     readCheckedState(ui->actionHideDebugInformation, true);
0263     readCheckedState(ui->actionHideOccupiesMemory, false);
0264     readCheckedState(ui->actionHideNonExecutable, false);
0265     readCheckedState(ui->actionHideNonWritable, false);
0266 
0267     readCheckedState(ui->actionNoColorization, false);
0268     readCheckedState(ui->actionColorizeSections, false);
0269     readCheckedState(ui->actionColorizeSymbols, true);
0270     readCheckedState(ui->actionRelocationHeatmap, false);
0271 }
0272 
0273 void SizeTreeMapView::viewActionToggled()
0274 {
0275     const auto action = qobject_cast<QAction*>(sender());
0276     if (!action)
0277         return;
0278 
0279     const auto data = action->data().toString();
0280     if (data.isEmpty())
0281         return;
0282     QSettings settings;
0283     settings.beginGroup(QStringLiteral("View"));
0284     settings.setValue(data, action->isChecked());
0285 
0286     // compress subsequent calls
0287     m_viewDirty = true;
0288     QMetaObject::invokeMethod(this, "reloadTreeMap", Qt::QueuedConnection);
0289 }
0290 
0291 bool SizeTreeMapView::isSectionHidden(ElfSectionHeader* shdr) const
0292 {
0293     if (ui->actionHideDebugInformation->isChecked() && shdr->isDebugInformation())
0294         return true;
0295     if (ui->actionHideOccupiesMemory->isChecked() && !(shdr->flags() & SHF_ALLOC))
0296         return true;
0297     if (ui->actionHideNonWritable->isChecked() && !(shdr->flags() & SHF_WRITE))
0298         return true;
0299     if (ui->actionHideNonExecutable->isChecked() && !(shdr->flags() & SHF_EXECINSTR))
0300         return true;
0301     return false;
0302 }