Gdb_MI2.cpp

Gdb_MI2 toolbar update & smalltool bar fix - Zbigniew Rebacz, 11/19/2013 01:01 PM

Download (47.8 KB)

 
1
#include "Debuggers.h"
2
#include <ide/ide.h>
3

    
4
#include "PrettyPrinters.brc"
5

    
6
#define LDUMP(x) // DDUMP(x)
7
#define LLOG(x)  // DLOG(x)
8

    
9
// we need those because we changed ArrayCtrl formats (including var types)
10
static VectorMap<String, String> DataMap2(const ArrayCtrl& data)
11
{
12
        VectorMap<String, String> m;
13
        for(int i = 0; i < data.GetCount(); i++)
14
                m.Add(data.Get(i, 0), data.Get(i, 2));
15
        return m;
16
}
17

    
18
static void MarkChanged2(const VectorMap<String, String>& m, ArrayCtrl& data)
19
{
20
        for(int i = 0; i < data.GetCount(); i++)
21
        {
22
                int q = m.Find(data.Get(i, 0));
23
                if(q >= 0 && m[q] != data.Get(i, 2))
24
                        data.SetDisplay(i, 2, Single<RedDisplay>());
25
                else
26
                        data.SetDisplay(i, 2, StdDisplay());
27
        }
28
}
29

    
30
void WatchEdit::HighlightLine(int line, Vector<Highlight>& h, int pos)
31
{
32
        Color cEven = Blend(SColorInfo, White, 220);
33
        Color cOdd = Blend(SColorInfo, White, 128);
34
        for(int i = 0; i < h.GetCount(); i++)
35
                h[i].paper = (line % 2 ? cOdd : cEven);
36
}
37

    
38
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
39
//                                                                                        PUBLIC IDE INTERFACE
40
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
41

    
42
void Gdb_MI2::DebugBar(Bar& bar)
43
{
44
        bar.Add("Stop debugging", DbgImg::StopDebug(), THISBACK(Stop)).Key(K_SHIFT_F5);
45
        bar.Separator();
46
// see note on Run() function -- crashes X on my machine, so removed by now
47
//        bar.Add(!stopped, "Asynchronous break", THISBACK(AsyncBrk));
48
        bool b = !IdeIsDebugLock();
49
        bar.Add(b, "Step into", DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "exec-step-instruction" : "exec-step")).Key(K_F11);
50
        bar.Add(b, "Step over", DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "exec-next-instruction" : "exec-next")).Key(K_F10);
51
        bar.Add(b, "Step out", DbgImg::StepOut(), THISBACK1(Step, "exec-finish")).Key(K_SHIFT_F11);
52
        bar.Add(b, "Run to", DbgImg::RunTo(), THISBACK(doRunTo)).Key(K_CTRL_F10);
53
        bar.Add(b, "Run", DbgImg::Run(), THISBACK(Run)).Key(K_F5);
54
        bar.MenuSeparator();
55
        bar.Add(b, "Quick watch", THISBACK(QuickWatch)).Key(K_CTRL_Q);
56
        bar.MenuSeparator();
57
        bar.Add(b, "Copy backtrace", THISBACK(CopyStack));
58
        bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas));
59
}
60

    
61
static int CharFilterReSlash(int c)
62
{
63
        return c == '\\' ? '/' : c;
64
}
65

    
66
bool Gdb_MI2::SetBreakpoint(const String& filename, int line, const String& bp)
67
{
68
        String file = Filter(host->GetHostPath(NormalizePath(filename)), CharFilterReSlash);
69
        
70
        // gets all breakpoints
71
        MIValue bps = GetBreakpoints();
72
        
73
        // line should start from 1...
74
        line++;
75
        
76
        // check wether we've got already a breakpoint here
77
        // and remove it
78
        MIValue brk = bps.FindBreakpoint(file, line);
79
        if(!brk.IsEmpty())
80
                if(!MICmd(Format("break-delete %s", brk["number"].Get())))
81
                {
82
                        Exclamation(t_("Couldn't remove breakpoint"));
83
                        return false;
84
                }
85
        
86
        if(bp.IsEmpty())
87
                return true;
88
        else if(bp[0] == 0xe)
89
                return MICmd(Format("break-insert %s:%d", file, line));
90
        else
91
                return MICmd(Format("break-insert -c \"%s\" %s:%d", bp, file, line));
92
}
93

    
94
bool Gdb_MI2::RunTo()
95
{
96
        String bi;
97
        bool df = disas.HasFocus();
98
        bool res;
99
        // sets a temporary breakpoint on cursor location
100
        // it'll be cleared automatically on first stop
101
        if(df)
102
        {
103
                if(!disas.GetCursor())
104
                        return false;
105
                res = TryBreak(disas.GetCursor(), true);
106
        }
107
        else
108
                res = TryBreak(IdeGetFileName(), IdeGetFileLine() + 1, true);
109

    
110
        if(!res)
111
        {
112
                Exclamation(t_("No code at chosen location !"));
113
                return false;
114
        }
115

    
116
        Run();
117
        if(df)
118
                disas.SetFocus();
119
        return true;
120
}
121

    
122
void Gdb_MI2::Run()
123
{
124
        MIValue val;
125
        if(firstRun)
126
                // GDB up to 7.1 has a bug that maps -exec-run ro run, not to run&
127
                // making so async mode useless; we use the console run& command instead
128
// 2012-07-08 update : interrupting GDB in async mode without having
129
// non-stop mode enabled crashes X...don't know if it's a GDB bug or Theide one.
130
// anyways, by now we give up with async mode and remove 'Asynchronous break' function
131
                val = MICmd("exec-run");
132

    
133
//                val = MICmd("interpreter-exec console run&");
134
                
135
        else
136
                val = MICmd("exec-continue --all");
137
        int i = 50;
138
        while(!started && --i)
139
        {
140
                GuiSleep(20);
141
                Ctrl::ProcessEvents();
142
                ReadGdb(false);
143
        }
144
        if(!started)
145
        {
146
                Exclamation(t_("Failed to start application"));
147
                return;
148
        }
149
        Lock();
150
        while(dbg && !stopped)
151
        {
152
                GuiSleep(20);
153
                Ctrl::ProcessEvents();
154
                ReadGdb(false);
155
        }
156
        Unlock();
157
        if(stopped)
158
                CheckStopReason();
159
                
160
        started = stopped = false;
161
        firstRun = false;
162
        IdeActivateBottom();
163
}
164

    
165
void Gdb_MI2::AsyncBrk()
166
{
167
        // send an interrupt command to all running threads
168
        StopAllThreads();
169
        
170
        // gdb usually returns to command prompt instantly, BEFORE
171
        // giving out stop reason, which we need. So, we wait some
172
        // milliseconds and re-read (non blocking) GDB output to get it
173
/*
174
        for(int i = 0; i < 20 && !stopped; i++)
175
        {
176
                Sleep(100);
177
                ReadGdb(false);
178
        }
179
*/
180
        
181
        // if target is correctly stopped, 'stopped' flag should be already set
182
        // so we don't care here. If not set, target can't be stopped.
183
}
184

    
185
void Gdb_MI2::Stop()
186
{
187
        stopped = true;
188
        if(dbg && dbg->IsRunning())
189
                dbg->Kill();
190
}
191

    
192
bool Gdb_MI2::IsFinished()
193
{
194
        return !dbg->IsRunning() && !IdeIsDebugLock();
195
}
196

    
197
bool Gdb_MI2::Tip(const String& exp, CodeEditor::MouseTip& mt)
198
{
199
        // quick exit
200
        if(exp.IsEmpty())
201
                return false;
202
        
203
        // if local vars are empty, update them
204
        UpdateLocalVars();
205

    
206
        // display tip just for local variables... it should be more than enough
207
        int i = localVarExpressions.Find(exp);
208
        if(i < 0)
209
                return false;
210

    
211
        mt.display = &StdDisplay();
212
        mt.value = exp + "=" + localVarValues[i];
213
        mt.sz = mt.display->GetStdSize(String(mt.value) + "X");
214
        return true;
215
}
216

    
217
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
218
//                                                                                        CONSTRUCTOR / DESTRUCTOR
219
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
220

    
221
Gdb_MI2::Gdb_MI2()
222
{
223
        CtrlLayout(regs);
224
        regs.Height(regs.GetLayoutSize().cy);
225
        AddReg(RPREFIX "ax", &regs.rax);
226
        AddReg(RPREFIX "bx", &regs.rbx);
227
        AddReg(RPREFIX "cx", &regs.rcx);
228
        AddReg(RPREFIX "dx", &regs.rdx);
229
        AddReg(RPREFIX "si", &regs.rsi);
230
        AddReg(RPREFIX "di", &regs.rdi);
231
        AddReg(RPREFIX "bp", &regs.rbp);
232
        AddReg(RPREFIX "sp", &regs.rsp);
233
#ifdef CPU_64
234
        AddReg("r8", &regs.r8);
235
        AddReg("r9", &regs.r9);
236
        AddReg("r10", &regs.r10);
237
        AddReg("r11", &regs.r11);
238
        AddReg("r12", &regs.r12);
239
        AddReg("r13", &regs.r13);
240
        AddReg("r14", &regs.r14);
241
        AddReg("r15", &regs.r15);
242
#endif
243
        regs.Color(SColorLtFace);
244
        regs.AddFrame(TopSeparatorFrame());
245
        regs.AddFrame(RightSeparatorFrame());
246

    
247
        autos.NoHeader();
248
        autos.AddColumn("", 1);
249
        autos.AddColumn("", 10);
250
        autos.AddColumn("", 3);
251
        autos.WhenLeftDouble = THISBACK1(onExploreExpr, &autos);
252
        autos.EvenRowColor();
253
        autos.OddRowColor();
254

    
255
        locals.NoHeader();
256
        locals.AddColumn("", 1);
257
        locals.AddColumn("", 10);
258
        locals.AddColumn("", 3);
259
        locals.WhenLeftDouble = THISBACK1(onExploreExpr, &locals);
260
        locals.EvenRowColor();
261
        locals.OddRowColor();
262

    
263
        watches.NoHeader();
264
        watches.AddColumn("", 1).Edit(watchedit);
265
        watches.AddColumn("", 10);
266
        watches.AddColumn("", 3);
267
        watches.Inserting().Removing();
268
        watches.WhenLeftDouble = THISBACK1(onExploreExpr, &watches);
269
        watches.EvenRowColor();
270
        watches.OddRowColor();
271

    
272
        int c = EditField::GetStdHeight();
273
        explorer.AddColumn("", 1);
274
        explorer.AddColumn("", 6) /*.SetDisplay(Single<VisualDisplay>()) */;
275
        explorerPane.Add(explorerBackBtn.LeftPos(0, c).TopPos(0, c));
276
        explorerPane.Add(explorerForwardBtn.LeftPos(c + 2, c).TopPos(0, c));
277
        explorerPane.Add(explorerExprEdit.HSizePos(2 * c + 4).TopPos(0, c));
278
        explorerPane.Add(explorer.HSizePos().VSizePos(EditField::GetStdHeight(), 0));
279
        explorer.NoHeader();
280
        explorer.WhenLeftDouble = THISBACK(onExplorerChild);
281
        explorer.WhenBar = THISBACK(ExplorerMenu);
282

    
283
        explorerBackBtn.SetImage(DbgImg::ExplorerBack());
284
        explorerBackBtn <<= THISBACK(onExplorerBack);
285
        explorerForwardBtn.SetImage(DbgImg::ExplorerFw());
286
        explorerForwardBtn <<= THISBACK(onExplorerForward);
287
        explorerBackBtn.Disable();
288
        explorerForwardBtn.Disable();
289
        explorerHistoryPos = -1;
290
        
291
        Add(tab.SizePos());
292
        tab.Add(autos.SizePos(), "Autos");
293
        tab.Add(locals.SizePos(), t_("Locals"));
294
        tab.Add(watches.SizePos(), t_("Watches"));
295
        tab.Add(explorerPane.SizePos(), t_("Explorer"));
296
        
297
        Add(threadSelector.LeftPos(FindTabsRight() + 10, StdFont().GetWidth('X') * 10).TopPos(2, EditField::GetStdHeight()));
298
        Add(frame.HSizePos(threadSelector.GetRect().right + 10, 0).TopPos(2, EditField::GetStdHeight()));
299
        frame.Ctrl::Add(dlock.SizePos());
300
        dlock = "  Running..";
301
        dlock.SetFrame(BlackFrame());
302
        dlock.SetInk(Red);
303
        dlock.NoTransparent();
304
        dlock.Hide();
305

    
306
        CtrlLayout(quickwatch, "Quick watch");
307
        quickwatch.close.Cancel() <<= quickwatch.Breaker(IDCANCEL);
308
        quickwatch.evaluate.Ok() <<= quickwatch.Acceptor(IDOK);
309
        quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL);
