1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "reversedebuggermgr.h"
6#include "reversedebuggerconstants.h"
7#include "minidumpruncontrol.h"
8#include "eventfilterdialog.h"
9#include "event_man.h"
10#include "taskwindow.h"
11#include "taskmodel.h"
12#include "taskfiltermodel.h"
13#include "timelinewidget.h"
14#include "loadcoredialog.h"
15#include "services/debugger/debuggerservice.h"
16#include "common/actionmanager/actionmanager.h"
17#include "services/project/projectservice.h"
18#include "services/window/windowservice.h"
19
20#include <QtConcurrent>
21#include <QVBoxLayout>
22#include <QSpacerItem>
23#include <QMessageBox>
24
25#include <linux/limits.h>
26
27#define AsynInvoke(Fun) \
28 QtConcurrent::run([this]() { \
29 Fun; \
30 });
31
32extern bool kEmdRunning;
33void* kTimeline = nullptr;
34static const char *kCoredump = "/tmp/emd.core";
35
36// default flags count.
37#define X11_FLAGS_COUNT 20
38#define SIGNAL_FLAGS_COUNT 20
39#define SYSCALL_FLAGS_COUNT 8
40#define DBUS_FLAGS_COUNT 5
41
42using namespace dpfservice;
43
44namespace ReverseDebugger {
45namespace Internal {
46
47static TaskWindow* g_taskWindow = nullptr;
48
49ReverseDebuggerMgr::ReverseDebuggerMgr(QObject *parent)
50 : QObject(parent), runCtrl(new MinidumpRunControl(this))
51{
52 initialize();
53}
54
55void ReverseDebuggerMgr::initialize()
56{
57 if (!g_taskWindow) {
58 g_taskWindow = new TaskWindow;
59
60 g_taskWindow->addCategory(Constants::EVENT_CATEGORY_SYSCALL, tr("syscall"), true);
61 g_taskWindow->addCategory(Constants::EVENT_CATEGORY_SIGNAL, tr("signal"), true);
62 g_taskWindow->addCategory(Constants::EVENT_CATEGORY_X11, tr("x11"), true);
63 g_taskWindow->addCategory(Constants::EVENT_CATEGORY_DBUS, tr("dbus"), true);
64 connect(g_taskWindow, SIGNAL(coredumpChanged(int)),
65 this, SLOT(runCoredump(int)));
66 connect(g_taskWindow, SIGNAL(tasksCleared()),
67 this, SLOT(unloadMinidump()));
68 }
69
70 if (!settings) {
71 QString iniPath = CustomPaths::user(CustomPaths::Flags::Configures) + QDir::separator() + QString("reversedbg.ini");
72 bool setDefaultVaule = false;
73 if (!QFile::exists(iniPath)) {
74 setDefaultVaule = true;
75 }
76 settings = new QSettings(iniPath, QSettings::IniFormat, this);
77 if (setDefaultVaule) {
78 setConfigValue("StackSize", 32);
79 setConfigValue("HeapSize", 0);
80 setConfigValue("ParamSize", 256);
81 setConfigValue("CurrentThread", true);
82 }
83 }
84}
85
86void ReverseDebuggerMgr::recored()
87{
88 recordMinidump();
89}
90
91void ReverseDebuggerMgr::replay()
92{
93 QString defaultTraceDir = QDir::homePath() + QDir::separator() + ".local/share/emd/latest-trace";
94
95 LoadCoreDialog dlg;
96 CoredumpRunParameters parameters = dlg.displayDlg(defaultTraceDir);
97 if (parameters.tracedir.isEmpty() || parameters.pid == 0)
98 return;
99
100 bool replaySuccess = replayMinidump(parameters.tracedir, parameters.pid);
101 if (replaySuccess) {
102 enterReplayEnvironment();
103 }
104}
105
106QWidget *ReverseDebuggerMgr::getWidget() const
107{
108 QWidget *widget = new QWidget();
109
110 // verhicle
111 QVBoxLayout *vLayout = new QVBoxLayout();
112 vLayout->setMargin(0);
113 vLayout->setSpacing(0);
114 widget->setLayout(vLayout);
115 QHBoxLayout *hLayout = new QHBoxLayout();
116 for (auto it : g_taskWindow->toolBarWidgets()) {
117 hLayout->addWidget(it);
118 }
119
120 QSpacerItem *spaceItem = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
121 hLayout->addSpacerItem(spaceItem);
122 vLayout->addLayout(hLayout);
123 vLayout->addWidget(g_taskWindow->outputWidget());
124
125 return widget;
126}
127
128QString ReverseDebuggerMgr::generateFilePath(const QString &fileName, const QString &traceDir, int pid)
129{
130 QString ret = traceDir + (traceDir.back() == '/' ? "" : "/");
131 ret += fileName;
132 ret += QString::number(pid);
133
134 return ret;
135}
136
137static void getProgramFile(QString& linkname)
138{
139 FILE* pf = fopen(linkname.toLocal8Bit().data(), "rb");
140 if (pf) {
141 char exename[PATH_MAX] = {0};
142 fscanf(pf, "%s", exename);
143 fclose(pf);
144 linkname = QString::fromLocal8Bit(exename);
145 qDebug() << "get_program_file :" << linkname;
146 }
147}
148
149bool ReverseDebuggerMgr::replayMinidump(const QString &traceDir, int pid)
150{
151 if (kTimeline) {
152 g_taskWindow->updateTimeline(nullptr, 0);
153 destroy_timeline(kTimeline);
154 kTimeline = nullptr;
155 }
156
157 // core file.
158 QString corefile = generateFilePath(CONTEXT_FILE_NAME, traceDir, pid);
159 if (corefile.isEmpty()) {
160 outputMessage("Context file is empty!");
161 return false;
162 }
163
164 // map file.
165 QString mapFile = generateFilePath(MAP_FILE_NAME, traceDir, pid);
166 int frameCount = create_timeline(mapFile.toLocal8Bit(),
167 corefile.toLocal8Bit(), &kTimeline);
168 if (frameCount < 1) {
169 QMessageBox msgBox;
170 msgBox.setText(tr("Not found valid event in context file!"));
171 msgBox.exec();
172 return false;
173 }
174
175 const EventEntry* entry = get_event_pointer(kTimeline);
176 if (entry) {
177 // Add task.
178 const char* cats[] = {
179 ReverseDebugger::Constants::EVENT_CATEGORY_SYSCALL,
180 ReverseDebugger::Constants::EVENT_CATEGORY_SIGNAL,
181 ReverseDebugger::Constants::EVENT_CATEGORY_DBUS,
182 ReverseDebugger::Constants::EVENT_CATEGORY_X11,
183 };
184
185 for (int i = 0; i < frameCount; ++i) {
186
187 /* ON MIPS, there's these three value:
188 22:#define __NR_Linux 4000
189 406:#define __NR_Linux 5000
190 749:#define __NR_Linux 6000
191 */
192 ReverseDebugger::Internal::Task task(
193 QString::asprintf("%d:%s", i, get_event_name(entry->type)),
194 cats[entry->type/1000 - __NR_Linux/1000],
195 entry);
196 g_taskWindow->addTask(task);
197 ++entry;
198 }
199 }
200 g_taskWindow->updateTimeline(kTimeline, frameCount);
201
202
203 QString localExecutableFile = generateFilePath(EXEC_FILE_NAME, traceDir, pid);
204 getProgramFile(localExecutableFile);
205 targetPath = localExecutableFile;
206
207 // do something.
208 // check if trace-dir/crash.txt exist? Auto load crash event if true.
209 --entry;
210 if (entry->type >= DUMP_REASON_signal && entry->type < DUMP_REASON_dbus) {
211 g_taskWindow->goTo(frameCount - 1);
212 }
213 return true;
214}
215
216void ReverseDebuggerMgr::outputMessage(const QString &msg)
217{
218 // TODO(mozart):display the message to outputpane.
219 qDebug() << msg;
220}
221
222void ReverseDebuggerMgr::exist()
223{
224 auto command = ActionManager::getInstance()->command("Debug.Abort.Debugging");
225 QAction *action = nullptr;
226 if (command && (action = command->action()) && action->isEnabled()) {
227 action->trigger();
228 }
229}
230
231const QString &ReverseDebuggerMgr::dumpTargetPath() const
232{
233 return targetPath;
234}
235
236QString ReverseDebuggerMgr::projectTargetPath() const
237{
238 QString targetPath;
239 auto &ctx = dpfInstance.serviceContext();
240 ProjectService *projectService = ctx.service<ProjectService>(ProjectService::name());
241 if (projectService && projectService->getActiveTarget) {
242 Target target = projectService->getActiveTarget(kActiveExecTarget);
243 targetPath = target.output;
244 }
245 return targetPath;
246}
247
248void ReverseDebuggerMgr::enterReplayEnvironment()
249{
250 emit editor.switchContext(tr("R&everse Debug"));
251}
252
253static void NumberList2QString(uchar *in, int size, QString &str)
254{
255 str.clear();
256 for (int i = 0; i < size; ++i) {
257 if (in[i] != 0) {
258 str += QString::number(i) + QLatin1Char(',');
259 }
260 }
261}
262
263static void ParseNumberList(QString &str, unsigned char *out, size_t size)
264{
265 QStringList ret = str.split(QLatin1Char(','));
266
267 memset(out, 0, size);
268
269 if (ret.size() == 0) {
270 return;
271 }
272
273 QStringList::const_iterator it;
274 for (it = ret.constBegin(); it != ret.constEnd(); ++it) {
275 out[(*it).toInt()] = 1;
276 }
277}
278
279void ReverseDebuggerMgr::recordMinidump()
280{
281 if (kEmdRunning) {
282 qDebug() << "emd is running";
283 return;
284 }
285
286 uchar x11Flags[X11_FLAGS_COUNT] = {0};
287 uchar signalFlags[SIGNAL_FLAGS_COUNT] = {0};
288 uchar syscallFlags[SYSCALL_FLAGS_COUNT] = {0};
289 uchar dbusFlags[DBUS_FLAGS_COUNT] = {0};
290
291 QString syscall = configValue("SyscallFilter").toString();
292 QString signal = configValue("SignalFilter").toString();
293 QString x11 = configValue("X11Filter").toString();
294 QString dbus = configValue("DbusFilter").toString();
295
296 ParseNumberList(syscall, syscallFlags, sizeof(syscallFlags));
297 ParseNumberList(signal, signalFlags, sizeof(signalFlags));
298 ParseNumberList(x11, x11Flags, sizeof(x11Flags));
299 ParseNumberList(dbus, dbusFlags, sizeof(dbusFlags));
300
301 EventFilterDialog dlg(nullptr, syscallFlags, dbusFlags, x11Flags, signalFlags);
302
303 QString maxStackSize = configValue("StackSize").toString();
304 QString maxHeapSize = configValue("HeapSize").toString();
305 QString maxParamSize = configValue("ParamSize").toString();
306 bool onlyCurrentThread = configValue("CurrentThread").toBool();
307 dlg.setMaxStackSize(maxStackSize);
308 dlg.setMaxHeapSize(maxHeapSize);
309 dlg.setMaxParamSize(maxParamSize);
310 dlg.setOnlyCurrentThread(onlyCurrentThread);
311 if (dlg.exec() != QDialog::Accepted)
312 return;
313
314 maxStackSize = dlg.maxStackSize();
315 maxHeapSize = dlg.maxHeapSize();
316 maxParamSize = dlg.maxParamSize();
317 onlyCurrentThread = dlg.onlyCurrentThread();
318 setConfigValue("StackSize", maxStackSize);
319 setConfigValue("HeapSize", maxHeapSize);
320 setConfigValue("ParamSize", maxParamSize);
321 setConfigValue("CurrentThread", onlyCurrentThread);
322
323 NumberList2QString(syscallFlags, sizeof(syscallFlags), syscall);
324 NumberList2QString(signalFlags, sizeof(signalFlags), signal);
325 NumberList2QString(x11Flags, sizeof(x11Flags), x11);
326 NumberList2QString(dbusFlags, sizeof(dbusFlags), dbus);
327
328 setConfigValue("SyscallFilter", syscall);
329 setConfigValue("SignalFilter", signal);
330 setConfigValue("X11Filter", x11);
331 setConfigValue("DbusFilter", dbus);
332
333 QString emdParams;
334
335 //NOTE: There's no need to store global variable name and break function.
336 // They are depend on the specified project.
337 QString globalVar = dlg.globalVar();
338 if (!globalVar.isEmpty()) {
339 qDebug() << "global var:" << globalVar;
340 emdParams += QLatin1String(" --var=") + globalVar;
341 }
342
343 QString breakFunc = dlg.breakFunc();
344 if (!breakFunc.isEmpty()) {
345 qDebug() << "break func:" << breakFunc;
346 emdParams += QLatin1String(" --func=") + breakFunc;
347 }
348
349 if (syscallFlags[7]) {
350 qDebug() << "hook vdso";
351 emdParams += QLatin1String(" --vdso=on");
352 }
353
354 if (syscall.size()) {
355 qDebug() << "syscall:" << syscall;
356 emdParams += QLatin1String(" --sys=") + dlg.syscallKindNames();
357 }
358 if (x11.size()) {
359 qDebug() << "x11:" << x11;
360 emdParams += QLatin1String(" --x11=") + x11;
361 }
362 if (dbus.size()) {
363 qDebug() << "dbus:" << syscall;
364 emdParams += QLatin1String(" --dbus=") + dbus;
365 }
366
367 if (maxStackSize.size()) {
368 emdParams += QLatin1String(" --stack-size=") + maxStackSize;
369 }
370
371 if (maxHeapSize.size()) {
372 emdParams += QLatin1String(" --heap-size=") + maxHeapSize;
373 }
374
375 if (maxParamSize.size()) {
376 emdParams += QLatin1String(" --param-size=") + maxParamSize;
377 }
378
379 if (onlyCurrentThread) {
380 emdParams += QLatin1String(" -1");
381 }
382
383 if (runCtrl) {
384 runCtrl->start(emdParams, projectTargetPath());
385 }
386}
387
388void ReverseDebuggerMgr::runCoredump(int index)
389{
390 qDebug() << Q_FUNC_INFO << ", " << index;
391
392 // produce coredump.
393 if (0 == generate_coredump(kTimeline, index, kCoredump, 0)) {
394 // run coredump.
395 // gdb target core.
396 auto &ctx = dpfInstance.serviceContext();
397 auto service = ctx.service<DebuggerService>(DebuggerService::name());
398 if (service) {
399 if (service->runCoredump) {
400 service->runCoredump(dumpTargetPath(), kCoredump, "cmake");
401 }
402 }
403 } else {
404 qDebug() << "Failed to create coredump file:" << index;
405 }
406}
407
408void ReverseDebuggerMgr::unloadMinidump()
409{
410 qDebug() << __FUNCTION__ << " timeline=" << kTimeline;
411
412 if (kTimeline) {
413 g_taskWindow->updateTimeline(nullptr, 0);
414 destroy_timeline(kTimeline);
415 kTimeline = nullptr;
416 }
417}
418
419QVariant ReverseDebuggerMgr::configValue(const QByteArray &name)
420{
421 // here are temporay value.
422 QHash<QString, QString> values { { "StackSize", "32" },
423 { "HeapSize", "0" },
424 { "ParamSize", "256" },
425 { "CurrentThread", "true" }};
426
427 return settings->value(QString::fromLatin1("DebugMode/" + name));
428}
429
430void ReverseDebuggerMgr::setConfigValue(const QByteArray &name, const QVariant &value)
431{
432 settings->setValue(QString::fromLatin1("DebugMode/" + name), value);
433}
434
435} // namespace Internal
436} // namespace ReverseDebugger
437