]> git.siccegge.de Git - frida/frida.git/blob - src/gui/widgets/BasicBlockWidget.cxx
Highlight jumptargets
[frida/frida.git] / src / gui / widgets / BasicBlockWidget.cxx
1 #include "BasicBlockWidget.hxx"
2 #include "CFGScene.hxx"
3 #include "gui/Mainwindow.hxx"
4 #include "gui/dialogs/SimpleStringDialog.hxx"
5 #include "core/BasicBlock.hxx"
6 #include "core/Function.hxx"
7 #include "core/Comment.hxx"
8 #include "disassembler/Instruction.hxx"
9 #include "core/InformationManager.hxx"
10 #include "core/events/RenameFunctionEvent.hxx"
11 #include "core/events/ChangeCommentEvent.hxx"
12 #include <algorithm>
13
14 class CustomQGraphicsTextItem : public QObject, public QGraphicsTextItem {
15 public:
16 CustomQGraphicsTextItem(const QString& text, BasicBlockWidget* parent)
17 : QGraphicsTextItem(text, parent), parent(parent) {}
18 void contextMenuEvent(QGraphicsSceneContextMenuEvent*);
19
20 void adjustSize();
21 private:
22 void addComment(int row, bool global);
23
24 BasicBlockWidget* parent;
25 };
26
27 void CustomQGraphicsTextItem::addComment(int row, bool global) {
28 SimpleStringDialog dialog(global ? "Global comment" : "Local comment");
29 int result = dialog.exec();
30 uint64_t address = parent->instructions[row].getAddress();
31 if (QDialog::Accepted == result) {
32 Comment* comment;
33 if (global) {
34 comment = parent->block->getManager()->newGlobalComment(address);
35 } else {
36 /* TODO: 0x23 as we currently don't have the function here
37 * and setting it to null will make the comment appear
38 * global. Also means local comments are largely still
39 * broken.
40 */
41 comment = parent->block->getManager()->newLocalComment(address, (Function*)0x23);
42 }
43 comment->setText(dialog.result().toStdString());
44 parent->block->getManager()->finishComment(comment);
45 } else {
46 LOG4CXX_DEBUG(parent->logger, "addComment aborted");
47 }
48 }
49
50 void CustomQGraphicsTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
51 QTextCursor c = textCursor();
52 c.setPosition(document()->documentLayout()->hitTest(event->pos(), Qt::FuzzyHit));
53 c.select(QTextCursor::WordUnderCursor);
54
55 QMenu menu;
56 bool ok;
57 uint64_t address = c.selectedText().toLongLong(&ok, 16);
58 if (ok) {
59 QAction* act = menu.addAction(c.selectedText() + " is a Function");
60 QObject::connect(act, &QAction::triggered,
61 [=]() {parent->mainwindow->requestNewFunctionByAddress(address);});
62 }
63
64 QTextTable* table = c.currentTable();
65 if (NULL != table) {
66 int row = table->cellAt(c).row();
67 QAction* globalComment = menu.addAction("Add global Comment");
68 QAction* localComment = menu.addAction("Add local Comment");
69
70 QObject::connect(globalComment, &QAction::triggered,
71 [=]() { addComment(row, true); });
72 QObject::connect(localComment, &QAction::triggered,
73 [=]() { addComment(row, false); });
74 }
75
76 menu.exec(event->screenPos());
77 }
78
79 /* QGraphicsTextItem has an adjustSize() function that is supposed to
80 * resize the widget to it's "ideal" size. However it totally ignores
81 * all directives to not wrap lines and "ideal" is actually just a
82 * bunch of heuristics.
83 *
84 * We are starting with a hopefully absurdly large startingwidth and
85 * reduce it untill a line is broken (detected by a change in
86 * height). As long as the width (1000 here) is sufficiently large,
87 * this should give us a widget without any line-wrapping.
88 *
89 * One needs to call this on a Pointer of tye CustomQGraphicsTextItem
90 * as the adjustSize() function is not polymorphic (vurtual).
91 */
92 void CustomQGraphicsTextItem::adjustSize() {
93 int width = 1000;
94 setTextWidth(width);
95 int height = boundingRect().height();
96 while (width > 250 && height == boundingRect().height()) {
97 setTextWidth(width -= 10);
98 }
99 width += 10;
100 if (width < 250) width = 250;
101 setTextWidth(width);
102 }
103
104 BasicBlockWidget::BasicBlockWidget(const QString& name, BasicBlock * block,
105 Mainwindow * mainwindow)
106 : width(200), height(45), name(name)
107 , currentColor(defaultColor), _table(NULL)
108 , block(block), mainwindow(mainwindow)
109 , logger(log4cxx::Logger::getLogger("gui.BasicBlockWidget." + name.toStdString())) {
110 next[0] = NULL; next[1] = NULL;
111
112 block->getManager()->registerRenameFunctionEvent([=](RenameFunctionEvent* event) {updateFunctionName(event);});
113
114 _widget.reset(new CustomQGraphicsTextItem("", this));
115 _widget->setPos(5, 20);
116 _widget->setTextInteractionFlags(Qt::TextSelectableByMouse|
117 Qt::LinksAccessibleByMouse);
118
119 if (width < 250) width = 250;
120
121 QObject::connect(_widget.get(), &QGraphicsTextItem::linkActivated,
122 [=](QString str) {
123 if (str.startsWith("function:")) {
124 QString address = str.remove("function:");
125 mainwindow->switchMainPlaneToAddress(address.toInt(NULL, 16));
126 } else if (str.startsWith("block:")) {
127 QString address = str.remove("block:");
128
129 /* next[0] is always the jumptarget. On a
130 * conditional jump, next[1] also
131 * contains the following instruction
132 *
133 * TODO: Verify we're switching to the
134 * right block -- the target
135 * address matches the next blocks
136 * start address
137 */
138 LOG4CXX_TRACE(logger, "Highlighting block at Address " << address.toStdString()
139 << " BasicBlockWidget " << std::hex << next[0]);
140 ((CFGScene*)this->scene())->highlightBlock(next[0]);
141 }
142 });
143 instructions = block->getInstructions();
144 populateWidget();
145 block->getManager()->registerChangeCommentEvent([=](ChangeCommentEvent* e) {changeCommentHandler(e);});
146 }
147
148 void BasicBlockWidget::updateFunctionName(RenameFunctionEvent *event) {
149 QString search = QString("function:") + QString::number(event->address, 16);
150 QTextDocument *document = _widget->document();
151 QTextBlock b = document->begin();
152 while (b.isValid()) {
153 for (QTextBlock::iterator i = b.begin(); !i.atEnd(); ++i) {
154 QTextCharFormat format = i.fragment().charFormat();
155 bool isLink = format.isAnchor();
156 if (isLink)
157 {
158 if (search == format.anchorHref()) {
159 LOG4CXX_DEBUG(logger, i.fragment().text().toStdString() << " ---> "
160 << format.anchorHref().toStdString());
161
162 /* This should select the function name. It stars
163 * by selecting the whole link fragment from back
164 * to front and then moves one word to the back
165 * again deselecting whatever mnemonic is used for
166 * the call instruction.
167 */
168 QTextCursor c(b);
169 c.setPosition(i.fragment().position());
170 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, i.fragment().length());
171 c.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i.fragment().length());
172 c.movePosition(QTextCursor::WordRight, QTextCursor::KeepAnchor);
173 c.insertText(event->new_name.c_str());
174
175 QGraphicsTextItem* item = _widget.get();
176 ((CustomQGraphicsTextItem*)item)->adjustSize();
177 }
178 }
179 }
180 b = b.next();
181 }
182 }
183
184 void BasicBlockWidget::changeCommentHandler(ChangeCommentEvent* event) {
185 auto inst_it = std::find_if(instructions.begin(), instructions.end(),
186 [=](Instruction& inst) {
187 return inst.getAddress() == event->address;
188 });
189 if (inst_it != instructions.end()) {
190 if (std::find(inst_it->comments().begin(),
191 inst_it->comments().begin(),
192 event->comment) == inst_it->comments().end()) {
193 LOG4CXX_DEBUG(logger, "Change Comment Event -- New Comment!");
194 inst_it->comments().push_back(event->comment);
195 }
196 int row = inst_it - instructions.begin();
197 LOG4CXX_DEBUG(logger, "Inserting comment for instruction at row " << std::hex << row);
198 QTextCursor cursor = _table->cellAt(row, 2).lastCursorPosition();
199 while (cursor != _table->cellAt(row, 2).firstCursorPosition()) {
200 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
201 }
202 cursor.removeSelectedText();
203 cursor.insertHtml(formatComments(&*inst_it));
204 QGraphicsTextItem* item = _widget.get();
205 ((CustomQGraphicsTextItem*)item)->adjustSize();
206 }
207 }
208
209 void BasicBlockWidget::populateWidget() {
210 int row;
211 QTextTableFormat format;
212 format.setBorderStyle(QTextFrameFormat::BorderStyle_None);
213 format.setBorder(0);
214
215 for (Instruction& inst : instructions) {
216 if (_table) {
217 row = _table->rows();
218 _table->appendRows(1);
219 } else {
220 row = 0;
221 _table = _widget->textCursor().insertTable(1, 3, format);
222 }
223 QString bytestring;
224 for (uint8_t byte : inst.getBytes()) {
225 const char * hexdigits = "0123456789ABCDEF";
226 bytestring += hexdigits[(byte >> 4) & 0xF];
227 bytestring += hexdigits[byte & 0xF];
228 bytestring += ' ';
229 }
230 _table->cellAt(row, 0).firstCursorPosition().insertHtml("<nobr>" + bytestring + "</nobr>");
231
232 QString line = inst.getText().c_str();
233 line = line.replace('\t', ' ').toHtmlEscaped();
234 if (inst.getReference() != "") {
235 QString href = inst.getReference().c_str();
236 QStringList list = href.split(":");
237 if (list[0] == "function") {
238 uint64_t address = href.split(":")[1].toLongLong(NULL, 16);
239 Function* fun = block->getManager()->getFunction(address);
240
241 if (fun) {
242 line = line.split(" ")[0] + " " + QString(fun->getName().c_str()).toHtmlEscaped();
243 LOG4CXX_DEBUG(logger, "Naming function at " << address << " " << fun->getName());
244 }
245 }
246 line = "<a href=\"" + href + "\">" + line + "</a>";
247 }
248 _table->cellAt(row, 1).firstCursorPosition().insertHtml("<nobr>" + line + "</nobr>");
249 _table->cellAt(row, 2).firstCursorPosition().insertHtml(formatComments(&inst));
250 }
251 QGraphicsTextItem* item = _widget.get();
252 ((CustomQGraphicsTextItem*)item)->adjustSize();
253 }
254
255 QString BasicBlockWidget::formatComments(Instruction* inst) {
256 QString comments;
257 for (Comment* c: inst->comments()) {
258 comments += "<br />";
259 comments += QString(c->getText().c_str()).toHtmlEscaped();
260 }
261 return (comments == "" ? "" : ";; ") + comments.trimmed();
262 }
263
264 void BasicBlockWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem*,
265 QWidget*) {
266 width = 10 + _widget->boundingRect().width();
267 height = 25 + _widget->boundingRect().height();
268 if (width < 250) width = 250;
269
270 painter->fillRect(0, 0, width, height, currentColor);
271 painter->setPen(QColor(0x00, 0x00, 0xff, 0xff));
272 painter->drawRect(0, 0, width, height);
273 painter->drawText(5, 15, name);
274 }
275
276 QRectF BasicBlockWidget::boundingRect() const {
277 qreal penWidth = 1;
278 QRectF result(- penWidth / 2, - penWidth / 2,
279 width + penWidth, height + penWidth);
280 return result;
281 }
282
283 std::array<QPointF, 3> BasicBlockWidget::getExits() const {
284 return { { mapToScene(QPointF( width/3, height)),
285 mapToScene(QPointF( width/2, height)),
286 mapToScene(QPointF(2*width/3, height)) } };
287 }
288