310
        quickwatch.value.SetReadOnly();
311
        quickwatch.value.SetFont(Courier(12));
312
        quickwatch.value.SetColor(TextCtrl::PAPER_READONLY, White);
313
        quickwatch.Sizeable().Zoomable();
314
        quickwatch.NoCenter();
315
        quickwatch.SetRect(0, 150, 300, 400);
316
        quickwatch.Icon(DbgImg::QuickWatch());
317

    
318
        Transparent();
319

    
320
        started = false;
321
        stopped = false;
322
}
323

    
324
Gdb_MI2::~Gdb_MI2()
325
{
326
        IdeRemoveBottom(*this);
327
        IdeRemoveRight(disas);
328
}
329

    
330
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
331
//                                                                                        PRIVATE FUNCTIONS
332
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
333

    
334
// find free space at right of tabs (we should probably add something to TabCtrl for that..)
335
int Gdb_MI2::FindTabsRight(void)
336
{
337
        if(!tab.GetCount())
338
                return 0;
339
        
340
        int lastTab = tab.GetCount() - 1;
341
        int i1 = 0, i2 = 10000;
342
        
343
        // bisect up it finds a point on last tab...
344
        int iTab = -1;
345
        int i = 0;
346
        while(iTab != lastTab)
347
        {
348
                i = (i1 + i2) / 2;
349
                iTab = tab.GetTab(Point(i, 0));
350
                if(iTab < 0)
351
                        i2 = i;
352
                else
353
                        i1 = i;
354
        }
355
        
356
        // now scan for righ tab edge
357
        i1 = i; i2 = i + 10000;
358
        while(abs(i1 - i2) > 2)
359
        {
360
                i = (i1 + i2) / 2;
361
                iTab = tab.GetTab(Point(i, 0));
362
                if(iTab == -1)
363
                        i2 = i;
364
                else
365
                        i1 = i;
366
        }
367
        return i;
368
}
369

    
370
// lock/unlock debugger controls
371
void Gdb_MI2::Lock()
372
{
373
        IdeDebugLock();
374
        watches.Disable();
375
        locals.Disable();
376
        frame.Disable();
377
        threadSelector.Disable();
378
        dlock.Show();
379
}
380

    
381
void Gdb_MI2::Unlock()
382
{
383
        if(IdeDebugUnLock())
384
        {
385
                watches.Enable();
386
                locals.Enable();
387
                frame.Enable();
388
                threadSelector.Enable();
389
                dlock.Hide();
390
        }
391
}
392

    
393
// read debugger output analyzing command responses and async output
394
// things are quite tricky because debugger output seems to be
395
// slow and we have almost no terminator to stop on -- (gdb) is not
396
// so reliable as it can happen (strangely) in middle of nothing
397
MIValue Gdb_MI2::ParseGdb(String const &output, bool wait)
398
{
399
        MIValue res;
400
        
401
        LDUMP(output);
402

    
403
        // parse result data
404
        StringStream ss(output);
405
        int iSubstr;
406
        while(!ss.IsEof())
407
        {
408
                String s = TrimBoth(ss.GetLine());
409
                
410
                // check 'running' and 'stopped' async output
411
                if(s.StartsWith("*running"))
412
                {
413
                        started = true;
414
                        stopReason.Clear();
415
                        continue;
416
                }
417
// 2012-07-08 -- In some cases, GDB output get mixed with app one
418
//               so we shall look for 'stop' record in all string,
419
//               not just at beginning as it was before
420
//                                  not a wanderful way, as debugger could be tricked by some text...
421
/*
422
                else if(s.StartsWith("*stopped"))
423
                {
424
                        stopped = true;
425
                        s = '{' + s.Mid(9) + '}';
426
                        stopReason = MIValue(s);
427
                        continue;
428
                }
429
*/
430
                else if( (iSubstr = s.Find("*stopped,reason=")) >= 0)
431
                {
432
                        stopped = true;
433
                        s = '{' + s.Mid(iSubstr + 9) + '}';
434
                        stopReason = MIValue(s);
435
                        continue;
436
                }
437
                
438
                // skip asynchronous responses
439
                // in future, we could be gather/use them
440
                if(s[0] == '*'|| s[0] == '=')
441
                        continue;
442
                
443
                // here handling of command responses
444
                // we're not interested either, as we use MI interface
445
                if(s[0] == '~')
446
                        continue;
447
                
448
                // here handling of target output
449
                // well, for now discard this one too, but it should go on console output
450
                if(s[0] == '~')
451
                        continue;
452
        
453
                // here handling of gdb log/debug message
454
                // not interesting here
455
                if(s[0] == '&')
456
                        continue;
457
                
458
                // now we SHALL have something starting with any of
459
                // // "^done", "^running", "^connected", "^error" or "^exit" records
460
                if(s.StartsWith("^done") || s.StartsWith("^running"))
461
                {
462
                        // here we've got succesful command output in list form, if any
463
                        // shall skip the comma; following can be a serie of pairs,
464
                        // or directly an array of maps in form of :
465
                        // [{key="value",key="value",...},{key="value"...}...]
466
                        
467
                        int i = 5; // just skip shortest, ^done
468
                        while(s[i] && s[i] != ',')
469
                                i++;
470
                        if(!s[i])
471
                                continue;
472
                        i++;
473
                        if(!s[i])
474
                                continue;
475
                        res = MIValue(s.Mid(i));
476
                        continue;
477
                }
478
                else if(s.StartsWith("^error"))
479
                {
480
                        // first array element is reserved for command result status
481
                        s = s.Right(12); // '^error,msg=\"'
482
                        s = s.Left(s.GetCount() - 1);
483
                        res.SetError(s);
484
                }
485
                else
486
                        continue;
487
        }
488
        return res;
489
}
490

    
491
MIValue Gdb_MI2::ReadGdb(bool wait)
492
{
493
        String output;
494
        MIValue res;
495

    
496
        if(wait)
497
        {
498
                // this path is for locking read -- we NEED cmd output
499
                int retries = 4;
500
                while(dbg)
501
                {
502
                        String s;
503
                        dbg->Read(s);
504
                        
505
                        // we really NEED an answer
506
                        if(s.IsEmpty() && output.IsEmpty())
507
                                continue;
508
                        
509
                        // on first empty string, we check if we really
510
                        // got a valid answer
511
                        if(s.IsEmpty())
512
                        {
513
                                res = ParseGdb(output);
514
                                
515
                                // if no valid answer (or, well, an empty answer..)
516
                                // we retry up to 4 times
517
                                if(res.IsEmpty() && --retries)
518
                                {
519
                                        Sleep(20);
520
                                        continue;
521
                                }
522
                                return res;
523
                        }
524
                        output += s;
525
                }
526
        }
527
        else
528
        {
529
                // this path is for NON locking read -- we just cleanup
530
                // streaming output, mostly
531
                while(dbg)
532
                {
533
                        String s;
534
                        dbg->Read(s);
535
                        if(s.IsEmpty())
536
                                break;
537
                        output += s;
538
                }
539
        }
540
        if(output.IsEmpty())
541
                return res;
542
        return ParseGdb(output);
543
}
544

    
545
// new-way commands using GDB MI interface
546
// on input  : MI interface command line
547
// on output : an MIValue containing GDB output
548
// STREAM OUTPUT
549
// ~                                                command response
550
// @                                                target output
551
// &                                                gdb log/debug messages
552
//
553
// RESULT RECORDS
554
// "^done" [ "," results ]
555
// "^running"                                same as "^done"
556
//        "^connected"                        gdb has connected to a remote target.
557
//        "^error" "," c-string        The operation failed. The c-string contains the corresponding error message.
558
//        "^exit"                                        gdb has terminate
559
//
560
// ASYNCHRONOUS RECORDS
561
// *running,thread-id="thread"
562
// *stopped,reason="reason",thread-id="id",stopped-threads="stopped",core="core"
563
// =thread-group-added,id="id"
564
// =thread-group-removed,id="id"
565
// =thread-group-started,id="id",pid="pid"
566
// =thread-group-exited,id="id"[,exit-code="code"]
567
// =thread-created,id="id",group-id="gid"
568
// =thread-exited,id="id",group-id="gid"
569
// =thread-selected,id="id"
570
// =library-loaded,...
571
// =library-unloaded,...
572
// =breakpoint-created,bkpt={...}
573
// =breakpoint-modified,bkpt={...}
574
// =breakpoint-deleted,bkpt={...}
575
//
576
// FRAME INFO INSIDE RESPONSES
577
// level                The level of the stack frame. The innermost frame has the level of zero. This field is always present.
578
// func                The name of the function corresponding to the frame. This field may be absent if gdb is unable to determine the function name.
579
// addr                The code address for the frame. This field is always present.
580
// file                The name of the source files that correspond to the frame's code address. This field may be absent.
581
// line                The source line corresponding to the frames' code address. This field may be absent.
582
// from                The name of the binary file (either executable or shared library) the corresponds to the frame's code address. This field may be absent. 
583

    
584
// THREAD INFO INSIDE RESPONSES
585
// id                        The numeric id assigned to the thread by gdb. This field is always present.
586
// target-id        Target-specific string identifying the thread. This field is always present.
587
// details                Additional information about the thread provided by the target. It is supposed to be human-readable and not interpreted by the frontend. This field is optional.
588
// state                Either `stopped' or `running', depending on whether the thread is presently running. This field is always present.
589
// core                The value of this field is an integer number of the processor core the thread was last seen on. This field is optional. 
590
//
591
// REMARKS : by now, we just handle synchronous output and check asynchronous one just to detect
592
// debugger run/stop status -- all remaining asynchrnonous output is discarded
593
MIValue Gdb_MI2::MICmd(const char *cmdLine)
594
{
595
        LDUMP(cmdLine);
596
        // sends command to debugger and get result data
597

    
598
        // should handle dbg unexpected termination ?
599
        if(!dbg || !dbg->IsRunning() /* || IdeIsDebugLock() */)
600
                return MIValue();
601

    
602
        // consume previous output from gdb... don't know why sometimes
603
        // is there and gives problems to MI interface. We shall maybe
604
        // parse and store it somewhere
605
        ReadGdb(false);
606
        dbg->Write(String("-") + cmdLine + "\n");
607

    
608
        return ReadGdb();
609
}
610

    
611
// format breakpoint line from ide file and line
612
String Gdb_MI2::BreakPos(String const &file, int line)
613
{
614
        return String().Cat() << Filter(host->GetHostPath(NormalizePath(file)), CharFilterReSlash) << ":" << line;
615
}
616

    
617
// set breakpoint
618
MIValue Gdb_MI2::InsertBreakpoint(const char *file, int line)
619
{
620
        return MICmd("break-insert " + BreakPos(file, line));
621
}
622

    
623
// get breakpoints info
624
MIValue Gdb_MI2::GetBreakpoints(void)
625
{
626
        // issue a break-list command
627
        return MICmd("break-list")["BreakpointTable"];
628
}
629

    
630
MIValue Gdb_MI2::GetBreakpoint(int id)
631
{
632
        return MIValue();
633
}
634

    
635
MIValue Gdb_MI2::GetBreakPoint(const char *file, int line)
636
{
637
        return MIValue();
638
}
639

    
640
bool Gdb_MI2::TryBreak(adr_t addr, bool temp)
641
{
642
        String bp;
643
        if(temp)
644
                bp = "-t ";
645
        bp += Format("*0x%X", (int64)addr);
646

    
647
        MIValue res = MICmd(String("break-insert ") + bp);
648
        return !res.IsError();
649
}
650

    
651
bool Gdb_MI2::TryBreak(String const &file, int line, bool temp)
652
{
653
        String bp;
654
        if(temp)
655
                bp = "-t ";
656
        bp += BreakPos(file, line);
657
        MIValue res = MICmd(String("break-insert ") + bp);
658
        return !res.IsError();
659
}
660

    
661
void Gdb_MI2::SyncDisas(MIValue &fInfo, bool fr)
662
{
663
        if(!disas.IsVisible())
664
                return;
665

    
666
        // get current frame's address
667
        adr_t adr = stou(~fInfo["addr"].Get().Mid(2), NULL, 16);
668
        if(!disas.InRange(adr))
669
        {
670
                MIValue code;
671
                
672
                // if frame is inside a source file, disassemble current function
673
                if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0)
674
                {
675
                        String file = fInfo["file"];
676
                        String line = fInfo["line"];
677
                        code = MICmd(Format("data-disassemble -f %s -l %s -n -1 -- 0", file, line))["asm_insns"];
678
                }
679
                else
680
                        // otherwise disassemble some -100 ... +100 bytes around address
681
                        code = MICmd(Format("data-disassemble -s %x -e %x -- 0", (void *)(adr - 100), (void *)(adr + 100)))["asm_insns"];
682
                        
683
                disas.Clear();
684
                for(int iLine = 0; iLine < code.GetCount(); iLine++)
685
                {
686
                        MIValue &line = code[iLine];
687
                        adr_t address = stou(~line["address"].Get().Mid(2), NULL, 16);
688
                        String inst = line["inst"];
689
                        int spc = inst.Find(' ');
690
                        String opCode, operand;
691
                        if(spc >= 0)
692
                        {
693
                                opCode = inst.Left(spc);
694
                                operand = TrimBoth(inst.Mid(spc));
695
                        }
696
                        else
697
                                opCode = inst;
698
                        disas.Add(address, opCode, operand);
699
                }
700
        }
