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 }