File: DialogBacktrace.cpp

package info (click to toggle)
edb-debugger 1.3.0-2.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,124 kB
  • sloc: cpp: 46,241; xml: 4,998; ansic: 3,088; sh: 52; asm: 33; makefile: 5
file content (263 lines) | stat: -rw-r--r-- 7,896 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/*
Copyright (C) 2015	Armen Boursalian
					aboursalian@gmail.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "DialogBacktrace.h"
#include "CallStack.h"
#include "IBreakpoint.h"
#include "IDebugger.h"
#include "IProcess.h"
#include "ISymbolManager.h"
#include "Symbol.h"

#include <QPushButton>
#include <QTableWidget>

namespace BacktracePlugin {
namespace {

// Default values in the table
constexpr int FirstRow     = 0;
constexpr int ReturnColumn = 1;

/**
 * @brief address_from_table
 * @param item
 * @return the edb::address_t represented by the given *item and sets *ok to
 * true if successful or false, otherwise.
 */
edb::address_t address_from_table(const QTableWidgetItem *item) {
	return static_cast<edb::address_t>(item->data(Qt::UserRole).value<qulonglong>());
}

/**
 * @brief is_ret
 * @param column
 * @return true if the column number is the one dedicated to return addresses.
 * otherwise, false.
 */
bool is_ret(int column) {
	return column == ReturnColumn;
}

/**
 * @brief is_ret
 * @param item
 * @return true if the selected item is in the column for return addresses.
 * otherwise, false.
 */
bool is_ret(const QTableWidgetItem *item) {
	if (!item) {
		return false;
	}

	return is_ret(item->column());
}

}

/**
 * @brief DialogBacktrace::DialogBacktrace
 *
 * Initializes the Dialog with its QTableWidget.  This class over all is
 * designed to analyze the stack for return addresses to show the user the
 * runtime call stack.  The user can double-click addresses to go to them in
 * the CPU Disassembly view or click "Run To Return" on return addresses to
 * return to those addresses.  The first item on the stack should be the
 * current RIP/PC, and "Run To Return" should do a "Step Out"
 * (the behavior for the 1st row should be different than all others.
 *
 * @param parent
 * @param f
 */
DialogBacktrace::DialogBacktrace(QWidget *parent, Qt::WindowFlags f)
	: QDialog(parent, f) {

	ui.setupUi(this);

	table_ = ui.tableWidgetCallStack;
	table_->verticalHeader()->hide();
	table_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);

	buttonReturnTo_ = new QPushButton(QIcon::fromTheme("edit-undo"), tr("Return To"));
	connect(buttonReturnTo_, &QPushButton::clicked, this, [this]() {
		// Desc: Ensures that the selected item is a return address.  If so, sets a
		//       breakpoint at that address and continues execution.

		//Make sure our current item is in the RETURN_COLUMN
		QTableWidgetItem *item = table_->currentItem();
		if (!is_ret(item)) {
			return;
		}

		edb::address_t address = address_from_table(item);

		if (IProcess *process = edb::v1::debugger_core->process()) {

			// Now that we got the address, we can run.  First check if bp @ that address
			// already exists.
			if (std::shared_ptr<IBreakpoint> bp = edb::v1::debugger_core->findBreakpoint(address)) {
				process->resume(edb::DEBUG_CONTINUE);
				return;
			}

			// Using the non-debugger_core version ensures bp is set in a valid region
			// TODO(eteran): I think it's safe to just use the return value of create_breakpoint here...
			edb::v1::create_breakpoint(address);
			if (std::shared_ptr<IBreakpoint> bp = edb::v1::debugger_core->findBreakpoint(address)) {
				bp->setInternal(true);
				bp->setOneTime(true);
				process->resume(edb::DEBUG_CONTINUE);
			}
		}
	});

	ui.buttonBox->addButton(buttonReturnTo_, QDialogButtonBox::ActionRole);
}

/**
 * @brief DialogBacktrace::showEvent
 *
 * Ensures the column sizes are correct, connects the sig/slot for syncing with
 * the Debugger UI, then populates the Call Stack table.
 *
 */