701
        
702
        // setup disassembler cursor
703
        disas.SetCursor(adr);
704
        disas.SetIp(adr, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr());
705

    
706
        // update registers
707
        MIValue rNames = MICmd("data-list-register-names")["register-names"];
708
        MIValue rValues = MICmd("data-list-register-values x")["register-values"];
709
        Index<String> iNames;
710
        for(int i = 0; i < rNames.GetCount(); i++)
711
                iNames.Add(rNames[i]);
712
        for(int iReg = 0; iReg < regname.GetCount(); iReg++)
713
        {
714
                int i = iNames.Find(regname[iReg]);
715
                String rValue = "****************";
716
                if(i >= 0)
717
                        rValue = "0000000000000000" + rValues[i]["value"].Get().Mid(2);
718
#ifdef CPU_64
719
                rValue = rValue.Right(16);
720
#else
721
                rValue = rValue.Right(8);
722
#endif
723
                if(reglbl[iReg]->GetText() != rValue)
724
                {
725
                        reglbl[iReg]->SetLabel(rValue);
726
                        reglbl[iReg]->SetInk(LtRed);
727
                }
728
                else
729
                        reglbl[iReg]->SetInk(Black);
730
        }
731
                        
732
}
733

    
734
// sync ide display with breakpoint position
735
void Gdb_MI2::SyncIde(bool fr)
736
{
737
        // setup threads droplist
738
        threadSelector.Clear();
739
        MIValue tInfo = MICmd("thread-info");
740
        int curThread = atoi(tInfo["current-thread-id"].Get());
741
        MIValue &threads = tInfo["threads"];
742
        for(int iThread = 0; iThread < threads.GetCount(); iThread++)
743
        {
744
                int id = atoi(threads[iThread]["id"].Get());
745
                if(id == curThread)
746
                {
747
                        threadSelector.Add(id, Format("#%03x(*)", id));
748
                        break;
749
                }
750
        }
751
        threadSelector <<= curThread;
752

    
753
        // get current frame info and level
754
        MIValue fInfo = MICmd("stack-info-frame")["frame"];
755
        int level = atoi(fInfo["level"].Get());
756
        
757
        // if we got file and line info, we can sync the source editor position
758
        autoLine = "";
759
        if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0)
760
        {
761
                String file = GetLocalPath(fInfo["file"]);
762
                int line = atoi(fInfo["line"].Get());
763
                if(FileExists(file))
764
                {
765
                        IdeSetDebugPos(GetLocalPath(file), line - 1, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr(), 0);
766
                        IdeSetDebugPos(GetLocalPath(file), line - 1, disas.HasFocus() ? fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr() : Image(), 1);
767
                        autoLine = IdeGetLine(line - 2) + ' ' + IdeGetLine(line - 1) + ' ' + IdeGetLine(line);
768
                }
769
        }
770

    
771
        // get the arguments for current frame
772
        MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"];
773

    
774
        // setup frame droplist
775
        frame.Clear();
776
        frame.Add(0, FormatFrame(fInfo, fArgs));
777
        frame <<= 0;
778
        
779
        SyncData();
780
        SyncDisas(fInfo, fr);
781
}
782

    
783
// logs frame data on console
784
void Gdb_MI2::LogFrame(String const &msg, MIValue &frame)
785
{
786
        String file = frame("file", "<unknown>");
787
        String line = frame("line", "<unknown>");
788
        String function = frame("function", "<unknown>");
789
        String addr = frame("addr", "<unknown>");
790

    
791
        PutConsole(Format(msg + " at %s, function '%s', file '%s', line %s", addr, function, file, line));
792
}
793

    
794
// stop all running threads and re-select previous current thread
795
void Gdb_MI2::StopAllThreads(void)
796
{
797
        // get thread info for all threads
798
        MIValue tInfo = MICmd("thread-info");
799
        MIValue &threads = tInfo["threads"];
800
        bool someRunning = false;
801
        for(int iThread = 0; iThread < threads.GetCount(); iThread++)
802
        {
803
                if(threads[iThread]["state"].Get() != "stopped")
804
                {
805
                        someRunning = true;
806
                        break;
807
                }
808
        }
809
        // don't issue any stop command if no threads running
810
        // (brings problems....)
811
        if(!someRunning)
812
                return;
813

    
814
        // stores current thread id
815
        String current = tInfo("current-thread-id", "");
816
        
817
        // stops all threads
818
        MICmd("exec-interrupt --all");
819
        
820
        // just to be sure, reads out GDB output
821
        ReadGdb(false);
822
        
823
        // reselect current thread as it was before stopping all others
824
        if(current != "")
825
                MICmd("thread-select " + current);
826
}
827
                