void DialogBacktrace::showEvent(QShowEvent *) {

	// Sync with the Debugger UI.
	connect(edb::v1::debugger_ui, SIGNAL(uiUpdated()), this, SLOT(populateTable()));

	// Populate the tabel with our call stack info.
	populateTable();

	table_->horizontalHeader()->resizeSections(QHeaderView::Stretch);
}

/**
 * @brief DialogBacktrace::populateTable
 *
 * Populates the Call Stack table with stack frame entries.
 *
 */
void DialogBacktrace::populateTable() {

	//TODO: The first row should break protocol and display the current RIP/PC.
	//		It should be treated specially on "Run To Return" and do a "Step Out"

	//Remove rows of the table (clearing does not remove rows)
	//Yes, we depend on i going negative.
	for (int i = table_->rowCount() - 1; i >= 0; i--) {
		table_->removeRow(i);
	}

	//Get the call stack and populate the table with entries.
	CallStack call_stack;
	const size_t size = call_stack.size();
	for (size_t i = 0; i < size; i++) {

		//Create the row to insert info
		table_->insertRow(i);

		//Get the stack frame so that we can insert its info
		CallStack::StackFrame *frame = call_stack[i];

		//Get the caller & ret addresses and put them in the table
		QList<edb::address_t> stack_entry;
		edb::address_t caller = frame->caller;
		edb::address_t ret    = frame->ret;
		stack_entry.append(caller);
		stack_entry.append(ret);

		//Put them in the table: create string from address and set item flags.
		for (int j = 0; j < stack_entry.size() && j < table_->columnCount(); j++) {

			edb::address_t address              = stack_entry.at(j);
			std::shared_ptr<Symbol> near_symbol = edb::v1::symbol_manager().findNearSymbol(address);

			//Turn the address into a string prefixed with "0x"
			auto item = new QTableWidgetItem;
			item->setData(Qt::UserRole, static_cast<qlonglong>(address));

			if (near_symbol) {
				const QString function = near_symbol->name;
				const uint64_t offset  = address - near_symbol->address;
				item->setText(tr("0x%1 <%2+%3>").arg(QString::number(address, 16), function).arg(offset));
			} else {
				item->setText(tr("0x%1").arg(QString::number(address, 16)));
			}

			//Remove all flags (namely Edit), then put the flags that we want.
			Qt::ItemFlags flags = Qt::NoItemFlags;
			flags |= Qt::ItemIsEnabled | Qt::ItemIsSelectable;
			item->setFlags(flags);

			table_->setItem(i, j, item);
		}
	}

	//1st ret is selected on every refresh so that we can just click "Return To"
	//Turn Run To button off if no item.
	QTableWidgetItem *item = table_->item(FirstRow, ReturnColumn);
	if (item) {
		table_->setCurrentItem(item);
		buttonReturnTo_->setEnabled(true);
	} else {
		buttonReturnTo_->setEnabled(false);
	}
}

/**
 * @brief DialogBacktrace::hideEvent
 *
 * Disconnects the signal/slot when the dialog goes away so that
 * populate_table() is not called unnecessarily.
 *
 */
void DialogBacktrace::hideEvent(QHideEvent *) {
	disconnect(edb::v1::debugger_ui, SIGNAL(uiUpdated()), this, SLOT(populateTable()));
}

/**
 * @brief DialogBacktrace::on_tableWidgetCallStack_itemDoubleClicked
 *
 * Jumps to the double-clicked address in the CPU/Disassembly view.
 *
 * @param item
 */
void DialogBacktrace::on_tableWidgetCallStack_itemDoubleClicked(QTableWidgetItem *item) {
	edb::v1::jump_to_address(address_from_table(item));
}

/**
 * @brief DialogBacktrace::on_tableWidgetCallStack_cellClicked
 *
 * Enables the "Run To Return" button if the selected cell is in the column for
 * return addresses.  Disables it, otherwise.
 *
 * @param row
 * @param column
 */
void DialogBacktrace::on_tableWidgetCallStack_cellClicked(int row, int column) {
	Q_UNUSED(row)
	if (is_ret(column)) {
		buttonReturnTo_->setEnabled(true);
	} else {
		buttonReturnTo_->setEnabled(false);
	}
}

}