828
// check for stop reason
829
void Gdb_MI2::CheckStopReason(void)
830
{
831
        // we need to store stop reason BEFORE interrupting all other
832
        // threads, otherwise it'll be lost
833
        MIValue stReason = stopReason;
834
        
835
        // get the reason string
836
        String reason;
837
        if(stReason.IsEmpty())
838
                reason = "unknown reason";
839
        else
840
                reason = stReason["reason"];
841

    
842
        // as we are in non-stop mode, to allow async break to work
843
        // we shall stop ALL running threads here, otherwise we'll have
844
        // problems when single stepping a gui MT app
845
        // single step will be done so for a single thread, while other
846
        // are idle. Maybe we could make this behaviour optional
847
        StopAllThreads();
848

    
849
        if(reason == "exited-normally")
850
        {
851
                Stop();
852
                PutConsole("Program exited normally.");
853
        }
854
        else if(reason == "exited")
855
        {
856
                Stop();
857
                PutConsole("Program exited with code ");
858
        }
859
        else if(reason == "breakpoint-hit")
860
        {
861
                LogFrame("Hit breakpoint", stReason["frame"]);
862
                SyncIde();
863
        }
864
        else if(reason == "end-stepping-range")
865
        {
866
                LogFrame("End stepping range", stReason["frame"]);
867
                SyncIde();
868
        }
869
        else if(reason == "unknown reason")
870
        {
871
                PutConsole("Stopped by unknown reason");
872
                SyncIde();
873
        }
874
        else
875
        {
876
                // weird stop reasons (i.e., signals, segfaults... may not have a frame
877
                // data inside
878
                if(stReason.Find("frame") < 0)
879
                        PutConsole(Format("Stopped, reason '%s'", reason));
880
                else
881
                        LogFrame(Format("Stopped, reason '%s'", reason), stReason["frame"]);
882
                SyncIde();
883
        }
884
}
885

    
886
void Gdb_MI2::Step(const char *cmd)
887
{
888
        bool b = disas.HasFocus();
889

    
890
        MIValue res = MICmd(cmd);
891

    
892
        int i = 50;
893
        while(!started && --i)
894
        {
895
                GuiSleep(20);
896
                Ctrl::ProcessEvents();
897
                ReadGdb(false);
898
        }
899
        if(!started)
900
        {
901
                Stop();
902
                Exclamation(t_("Step failed - terminating debugger"));
903
                return;
904
        }
905

    
906
        Lock();
907
        ReadGdb(false);
908
        while(dbg && !stopped)
909
        {
910
                GuiSleep(20);
911
                Ctrl::ProcessEvents();
912
                ReadGdb(false);
913
        }
914
        Unlock();
915

    
916
        firstRun = false;
917
        if(stopped)
918
                CheckStopReason();
919
        started = stopped = false;
920
        IdeActivateBottom();
921

    
922
        // reset focus to disassembly pane if was there before
923
        if(b)
924
                disas.SetFocus();
925
}
926

    
927
// setup ide cursor based on disassembler one
928
void Gdb_MI2::DisasCursor()
929
{
930
        if(!disas.HasFocus())
931
                return;
932

    
933
        // temporary disable disas lost-focus handler
934
        // otherwise cursor will not be correctly set by following code
935
        disas.WhenFocus.Clear();
936

    
937
        // to get info of corresponding file/line of an address, the only way
938
        // we found is to insert a breakpoint, note the position and remove it
939
        MIValue b = MICmd(Format("break-insert *0x%X", (int64)disas.GetCursor()))["bkpt"];
940
        if(b.Find("file") >= 0 && b.Find("line") >= 0)
941
                IdeSetDebugPos(b["file"], atoi(b["line"].Get()) - 1, DbgImg::DisasPtr(), 1);
942
        if(b.Find("number") >= 0)
943
                MICmd(Format("break-delete %s", b["number"].Get()));
944
        disas.SetFocus();
945

    
946
        // re-enable disas lost focus handler
947
        disas.WhenFocus = THISBACK(DisasFocus);
948
}
949

    
950
// reset ide default cursor image when disassembler loose focus
951
void Gdb_MI2::DisasFocus()
952
{
953
        if(!disas.HasFocus())
954
                IdeSetDebugPos(IdeGetFileName(), IdeGetFileLine(), Null, 1);
955
}
956

    
957
// create a string representation of frame given its info and args
958
String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs)
959
{
960
        int idx = atoi(fInfo("level", "-1"));
961
        if(idx < 0)
962
                return t_("invalid frame info");
963
        if(!fArgs.IsArray())
964
                return t_("invalid frame args");
965
        String func = fInfo("func", "<unknown>");
966
        String file = fInfo("file", "<unknown>");
967
        String line = fInfo("line", "<unknown>");
968
        int nArgs = fArgs.GetCount();
969
        String argLine;
970
        for(int iArg = 0; iArg < nArgs; iArg++)
971
        {
972
                argLine += fArgs[iArg]["name"].Get();
973
                if(fArgs[iArg].Find("value") >= 0)
974
                        argLine << "=" << fArgs[iArg]["value"];
975
                argLine << ',';
976
        }
977
        if(!argLine.IsEmpty())
978
                argLine = "(" + argLine.Left(argLine.GetCount() - 1) + ")";
979
        return Format("%02d-%s%s at %s:%s", idx, func, argLine, file, line);
980
}
981

    
982
// re-fills frame's droplist when dropping it
983
void Gdb_MI2::DropFrames()
984
{
985
        int q = ~frame;
986
        frame.Clear();
987
        
988
        // get a list of frames
989
        MIValue frameList = MICmd("stack-list-frames")["stack"];
990
        if(frameList.IsError() || !frameList.IsArray())
991
        {
992
                Exclamation("Couldn't get stack frame list");
993
                return;
994
        }
995
        
996
        // get the arguments for all frames, values just for simple types
997
        MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"];
998
        if(frameArgs.IsError() || !frameArgs.IsArray())
999
        {
1000
                Exclamation("Couldn't get stack arguments list");
1001
                return;
1002
        }
1003
        
1004
        // fill the droplist
1005
        for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++)
1006
        {
1007
                MIValue &fInfo = frameList[iFrame];
1008
                MIValue &fArgs = frameArgs[iFrame]["args"];
1009
                frame.Add(iFrame, FormatFrame(fInfo, fArgs));
1010
        }
1011
        frame <<= q;
1012
}
1013

    
1014
// shows selected stack frame in editor
1015
void Gdb_MI2::ShowFrame()
1016
{
1017
        int i = (int)~frame;
1018
        if(!MICmd(Format("stack-select-frame %d", i)))
1019
        {
1020
                Exclamation(Format(t_("Couldn't select frame #%d"), i));
1021
                return;
1022
        }
1023
        SyncIde(i);
1024
}
1025

    
1026
// re-fills thread selector droplist on drop
1027
void Gdb_MI2::dropThreads()
1028
{
1029
        int q = ~threadSelector;
1030
        threadSelector.Clear();
1031

    
1032
        // get a list of all available threads
1033
        MIValue tInfo = MICmd("thread-info");
1034
        MIValue &threads = tInfo["threads"];
1035
        if(!tInfo.IsTuple() || !threads.IsArray())
1036
        {
1037
                Exclamation(t_("couldn't get thread info"));
1038
                return;
1039
        }
1040
        int currentId = atoi(tInfo["current-thread-id"].Get());
1041
        for(int iThread = 0; iThread < threads.GetCount(); iThread++)
1042
        {
1043
                MIValue &t = threads[iThread];
1044
                int id = atoi(t["id"].Get());
1045
                String threadStr = Format("#%03x%s", id, (id == currentId ? "(*)" : ""));
1046
                threadSelector.Add(id, threadStr);
1047
        }
1048
        threadSelector <<= q;
1049
}
1050

    
1051
// selects current thread
1052
void Gdb_MI2::showThread(void)
1053
{
1054
        int i = (int)~threadSelector;
1055
        MICmd(Format("thread-select %d", i));
1056

    
1057
        SyncIde();        
1058
}
1059

    
1060
// update variables on demand (locals, watches....)
1061
void Gdb_MI2::UpdateVars(void)
1062
{
1063
        MIValue iUpdated = MICmd("var-update 2 *");
1064
        if(!iUpdated.IsTuple() || iUpdated.Find("changelist") < 0)
1065
                return;
1066
        MIValue &updated = iUpdated["changelist"];
1067
        for(int iUpd = 0; iUpd < updated.GetCount(); iUpd++)
1068
        {
1069
                if(!updated[iUpd].IsTuple() || updated[iUpd].Find("name") < 0 || updated[iUpd].Find("value") < 0)
1070
                        return;
1071
                String varName = updated[iUpd]["name"];
1072
                String value = updated[iUpd]["value"];
1073
                int iVar;
1074

    
1075
                // local variables
1076
                if( (iVar = localVarNames.Find(varName)) >= 0)
1077
                        localVarValues[iVar] = value;
1078
                
1079
                // watches
1080
                if( (iVar = watchesNames.Find(varName)) >= 0)
1081
                        watchesValues[iVar] = value;
1082
                
1083
                // autos
1084
                if( (iVar = autosNames.Find(varName)) >= 0)
1085
                        autosValues[iVar] = value;
1086
        }
1087
}
1088

    
1089
struct CapitalLess
1090
{
1091
        bool operator()(String const &a, String const &b) const { return ToUpper(a) < ToUpper(b); }
1092
};
1093

    
1094
// update local variables on demand
1095
void Gdb_MI2::UpdateLocalVars(void)
1096
{
1097
        // we try to speed-up at most reading of local vars; this
1098
        // because we need to keep them updated at each step and
1099
        // re-reading as a whole is too time expensive.
1100
        // So, at first we build a list of local variable NAMES only
1101
        MIValue iLoc = MICmd("stack-list-variables 0");
1102
        if(!iLoc || iLoc.IsEmpty())
1103
                return;
1104
        MIValue &loc = iLoc["variables"];
1105
        if(!loc.IsArray())
1106
                return;
1107
        Index<String>locIdx;
1108
        for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++)
1109
                locIdx.Add(loc[iLoc]["name"]);
1110
        
1111
        // then we 'purge' stored local variables that are not present
1112
        // anymore... that can happen, for example, exiting from a loop
1113
        for(int iVar = localVarNames.GetCount() - 1; iVar >= 0; iVar--)
1114
        {
1115
                if(locIdx.Find(localVarExpressions[iVar]) < 0)
1116
                {
1117
                        String varName = localVarNames.Pop();
1118
                        MICmd("var-delete \"" + varName + "\"");
1119
                        localVarExpressions.Pop();
1120
                        localVarValues.Pop();
1121
                        localVarTypes.Pop();
1122
                }
1123
        }
1124
        
1125
        // then we shall add missing variables
1126
        for(int iLoc = 0; iLoc < locIdx.GetCount(); iLoc++)
1127
        {
1128
                if(localVarExpressions.Find(locIdx[iLoc]) < 0)
1129
                {
1130
                        MIValue var = MICmd(String("var-create - @ \"") + locIdx[iLoc] + "\"");
1131
                        
1132
                        // sometimes it has problem creating vars... maybe because they're
1133
                        // still not active; we just skip them
1134
                        if(var.IsError() || var.IsEmpty())
1135
                                continue;
1136

    
1137
                        localVarNames.Add(var["name"]);
1138
                        localVarExpressions.Add(locIdx[iLoc]);
1139
                        localVarTypes.Add(var["type"]);
1140
                        localVarValues.Add(var["value"]);
1141
                }
1142
        }
1143
        
1144
        // unsorted local vars are ugly and difficult to look at...
1145
        // so we sort them. As we've 4 structures, it's not so simple
1146
        Vector<String> keys;
1147
        keys <<= localVarExpressions.GetKeys();
1148
        IndexSort(keys, localVarTypes, CapitalLess());
1149
        keys <<= localVarExpressions.GetKeys();
1150
        IndexSort(keys, localVarValues, CapitalLess());
1151
        Vector<String> names;
1152
        names <<= localVarNames.GetKeys();
1153
        keys <<= localVarExpressions.GetKeys();
1154
        IndexSort(keys, names, CapitalLess());
1155
        localVarNames = names;
1156
        localVarExpressions = keys;
1157
        
1158
        // and finally, we do an update to refresh changed variables
1159
        UpdateVars();
1160
}
1161

    
1162
// update stored watches values on demand
1163
void Gdb_MI2::UpdateWatches(void)
1164
{
1165
        // gets variable names from control
1166
        Index<String> exprs;
1167
        for(int i = 0; i < watches.GetCount(); i++)
1168
                exprs.Add(watches.Get(i, 0));
1169
        
1170
        // purge stored watches not available anymore
1171
        for(int iVar = watchesNames.GetCount() - 1; iVar >= 0;  iVar--)
1172
        {
1173
                if(exprs.Find(watchesExpressions[iVar]) < 0)
1174
                {
1175
                        String varName = watchesNames.Pop();
1176
                        MICmd("var-delete \"" + varName + "\"");
1177
                        watchesExpressions.Pop();
1178
                        watchesValues.Pop();
1179
                        watchesTypes.Pop();
1180
                }
1181
        }
1182

    
1183
        // then we shall add missing variables
1184
        for(int i = 0; i < exprs.GetCount(); i++)
1185
        {
1186
                if(watchesExpressions.Find(exprs[i]) < 0)
1187
                {
1188
                        // the '@' means we create a dynamic variable
1189
                        MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\"");
1190
                        
1191
                        // sometimes it has problem creating vars... maybe because they're
1192
                        // still not active; we just skip them
1193
                        if(!var || var.IsEmpty() || !var.IsTuple())
1194
                                continue;
1195
                        watchesNames.Add(var["name"]);
1196
                        watchesExpressions.Add(exprs[i]);
1197
                        watchesTypes.Add(var["type"]);
1198
                        watchesValues.Add(var["value"]);
1199
                }
1200
        }
1201

    
1202
        // and finally, we do an update to refresh changed variables
1203
        UpdateVars();
1204
}
1205

    
1206
// update stored auto values on demand
1207
void Gdb_MI2::UpdateAutos(void)
1208
{
1209
        // gets variable names from control
1210
        Index<String> exprs;
1211
        for(int i = 0; i < autos.GetCount(); i++)
1212
                exprs.Add(autos.Get(i, 0));
1213
        
1214
        // purge stored autos not available anymore
1215
        for(int iVar = autosNames.GetCount() - 1; iVar >= 0;  iVar--)
1216
        {
1217
                if(exprs.Find(autosExpressions[iVar]) < 0)
1218
                {
1219
                        String varName = autosNames.Pop();
1220
                        MICmd("var-delete \"" + varName + "\"");
1221
                        autosExpressions.Pop();
1222
                        autosValues.Pop();
1223
                        autosTypes.Pop();
1224
                }
1225
        }
1226

    
1227
        // then we shall add missing variables
1228
        for(int i = 0; i < exprs.GetCount(); i++)
1229
        {
1230
                if(autosExpressions.Find(exprs[i]) < 0)
1231
                {
1232
                        // the '@' means we create a dynamic variable
1233
                        MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\"");
1234
                        
1235
                        // sometimes it has problem creating vars... maybe because they're
1236
                        // still not active; we just skip them
1237
                        if(!var || var.IsEmpty() || !var.IsTuple())
1238
                                continue;
1239
                        autosNames.Add(var["name"]);
1240
                        autosExpressions.Add(exprs[i]);
1241
                        autosTypes.Add(var["type"]);
1242
                        autosValues.Add(var["value"]);
1243
                }
1244
        }
1245

    
1246
        // and finally, we do an update to refresh changed variables
1247
        UpdateVars();
1248
}
1249

    
1250
void Gdb_MI2::SyncLocals()
1251
{
1252
        // update local vars cache, if needed
1253
        UpdateLocalVars();
1254
        
1255
        // reload ide control
1256
        VectorMap<String, String> prev = DataMap2(locals);
1257
        locals.Clear();
1258
        for(int i = 0; i < localVarNames.GetCount(); i++)
1259
                locals.Add(localVarExpressions[i], localVarValues[i], localVarTypes[i]);
1260
        MarkChanged2(prev, locals);
1261
}
1262

    
1263
// sync watches treectrl
1264
void Gdb_MI2::SyncWatches()
1265
{
1266
        // update local watches cache, if needed
1267
        UpdateWatches();
1268
        
1269
        // re-fill the control
1270
        VectorMap<String, String> prev = DataMap2(watches);
1271
        for(int i = 0; i < watches.GetCount(); i++)
1272
        {
1273
                String expr = watches.Get(i, 0);
1274
                int idx = watchesExpressions.Find(expr);
1275
                if(idx >= 0)
1276
                {
1277
                        watches.Set(i, 2, watchesTypes[idx]);
1278
                        watches.Set(i, 1, watchesValues[idx]);
1279
                }
1280
                else
1281
                {
1282
                        watches.Set(i, 2, "");
1283
                        watches.Set(i, 1, t_("<can't evaluate expression>"));
1284
                }
1285
        }
1286
        MarkChanged2(prev, watches);
1287
}
1288

    
1289
// sync auto vars treectrl
1290
void Gdb_MI2::SyncAutos()
1291
{
1292
        VectorMap<String, String> prev = DataMap2(autos);
1293
        autos.Clear();
1294

    
1295
        // read expressions around cursor line
1296
        CParser p(autoLine);
1297
        while(!p.IsEof())
1298
        {
1299
                if(p.IsId())
1300
                {
1301
                        String exp = p.ReadId();
1302
                        for(;;)
1303
                        {
1304
                                if(p.Char('.') && p.IsId())
1305
                                        exp << '.';
1306
                                else
1307
                                if(p.Char2('-', '>') && p.IsId())
1308
                                        exp << "->";
1309
                                else
1310
                                        break;
1311
                                exp << p.ReadId();
1312
                        }
1313
                        if(autos.Find(exp) < 0)
1314
                                autos.Add(exp, "");
1315
                }
1316
                p.SkipTerm();
1317
        }
1318
        autos.Sort();
1319

    
1320
        // update autos cache, if needed
1321
        UpdateAutos();
1322

    
1323
        for(int i = autos.GetCount() -1; i >= 0; i--)
1324
        {
1325
                String expr = autos.Get(i, 0);
1326
                int idx = autosExpressions.Find(expr);
1327
                if(idx >= 0)
1328
                {
1329
                        autos.Set(i, 2, autosTypes[idx]);
1330
                        autos.Set(i, 1, autosValues[idx]);
1331
                }
1332
                else
1333
                        autos.Remove(i);
1334
        }
1335

    
1336
        MarkChanged2(prev, autos);
1337
}
1338

    
1339
// sync data tabs, depending on which tab is shown
1340
void Gdb_MI2::SyncData()
1341
{
1342
        switch(tab.Get()) {
1343
        case 0:
1344
                SyncAutos();
1345
                break;
1346
                
1347
        case 1:
1348
                SyncLocals();
1349
                break;
1350
                
1351
        case 2:
1352
                SyncWatches();
1353
                break;
1354
        }
1355
}
1356

    
1357
// watches arrayctrl key handling
1358
bool Gdb_MI2::Key(dword key, int count)
1359
{
1360
        if(key >= 32 && key < 65535 && tab.Get() == 2)
1361
        {
1362
                watches.DoInsertAfter();
1363
                Ctrl* f = GetFocusCtrl();
1364
                if(f && watches.HasChildDeep(f))
1365
                        f->Key(key, count);
1366
                return true;
1367
        }
1368
        if(key == K_ENTER && explorerExprEdit.HasFocus())
1369
        {
1370
                onExploreExpr();
1371
                return true;
1372
        }
1373
        return Ctrl::Key(key, count);
1374
}
1375

    
1376
// sends pretty-printing scripts
1377
void Gdb_MI2::SendPrettyPrinters(void)
1378
{
1379
        String fName = GetTempFileName();
1380
        fName = fName.Left(fName.GetCount() - 3) + "py";
1381
        SaveFile(fName, (const char *)PrettyPrinters);
1382
        MICmd("interpreter-exec console \"source " + fName + "\"");
1383
        FileDelete(fName);
1384
}
1385

    
1386
// format watch line
1387
String Gdb_MI2::FormatWatchLine(String exp, String const &val, int level)
1388
{
1389
        if(exp.GetCount() < 20)
1390
                exp = exp + String(' ', 20);
1391
        else
1392
                exp = exp.Left(17) + "...";
1393
        exp = exp.Left(20) + " = " + val;
1394
        return String(' ', level * 4) + exp;
1395
}
1396

    
1397
// deep watch current quickwatch variable
1398
void Gdb_MI2::WatchDeep0(String parentExp, String const &var, int level, int &maxRemaining)
1399
{
1400
        // avoid endless recursion for circularly linked vars
1401
        if(--maxRemaining <= 0)
1402
                return;
1403
        
1404
        MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100");
1405
        if(!childInfo || !childInfo.IsTuple())
1406
                return;
1407
        int nChilds = min(atoi(childInfo("numchild", "-1")), 100);
1408
        if(nChilds <= 0)
1409
                return;
1410

    
1411
        MIValue &childs = childInfo["children"];
1412
        for(int i = 0; i < childs.GetCount() && maxRemaining > 0; i++)
1413
        {
1414
                MIValue child = childs[i];
1415
                String exp = child["exp"];
1416
                
1417
                // handle pseudo children...
1418
/*
1419
                while(exp == "public" || exp == "private" || exp == "protected")
1420
                {
1421
                        child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0];
1422
                        exp = child["exp"];
1423
                }
1424
*/
1425
                if(isdigit(exp[0]))
1426
                {
1427
                        exp = '[' + exp + ']';
1428
                        if(parentExp.Mid(parentExp.GetCount() - 1, 1) == ".")
1429
                                parentExp = parentExp.Left(parentExp.GetCount() - 1);
1430
                }
1431
                if(exp[0] != '.' && exp[0] != '[')
1432
                        exp = '.' + exp;
1433

    
1434
                String type = child("type", "");
1435
                if(!type.IsEmpty())
1436
                        type = "(" + type + ")";
1437
                String value = child["value"];
1438
                
1439
                // try to format nicely results...
1440
                quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level);
1441
                
1442
                // recursive deep watch
1443
                WatchDeep0(exp, child["name"], level + 1, maxRemaining);
1444
        }
1445
}
1446

    
1447
void Gdb_MI2::WatchDeep(String parentExp, String const &name)
1448
{
1449
        // this is to avoid circular endless recursion
1450
        // we limit the total watched (sub)variables to this count
1451
        int maxRemaining = 300;
1452
        
1453
        WatchDeep0(parentExp, name, 1, maxRemaining);
1454
}
1455

    
1456
// opens quick watch dialog
1457
void Gdb_MI2::QuickWatch()
1458
{
1459
        // try to figure out if we've got the cursor in some interesting
1460
        // place... if it is, grab the expression from there
1461
        // otherwise let it unchanged
1462
        Ctrl *c = GetFocusCtrl();
1463
        if(typeid(*c) == typeid(AssistEditor))
1464
        {
1465
                AssistEditor *a = dynamic_cast<AssistEditor *>(c);
1466
                String s = a->ReadIdBack(a->GetCursor());
1467
                quickwatch.expression <<= s;
1468
        }
1469
        else if(c == &autos)
1470
        {
1471
                int i = autos.GetCursor();
1472
                if(i >= 0)
1473
                        quickwatch.expression <<= autos.Get(i, 0);
1474
        }
1475
        else if(c == &locals)
1476
        {
1477
                int i = locals.GetCursor();
1478
                if(i >= 0)
1479
                        quickwatch.expression <<= locals.Get(i, 0);
1480
        }
1481
        else if(c == &watches)
1482
        {
1483
                int i = watches.GetCursor();
1484
                if(i >= 0)
1485
                        quickwatch.expression <<= watches.Get(i, 0);
1486
        }
1487
        else if(c == &explorer || c == &explorerExprEdit)
1488
        {
1489
                quickwatch.expression <<= ~explorerExprEdit;
1490
        }
1491
        
1492
        for(;;)
1493
        {
1494
                String exp = ~quickwatch.expression;
1495
                if(!exp.IsEmpty())
1496
                {
1497
                        MIValue v = MICmd("var-create - @ " + exp);
1498
                        if(!v.IsError())
1499
                        {
1500
                                String type = v("type", "");
1501
                                if(!type.IsEmpty())
1502
                                        type = "(" + type + ")";
1503
                                String value = v["value"];
1504
                                quickwatch.value <<= FormatWatchLine(exp, type + value, 0);
1505
                                quickwatch.expression.AddHistory();
1506
                                String name = v["name"];
1507
                                WatchDeep(exp, name);
1508
                                MICmd("var-delete " + name);
1509
                        }
1510
                        else
1511
                                quickwatch.value <<= t_("<can't evaluate expression>");
1512
                }
1513
                else
1514
                        quickwatch.value.Clear();
1515
                int q = quickwatch.Run();
1516
                if(q == IDCANCEL)
1517
                        break;
1518
        }
1519
        quickwatch.Close();
1520
}
1521

    
1522
// copy stack frame list to clipboard
1523
void Gdb_MI2::CopyStack()
1524
{
1525
        DropFrames();
1526
        String s;
1527
        for(int i = 0; i < frame.GetCount(); i++)
1528
                s << frame.GetValue(i) << "\n";
1529
        WriteClipboardText(s);
1530
}
1531

    
1532
// copy disassembly listing to clipboard
1533
void Gdb_MI2::CopyDisas()
1534
{
1535
        disas.WriteClipboard();
1536
}
1537

    
1538
/////////////////////////////////////  EXPLORER //////////////////////////////////////////////////////
1539

    
1540
void Gdb_MI2::doExplore(String const &expr, String var, bool isChild, bool appendHistory)
1541
{
1542
        // set the expression inside expression editor
1543
        explorerExprEdit = expr;
1544

    
1545
        // store current expr as "parent expr" to be used
1546
        // when displaying childs... GDB is not reliable on this
1547
        explorerParentExpr = expr;
1548
        
1549
        // try to find a suitable already created var inside history
1550
        // if not, and if not a child, we shall create it
1551
        if(var.IsEmpty())
1552
        {
1553
                // empty var is allowed ONLY for non-childs exploring
1554
                if(isChild)
1555
                        return;
1556
                
1557
                int iVar = explorerHistoryExpressions.Find(expr);
1558
                if(iVar < 0)
1559
                {
1560
                        MIValue v = MICmd("var-create - @ \"" + expr + "\"");
1561
                        if(v.IsEmpty() || v.IsError())
1562
                        {
1563
                                explorer.Clear();
1564
                                explorerChildVars.Clear();
1565
                                explorer.Add(expr, "<can't evaluate>");
1566
                                return;
1567
                        }
1568
                        var = v["name"];
1569
                }
1570
                else
1571
                        var = explorerHistoryVars[iVar];
1572
        }
1573

    
1574
        // update the history : trim it from past current position
1575
        // and append it at end
1576
        if(appendHistory)
1577
        {
1578
                if(explorerHistoryPos >= 0)
1579
                {
1580
                        // frees all non-child variables following current position
1581
                        for(int i = explorerHistoryPos + 1; i < explorerHistoryVars.GetCount(); i++)
1582
                        {
1583
                                if(!explorerHistoryChilds[i] && explorerHistoryVars[i] != var)
1584
                                {
1585
                                        MICmd("var-delete \"" + explorerHistoryVars[i] + "\"");
1586
                                }
1587
                        }
1588
                        explorerHistoryPos++;
1589
                        explorerHistoryExpressions.Trim(explorerHistoryPos);
1590
                        explorerHistoryVars.Trim(explorerHistoryPos);
1591
                        explorerHistoryChilds.Trim(explorerHistoryPos);
1592
                }
1593
                else
1594
                        explorerHistoryPos = 0;
1595
                explorerHistoryExpressions.Add(expr);
1596
                explorerHistoryVars.Add(var);
1597
                explorerHistoryChilds.Add(isChild);
1598
        }
1599
        
1600
        // here we've finally got variable to explore, just read it
1601
        // and set into first row of explorer ArrayCtrl
1602
        String value = MICmd("var-evaluate-expression \"" + var + "\"")["value"];
1603
        String type = MICmd("var-info-type \"" + var + "\"")["type"];
1604
        explorer.Clear();
1605
        explorerChildVars.Clear();
1606
        explorer.Add("=", "(" + type + ")" + value, var);
1607
        
1608
        // now we shall add variable children... we limit them to a number of 100
1609
        // which should be anough. Alternative would be to display them in ranges,
1610
        // but this over-complicates the application.
1611
        MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100");
1612
        int nChilds = min(atoi(childInfo["numchild"].Get()), 100);
1613
        if(nChilds)
1614
        {
1615
                MIValue &childs = childInfo["children"];
1616
                for(int i = 0; i < childs.GetCount(); i++)
1617
                {
1618
                        MIValue child = childs[i];
1619
                        String exp = child["exp"];
1620
                        
1621
                        // handle pseudo children...
1622
/*
1623
                        while(exp == "public" || exp == "private" || exp == "protected")
1624
                        {
1625
                                child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0];
1626
                                exp = child["exp"];
1627
                        }
1628
*/
1629
                        if(isdigit(exp[0]))
1630
                                exp = '[' + exp + ']';
1631
                        String type = child("type", "");
1632
                        if(!type.IsEmpty())
1633
                                type = "(" + type + ")";
1634
                        String value = child["value"];
1635
                        explorer.Add(exp, type + value);
1636
                        explorerChildVars.Add(child["name"]);
1637
                }
1638
        }
1639
        
1640
        explorerBackBtn.Enable(explorerHistoryPos > 0);
1641
        explorerForwardBtn.Enable(explorerHistoryPos < explorerHistoryVars.GetCount() - 1);
1642
}
1643

    
1644
// 
1645
void Gdb_MI2::onExploreExpr(ArrayCtrl *what)
1646
{
1647
        String expr;
1648
        if(!what)
1649
        {
1650
                // if expression don't come from another ArrayCtrl
1651
                // we use the expression editbox
1652
                expr = ~explorerExprEdit;
1653
        }
1654
        else
1655
        {
1656
                // otherwise, we use the expression from sending ArrayCtrl
1657
                int line = what->GetCursor();
1658
                if(line >= 0)
1659
                        expr = what->Get(line, 0);
1660
        }
1661
        // nothing to do on empty expression
1662
        if(expr == "")
1663
                return;
1664
        doExplore(expr, "", false, true);
1665
        
1666
        // activate explorer tab
1667
        tab.Set(3);
1668
}
1669

    
1670
void Gdb_MI2::onExplorerChild()
1671
{
1672
        // click on first line (value line) does nothing
1673
        int line = explorer.GetCursor();
1674
        if(line < 1)
1675
                return;
1676
        if(--line < explorerChildVars.GetCount())
1677
        {
1678
                String var = explorerChildVars[line];
1679
                String varExp = var;
1680
                String expr = MICmd("var-info-expression \"" + var + "\"")["exp"];
1681
                if(expr[0] != '[' && expr[0] != '.')
1682
                        if(isdigit(expr[0]))
1683
                                expr = '[' + expr + ']';
1684
                        else
1685
                                expr = '.' + expr;
1686
                expr = explorerParentExpr + expr;
1687
                doExplore(expr, var, true, true);
1688
        }
1689
}
1690

    
1691
void Gdb_MI2::onExplorerBack()
1692
{
1693
        if(explorerHistoryPos < 1)
1694
                return;
1695
        explorerHistoryPos--;
1696
        String expr = explorerHistoryExpressions[explorerHistoryPos];
1697
        String var = explorerHistoryVars[explorerHistoryPos];
1698
        bool isChild = explorerHistoryChilds[explorerHistoryPos];
1699
        doExplore(expr, var, isChild, false);
1700
}
1701

    
1702
void Gdb_MI2::onExplorerForward()
1703
{
1704
        if(explorerHistoryPos >= explorerHistoryVars.GetCount() - 1)
1705
                return;
1706
        explorerHistoryPos++;
1707
        String expr = explorerHistoryExpressions[explorerHistoryPos];
1708
        String var = explorerHistoryVars[explorerHistoryPos];
1709
        bool isChild = explorerHistoryChilds[explorerHistoryPos];
1710
        doExplore(expr, var, isChild, false);
1711
}
1712

    
1713
void Gdb_MI2::ExplorerMenu(Bar& bar)
1714
{
1715
}
1716
//////////////////////////////////////////////////////////////////////////////////////////////////////
1717

    
1718
// create GDB process and initializes it
1719
bool Gdb_MI2::Create(One<Host> _host, const String& exefile, const String& cmdline)
1720
{
1721
        host = _host;
1722
        dbg = host->StartProcess("gdb " + GetHostPath(exefile) + " --interpreter=mi -q");
1723
        if(!dbg) {
1724
                Exclamation(t_("Error invoking gdb !"));
1725
                return false;
1726
        }
1727
        IdeSetBottom(*this);
1728
        IdeSetRight(disas);
1729

    
1730
        disas.AddFrame(regs);
1731
        disas.WhenCursor = THISBACK(DisasCursor);
1732
        disas.WhenFocus = THISBACK(DisasFocus);
1733

    
1734
        frame.WhenDrop = THISBACK(DropFrames);
1735
        frame <<= THISBACK(ShowFrame);
1736

    
1737
        threadSelector.WhenDrop = THISBACK(dropThreads);
1738
        threadSelector <<= THISBACK(showThread);
1739

    
1740
        watches.WhenAcceptEdit = THISBACK(SyncData);
1741
        tab <<= THISBACK(SyncData);
1742

    
1743
        // this one will allow asynchronous break of running app
1744
// 2012-07-08 -- DISABLED because of GDB bugs...
1745
//        MICmd("gdb-set target-async 1");
1746
        MICmd("gdb-set pagination off");
1747

    
1748
// Don't enable this one -- brings every sort of bugs with
1749
// It was useful to issue Asynchronous break, but too many bugs
1750
// to be useable
1751
//        MICmd("gdb-set non-stop on");
1752

    
1753
//        MICmd("gdb-set interactive-mode off");
1754

    
1755
        MICmd("gdb-set disassembly-flavor intel");
1756
        MICmd("gdb-set exec-done-display off");
1757
        MICmd("gdb-set annotate 1");
1758
        MICmd("gdb-set height 0");
1759
        MICmd("gdb-set width 0");
1760
        MICmd("gdb-set confirm off");
1761
        MICmd("gdb-set print asm-demangle");
1762
        
1763
        // avoids debugger crash if caught inside ungrabbing function
1764
        // (don't solves all cases, but helps...)
1765
        MICmd("gdb-set unwindonsignal on");
1766

    
1767
        if(!IsNull(cmdline))
1768
                MICmd("gdb-set args " + cmdline);
1769
        
1770
        // enable pretty printing
1771
        SendPrettyPrinters();
1772
        MICmd("enable-pretty-printing");
1773

    
1774
        firstRun = true;
1775

    
1776
        return true;
1777
}
1778

    
1779
One<Debugger> Gdb_MI2Create(One<Host> host, const String& exefile, const String& cmdline)
1780
{
1781
        Gdb_MI2 *dbg = new Gdb_MI2;
1782
        if(!dbg->Create(host, exefile, cmdline))
1783
        {
1784
                delete dbg;
1785
                return NULL;
1786
        }
1787
        return dbg;
1788
}