ide/Debugger: Fixed not being able to find debugged processID
[sdk] / ide / src / debugger / Debugger.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 public import static "ec"
4 #else
5 public import "ecere"
6 public import "ec"
7 #endif
8
9 import "ide"
10 import "process"
11 import "debugFindCtx"
12 import "debugTools"
13
14 #ifdef _DEBUG
15 #define GDB_DEBUG_CONSOLE
16 #define _DEBUG_INST
17 #endif
18
19 extern char * strrchr(const char * s, int c);
20
21 #define uint _uint
22 #define strlen _strlen
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <ctype.h>
26
27 #ifdef __APPLE__
28 #define __unix__
29 #endif
30
31 #if defined(__unix__)
32 #include <sys/stat.h>
33 #include <sys/time.h> // Required on Apple...
34 #endif
35 #undef uint
36 #undef strlen
37
38 char * PrintNow()
39 {
40    int c;
41    char * s[6];
42    char * time;
43    DateTime now;
44    now.GetLocalTime();
45    for(c=0; c<6; c++)
46       s[c] = new char[8];
47    sprintf(s[0], "%04d", now.year);
48    sprintf(s[1], "%02d", now.month+1);
49    sprintf(s[2], "%02d", now.day);
50    sprintf(s[3], "%02d", now.hour);
51    sprintf(s[4], "%02d", now.minute);
52    sprintf(s[5], "%02d", now.second);
53    time = PrintString("*", s[0], s[1], s[2], "-", s[3], s[4], s[5], "*");
54    for(c=0; c<6; c++)
55       delete s[c];
56    return time;
57 }
58
59 // use =0 to disable printing of specific channels
60 #ifdef _DEBUG_INST
61 static enum dplchan { none, gdbProtoIgnored=0/*1*/, gdbProtoUnknown=2, gdbOutput=3/*3*/, gdbCommand=4/*4*/, debuggerCall=0/*5*/, debuggerProblem=6,
62                         debuggerUserAction=7,debuggerState=8, debuggerBreakpoints=9, debuggerWatches=0/*10*/, debuggerTemp=0 };
63 #else
64 static enum dplchan { none, gdbProtoIgnored=0, gdbProtoUnknown=0, gdbOutput=0, gdbCommand=0, debuggerCall=0, debuggerProblem=0,
65                         debuggerUserAction=0,debuggerState=0, debuggerBreakpoints=0, debuggerWatches=0, debuggerTemp=0 };
66 #endif
67 static char * _dpct[] = {
68    null,
69    "GDB Protocol Ignored",
70    "GDB Protocol ***Unknown***",
71    "GDB Output",
72    "GDB Command",
73    ""/*Debugger Call*/,
74    "Debugger ***Problem***",
75    "Debugger::ChangeUserAction",
76    "Debugger::ChangeState",
77    "Breakpoints",
78    "Watches",
79    "-----> Temporary Message",
80    null
81 };
82
83 // TODO if(strlen(item.value) < MAX_F_STRING)
84
85 // Debug Print Line
86 #ifdef _DEBUG_INST
87 #define _dpl2(...) __dpl2(__FILE__, __LINE__, ##__VA_ARGS__)
88 #else
89 #define _dpl2(...)
90 #endif
91 static void __dpl2(char * file, int line, char ** channels, int channel, int indent, typed_object object, ...)
92 {
93    bool chan = channel && channels && channels[channel];
94    if(chan || !channels)
95    {
96       char string[MAX_F_STRING];
97       int len;
98       char * time = PrintNow();
99       va_list args;
100       //ide.outputView.debugBox.Logf();
101       Logf("%s %s:% 5d: %s%s", time, file, line, chan ? channels[channel] : "", chan && channels[channel][0] ? ": " : "");
102       va_start(args, object);
103       len = PrintStdArgsToBuffer(string, sizeof(string), object, args);
104       Log(string);
105       va_end(args);
106       Log("\n");
107       delete time;
108    }
109 }
110
111 #define _dpl(...) __dpl(__FILE__, __LINE__, ##__VA_ARGS__)
112 static void __dpl(char * file, int line, int indent, char * format, ...)
113 {
114    va_list args;
115    char string[MAX_F_STRING];
116    int c;
117    char * time = PrintNow();
118    //static File f = null;
119    va_start(args, format);
120    vsnprintf(string, sizeof(string), format, args);
121    string[sizeof(string)-1] = 0;
122    /*if(!f)
123    {
124       char * time = PrintNow();
125       char * logName;
126       logName = PrintString(time, ".log");
127       delete time;
128       f = FileOpen(logName, write);
129       delete logName;
130    }*/
131    /*f.Printf("%s %s:% 5d: ", time, file, line);
132    for(c = 0; c<indent; c++)
133       f.Putc(' ');
134    f.Printf("%s\n", string);*/
135    Logf("%s %s:% 5d: ", time, file, line);
136    for(c = 0; c<indent; c++)
137       Log(" ");
138    Logf("%s\n", string);
139    va_end(args);
140    delete time;
141 }
142
143 public char * StripQuotes2(char * string, char * output)
144 {
145    int c;
146    int d = 0;
147    bool quoted = false, escaped = false;
148    char ch;
149    for(c = 0; ch = string[c]; c++)
150    {
151       if(quoted)
152       {
153          if(escaped || ch != '\"')
154          {
155             output[d++] = ch;
156             escaped = !escaped && ch == '\\';
157          }
158          else
159             quoted = false;
160       }
161       else if(ch == '\"')
162          quoted = true;
163       else
164          output[d++] = ch;
165    }
166    output[d] = '\0';
167    return output;
168 }
169
170 // String Escape Copy
171 static void strescpy(char * d, char * s)
172 {
173    int j = 0, k = 0;
174    char ch;
175    while((ch = s[j]))
176    {
177       switch(ch)
178       {
179          case '\n': d[k] = '\\'; d[++k] = 'n'; break;
180          case '\t': d[k] = '\\'; d[++k] = 't'; break;
181          case '\a': d[k] = '\\'; d[++k] = 'a'; break;
182          case '\b': d[k] = '\\'; d[++k] = 'b'; break;
183          case '\f': d[k] = '\\'; d[++k] = 'f'; break;
184          case '\r': d[k] = '\\'; d[++k] = 'r'; break;
185          case '\v': d[k] = '\\'; d[++k] = 'v'; break;
186          case '\\': d[k] = '\\'; d[++k] = '\\'; break;
187          case '\"': d[k] = '\\'; d[++k] = '\"'; break;
188          default: d[k] = s[j];
189       }
190       j++, k++;
191    }
192    d[k] = '\0';
193 }
194
195 static char * CopyUnescapedSystemPath(char * p)
196 {
197    char * d = new char[strlen(p) + 1];
198    struscpy(d, p);
199 #if defined(__WIN32__)
200    ChangeCh(d, '/', '\\');
201 #endif
202    return d;
203 }
204
205 static char * CopyUnescapedUnixPath(char * p)
206 {
207    char * d = new char[strlen(p) + 1];
208    struscpy(d, p);
209 #if defined(__WIN32__)
210    ChangeCh(d, '\\', '/');
211 #endif
212    return d;
213 }
214
215 static char * CopyUnescapedString(char * s)
216 {
217    char * d = new char[strlen(s) + 1];
218    struscpy(d, s);
219    return d;
220 }
221
222 // String Unescape Copy
223
224 // TOFIX: THIS DOESN'T HANDLE NUMERIC ESCAPE CODES (OCTAL/HEXADECIMAL...)?
225 // Seems very similar to ReadString in pass15.ec (which also misses numeric escape codes :) )
226
227 static void struscpy(char * d, char * s)
228 {
229    int j = 0, k = 0;
230    char ch;
231    while((ch = s[j]))
232    {
233       switch(ch)
234       {
235          case '\\':
236             switch(s[++j])
237             {
238                case 'n': d[k] = '\n'; break;
239                case 't': d[k] = '\t'; break;
240                case 'a': d[k] = '\a'; break;
241                case 'b': d[k] = '\b'; break;
242                case 'f': d[k] = '\f'; break;
243                case 'r': d[k] = '\r'; break;
244                case 'v': d[k] = '\v'; break;
245                case '\\': d[k] = '\\'; break;
246                case '\"': d[k] = '\"'; break;
247                default: d[k] = '\\'; d[++k] = s[j];
248             }
249             break;
250          default:
251             d[k] = s[j];
252       }
253       j++, k++;
254    }
255    d[k] = '\0';
256 }
257
258 static char * StripBrackets(char * string)
259 {
260    int length = strlen(string);
261    if(length > 1 && *string == '[' && string[length - 1] == ']')
262    {
263       *string = '\0';
264       string[length - 1] = '\0';
265       return ++string;
266    }
267    else
268       return string;
269 }
270
271 static char * StripCurlies(char * string)
272 {
273    int length = strlen(string);
274    if(length > 1 && *string == '{' && string[length - 1] == '}')
275    {
276       *string = '\0';
277       string[length - 1] = '\0';
278       return ++string;
279    }
280    else
281       return string;
282 }
283
284 static int StringGetInt(char * string, int start)
285 {
286    char number[8];
287    int i, len = strlen(string);
288    number[0] = '\0';
289    for(i = start; i < len && i < start + 8; i++)
290    {
291       if(string[i] == '0' || string[i] == '1' || string[i] == '2' || string[i] == '3' || string[i] == '4' || string[i] == '5' || string[i] == '6' || string[i] == '7' || string[i] == '8' || string[i] == '9')
292          strncat(number, &string[i], 1);
293       else
294          break;
295    }
296    return atoi(number);
297 }
298
299 static int TokenizeList(char * string, const char seperator, Array<char *> tokens)
300 {
301    uint level = 0;
302
303    bool quoted = false, escaped = false;
304    char * start = string, ch;
305
306    for(; (ch = *string); string++)
307    {
308       if(!start)
309          start = string;
310
311       if(quoted)
312       {
313          if(escaped || ch != '\"')
314             escaped = !escaped && ch == '\\';
315          else
316             quoted = false;
317       }
318       else if(ch == '\"')
319          quoted = true;
320       else if(ch == '{' || ch == '[' || ch == '(' || ch == '<')
321          level++;
322       else if(ch == '}' || ch == ']' || ch == ')' || ch == '>')
323          level--;
324       else if(ch == seperator && !level)
325       {
326          tokens.Add(start);
327          *string = '\0';
328          start = null;
329       }
330    }
331    if(start)
332    {
333       //tokens[count] = start;
334       //tokens[count++] = start;
335       tokens.Add(start);
336       *string = '\0';
337    }
338    return tokens.count;
339 }
340
341 static bool TokenizeListItem(char * string, DebugListItem item)
342 {
343    char * equal = strstr(string, "=");
344    if(equal)
345    {
346       item.name = string;
347       *equal = '\0';
348       equal++;
349       item.value = equal;
350       return true;
351    }
352    return false;
353 }
354
355 static bool CheckCommandAvailable(const char * command)
356 {
357    bool available = false;
358    int c, count;
359    char * name = new char[MAX_FILENAME];
360    char * pathVar = new char[maxPathLen];
361    char * paths[128];
362    GetEnvironment("PATH", pathVar, maxPathLen);
363    count = TokenizeWith(pathVar, sizeof(paths) / sizeof(char *), paths, pathListSep, false);
364    strcpy(name, command);
365 #ifdef __WIN32__
366    {
367       int e;
368       const char * extensions[] = { "exe", "com", "bat", null };
369       for(e=0; extensions[e]; e++)
370       {
371          ChangeExtension(name, extensions[e], name);
372 #endif
373          for(c=0; c<count; c++)
374          {
375             FileListing fl { paths[c] };
376             while(fl.Find())
377             {
378                if(fl.stats.attribs.isFile && !fstrcmp(fl.name, name))
379                {
380                   available = true;
381                   fl.Stop();
382                   break;
383                }
384             }
385             if(available) break;
386          }
387 #ifdef __WIN32__
388          if(available) break;
389       }
390    }
391 #endif
392    delete name;
393    delete pathVar;
394    return available;
395 }
396
397 // define GdbGetLineSize = 1638400;
398 define GdbGetLineSize = 5638400;
399 #if defined(__unix__)
400 char progFifoPath[MAX_LOCATION];
401 char progFifoDir[MAX_LOCATION];
402 #endif
403
404 enum DebuggerState { none, prompt, loaded, running, stopped, terminated, error };
405 enum DebuggerEvent
406 {
407    none, hit, breakEvent, signal, stepEnd, functionEnd, exit, valgrindStartPause, locationReached;
408
409    property bool canBeMonitored { get { return (this == hit || this == breakEvent || this == signal || this == stepEnd || this == functionEnd || this == locationReached); } };
410 };
411 enum DebuggerAction { none, internal, restart, stop, selectFrame, advance }; //, bpValidation
412 enum DebuggerReason
413 {
414    unknown, endSteppingRange, functionFinished, signalReceived, breakpointHit, locationReached
415    //watchpointTrigger, readWatchpointTrigger, accessWatchpointTrigger, watchpointScope,
416    //exited, exitedNormally, exitedSignalled;
417 };
418 enum BreakpointType
419 {
420    none, internalMain, internalWinMain, internalModulesLoaded, user, runToCursor, internalModuleLoad, internalEntry;
421
422    property bool isInternal { get { return (this == internalMain || this == internalWinMain || this == internalModulesLoaded || this == internalModuleLoad || this == internalEntry); } };
423    property bool isUser { get { return (this == user || this == runToCursor); } };
424 };
425 enum DebuggerEvaluationError { none, symbolNotFound, memoryCantBeRead, unknown };
426 enum DebuggerUserAction
427 {
428    none, start, resume, _break, stop, restart, selectThread, selectFrame, stepInto, stepOver, stepUntil, stepOut, runToCursor;
429    property bool breaksOnInternalBreakpoint { get { return (this == stepInto || this == stepOver || this == stepUntil); } };
430 };
431 enum GdbExecution
432 {
433    none, run, _continue, next, until, advance, step, finish;
434    property bool suspendInternalBreakpoints { get { return (this == until || this == advance || this == step || this == finish); } };
435 };
436
437 FileDialog debuggerFileDialog { type = selectDir };
438
439 static DualPipe vgTargetHandle;
440 static File vgLogFile;
441 static char vgLogPath[MAX_LOCATION];
442 static DualPipe gdbHandle;
443 static DebugEvaluationData eval { };
444
445 static int targetProcessId;
446
447 static bool gdbReady;
448 static bool breakpointError;
449
450 class Debugger
451 {
452    Semaphore serialSemaphore { };
453    bool waitingForPID;
454    bool targeted;
455    bool symbols;
456    bool modules;
457    bool sentKill;
458    bool sentBreakInsert;
459    bool ignoreBreakpoints;
460    bool signalOn;
461    bool needReset;
462    bool usingValgrind;
463
464    int ideProcessId;
465    int gdbProcessId;
466
467    int activeFrameLevel;
468    int activeThread;
469    int hitThread;
470    int signalThread;
471    int frameCount;
472
473    char * targetDir;
474    char * targetFile;
475
476    GdbExecution gdbExecution;
477    DebuggerUserAction userAction;
478    DebuggerState state;
479    DebuggerEvent event;
480    DebuggerAction breakType;
481    char * breakString;
482    //DebuggerCommand lastCommand;    // THE COMPILER COMPILES STUFF THAT DOES NOT EXIST???
483
484    GdbDataStop stopItem;
485    GdbDataBreakpoint bpItem;
486    Frame activeFrame;
487
488    List<Breakpoint> sysBPs { };
489    Breakpoint bpRunToCursor;
490    Breakpoint intBpEntry;
491    Breakpoint intBpMain;
492    Breakpoint intBpWinMain;
493
494    OldList stackFrames;
495
496    CompilerConfig currentCompiler;
497    ProjectConfig prjConfig;
498    int bitDepth;
499
500    CodeEditor codeEditor;
501
502    ValgrindLogThread vgLogThread { debugger = this };
503    ValgrindTargetThread vgTargetThread { debugger = this };
504    GdbThread gdbThread { debugger = this };
505
506    bool entryPoint;
507    Map<String, bool> projectsLibraryLoaded { };
508
509    Timer gdbTimer
510    {
511       delay = 0.0, userData = this;
512
513       bool DelayExpired()
514       {
515          bool monitor = false;
516          DebuggerEvent curEvent = event;
517          GdbDataStop stopItem = this.stopItem;
518          Breakpoint bpUser = null;
519          Breakpoint bpInternal = null;
520
521          if(!gdbReady)
522             return false;
523
524          event = none;
525          if(this.stopItem)
526          {
527             this.stopItem = null;
528 #ifdef _DEBUG_INST
529             {
530                char * s = null;
531                DynamicString bpReport { };
532
533                for(bp : sysBPs; bp.inserted)
534                {
535                   bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")");
536                   delete s;
537                }
538                if(bpRunToCursor && bpRunToCursor.inserted)
539                {
540                   Breakpoint bp = bpRunToCursor;
541                   bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")");
542                   delete s;
543                }
544                for(bp : ide.workspace.breakpoints; bp.inserted)
545                {
546                   bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")");
547                   delete s;
548                }
549                s = bpReport;
550                _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "gdbTimer::DelayExpired: ", s+1);
551
552                if(stopItem.bkptno)
553                {
554                   bool isInternal;
555                   Breakpoint bp = GetBreakpointById(stopItem.bkptno, &isInternal);
556                   if(bp)
557                   {
558                      _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "gdb stopped by a breakpoint: ", bp.type, "(", s=bp.CopyLocationString(false), ")");
559                      delete s;
560                   }
561                }
562                delete bpReport;
563             }
564 #endif
565          }
566 #ifdef _DEBUG_INST
567          else
568          {
569             if(curEvent && curEvent != exit)
570             {
571                _dpl(0, "No stop item");
572             }
573          }
574 #endif
575          switch(breakType)
576          {
577             case restart:
578                breakType = none;
579                Restart(currentCompiler, prjConfig, bitDepth, usingValgrind);
580                break;
581             case stop:
582                breakType = none;
583                Stop();
584                break;
585             case selectFrame:
586             {
587                breakType = none;
588                GdbCommand(false, "-stack-select-frame %d", activeFrameLevel);
589                for(activeFrame = stackFrames.first; activeFrame; activeFrame = activeFrame.next)
590                   if(activeFrame.level == activeFrameLevel)
591                      break;
592                break;
593             }
594             //case bpValidation:
595             //   breakType = none;
596             //   GdbCommand(false, "-break-info %s", bpItem.number);
597             //   break;
598          }
599
600          if(curEvent == none)
601             return false;
602
603          switch(curEvent)
604          {
605             case hit:
606                {
607                   bool isInternal;
608                   Breakpoint bp = stopItem ? GetBreakpointById(stopItem.bkptno, &isInternal) : null;
609                   if(bp && bp.inserted && bp.bp.addr)
610                   {
611                      if(bp.type.isInternal)
612                         bpInternal = bp;
613                      else
614                         bpUser = bp;
615                      if(stopItem && stopItem.frame)
616                      {
617                         if(bpInternal && bpRunToCursor && bpRunToCursor.inserted && !strcmp(bpRunToCursor.bp.addr, bp.bp.addr))
618                            bpUser = bpRunToCursor;
619                         else
620                         {
621                            for(item : (bpInternal ? ide.workspace.breakpoints : sysBPs); item.inserted)
622                            {
623                               if(item.bp && item.bp.addr && !strcmp(item.bp.addr, bp.bp.addr))
624                               {
625                                  if(bpInternal)
626                                     bpUser = item;
627                                  else
628                                     bpInternal = item;
629                                  break;
630                               }
631                            }
632                         }
633                      }
634                      else
635                         _dpl2(_dpct, dplchan::debuggerProblem, 0, "Invalid stopItem!");
636                      if(bpUser && strcmp(stopItem.frame.addr, bpUser.bp.addr))
637                         _dpl2(_dpct, dplchan::debuggerProblem, 0, "Breakpoint bkptno(", stopItem.bkptno, ") address missmatch!");
638                   }
639                   else
640                      _dpl2(_dpct, dplchan::debuggerProblem, 0, "Breakpoint bkptno(", stopItem.bkptno, ") invalid or not found!");
641                   if((bpUser && !ignoreBreakpoints) || (bpInternal && userAction.breaksOnInternalBreakpoint))
642                      monitor = true;
643                   hitThread = stopItem.threadid;
644                }
645                break;
646             case signal:
647                signalThread = stopItem.threadid;
648             case breakEvent:
649             case stepEnd:
650             case functionEnd:
651             case locationReached:
652                monitor = true;
653                ignoreBreakpoints = false;
654                break;
655             case valgrindStartPause:
656                GdbExecContinue(true);
657                monitor = false;
658                break;
659             case exit:
660                HideDebuggerViews();
661                break;
662          }
663
664          if(curEvent == signal)
665          {
666             char * s = null;
667             signalOn = true;
668             ide.outputView.debugBox.Logf($"Signal received: %s - %s\n", stopItem.name, stopItem.meaning);
669             ide.outputView.debugBox.Logf("    %s:%d\n", (s = CopySystemPath(stopItem.frame.file)), stopItem.frame.line);
670             ide.outputView.Show();
671             ide.callStackView.Show();
672             delete s;
673          }
674          else if(curEvent == breakEvent)
675          {
676             ide.threadsView.Show();
677             ide.callStackView.Show();
678             ide.callStackView.Activate();
679          }
680          else if(curEvent == hit)
681          {
682             if(BreakpointHit(stopItem, bpInternal, bpUser))
683             {
684                ide.AdjustDebugMenus();
685                if(bpUser && bpUser.type == runToCursor)
686                {
687                   ignoreBreakpoints = false;
688                   UnsetBreakpoint(bpUser);
689                   delete bpRunToCursor;
690                }
691             }
692             else
693             {
694                if(breakType == advance && bpInternal && (bpInternal.type == internalMain || bpInternal.type == internalEntry))
695                {
696                   breakType = none;
697                   GdbExecAdvance(breakString, 0);
698                   delete breakString;
699                }
700                else
701                {
702                   GdbExecContinue(false);
703                   monitor = false;
704                }
705             }
706          }
707
708          if(monitor && curEvent.canBeMonitored)
709          {
710             GdbGetStack();
711             activeThread = stopItem.threadid;
712             GdbCommand(false, "-thread-list-ids");
713             InternalSelectFrame(activeFrameLevel);
714             GoToStackFrameLine(activeFrameLevel, true, false);
715             EvaluateWatches();
716             ide.ShowCodeEditor();
717             ide.AdjustDebugMenus();
718             ideMainFrame.Activate();   // TOFIX: ide.Activate() is not reliable (app inactive)
719             ide.Update(null);
720          }
721
722          if(stopItem)
723          {
724             stopItem.Free();
725             delete stopItem;
726          }
727          return false;
728       }
729    };
730
731 #ifdef GDB_DEBUG_CONSOLE
732    char lastGdbOutput[GdbGetLineSize];
733 #endif
734 #if defined(__unix__)
735    ProgramThread progThread { };
736 #endif
737
738 #ifdef _DEBUG_INST
739 #define _ChangeUserAction(value) ChangeUserAction(__FILE__, __LINE__, value)
740    void ChangeUserAction(char * file, int line, DebuggerUserAction value)
741    {
742       bool same = value == userAction;
743       __dpl2(file, line, _dpct, dplchan::debuggerUserAction, 0, userAction, /*same ? " *** == *** " : */" -> ", value);
744       userAction = value;
745    }
746 #else
747 #define _ChangeUserAction(value) userAction = value
748 #endif
749
750 #ifdef _DEBUG_INST
751 #define _ChangeState(value) ChangeState(__FILE__, __LINE__, value)
752    void ChangeState(char * file, int line, DebuggerState value)
753 #else
754 #define _ChangeState(value) ChangeState(value)
755    void ChangeState(DebuggerState value)
756 #endif
757    {
758       bool same = value == state;
759 #ifdef _DEBUG_INST
760       __dpl2(file, line, _dpct, dplchan::debuggerState, 0, state, same ? " *** == *** " : " -> ", value);
761 #endif
762       state = value;
763       if(!same) ide.AdjustDebugMenus();
764    }
765
766    void CleanUp()
767    {
768       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::CleanUp");
769
770       stackFrames.Free(Frame::Free);
771
772       delete targetDir;
773       delete targetFile;
774
775       ClearBreakDisplay();
776
777       // Clear Stuff up
778       gdbProcessId = 0;
779
780       waitingForPID = false;
781       targeted = false;
782       symbols = false;
783       modules = false;
784       sentKill = false;
785       sentBreakInsert = false;
786       ignoreBreakpoints = false;
787       signalOn = false;
788
789       activeFrameLevel = 0;
790       activeThread = 0;
791       hitThread = 0;
792       signalThread = 0;
793       frameCount = 0;
794
795       targetDir = null;
796       targetFile = null;
797
798       _ChangeState(none);
799       event = none;
800       breakType = none;
801
802       delete stopItem;
803       delete bpItem;
804       activeFrame = 0;
805
806       bpRunToCursor = null;
807
808       delete currentCompiler;
809       prjConfig = null;
810
811       WatchesReleaseCodeEditor();
812
813       entryPoint = false;
814       projectsLibraryLoaded.Free();
815
816       /*GdbThread gdbThread
817       Timer gdbTimer*/
818    }
819
820    Debugger()
821    {
822       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::constructor");
823       ideProcessId = Process_GetCurrentProcessId();
824
825       sysBPs.Add((intBpEntry = Breakpoint { type = internalEntry, enabled = false, level = -1 }));
826       sysBPs.Add((intBpMain = Breakpoint { type = internalMain, function = "main", enabled = true, level = -1 }));
827 #if defined(__WIN32__)
828       sysBPs.Add((intBpWinMain = Breakpoint { type = internalWinMain, function = "WinMain", enabled = true, level = -1 }));
829 #endif
830       sysBPs.Add(Breakpoint { type = internalModulesLoaded, enabled = true, level = -1 });
831       sysBPs.Add(Breakpoint { type = internalModuleLoad, function = "InternalModuleLoadBreakpoint", enabled = true, level = -1 });
832    }
833
834    ~Debugger()
835    {
836       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::destructor");
837       sysBPs.Free();
838       Stop();
839       CleanUp();
840    }
841
842    // PUBLIC MEMBERS
843
844    property bool isActive { get { return state == running || state == stopped; } }
845    property bool isPrepared  { get { return state == loaded || state == running || state == stopped; } }
846
847    void Resume()
848    {
849       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Resume");
850       _ChangeUserAction(resume);
851       GdbExecContinue(true);
852    }
853
854    void Break()
855    {
856       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Break");
857       _ChangeUserAction(_break);
858       if(state == running)
859       {
860          if(targetProcessId)
861             GdbDebugBreak(false);
862       }
863    }
864
865    void Stop()
866    {
867       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Stop");
868       _ChangeUserAction(stop);
869       switch(state)
870       {
871          case running:
872             if(targetProcessId)
873             {
874                breakType = stop;
875                GdbDebugBreak(false);
876             }
877             break;
878          case stopped:
879             GdbAbortExec();
880             HideDebuggerViews();
881             GdbExit();
882             break;
883          case loaded:
884             GdbExit();
885             break;
886       }
887    }
888
889    void Restart(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind)
890    {
891       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Restart");
892       _ChangeUserAction(restart);
893       if(StartSession(compiler, config, bitDepth, useValgrind, true, false) == loaded)
894          GdbExecRun();
895    }
896
897    bool GoToCodeLine(char * location)
898    {
899       CodeLocation codloc;
900       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GoToCodeLine(", location, ")");
901       codloc = CodeLocation::ParseCodeLocation(location);
902       if(codloc)
903       {
904          CodeEditor editor = (CodeEditor)ide.OpenFile(codloc.absoluteFile, normal, true, null, no, normal, false);
905          if(editor)
906          {
907             EditBox editBox = editor.editBox;
908             editBox.GoToLineNum(codloc.line - 1);
909             editBox.GoToPosition(editBox.line, codloc.line - 1, 0);
910             return true;
911          }
912       }
913       return false;
914    }
915
916    bool GoToStackFrameLine(int stackLevel, bool askForLocation, bool fromCallStack)
917    {
918       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GoToStackFrameLine(", stackLevel, ", ", askForLocation, ")");
919       if(ide)
920       {
921          char filePath[MAX_LOCATION];
922          char sourceDir[MAX_LOCATION];
923          Frame frame;
924          CodeEditor editor = null;
925          if(stackLevel == -1)  // this (the two lines) is part of that fix that I would not put in for some time
926             return false;
927          for(frame = stackFrames.first; frame; frame = frame.next)
928             if(frame.level == stackLevel)
929                break;
930          if(frame)
931          {
932             if(!fromCallStack)
933                ide.callStackView.Show();
934
935             if(frame.absoluteFile)
936                editor = (CodeEditor)ide.OpenFile(frame.absoluteFile, normal, true, null, no, normal, false);
937             if(!editor && frame.file)
938                frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file);
939             if(!frame.absoluteFile && askForLocation && frame.file)
940             {
941                char * s = null;
942                char title[MAX_LOCATION];
943                snprintf(title, sizeof(title), $"Provide source file location for %s", (s = CopySystemPath(frame.file)));
944                title[sizeof(title)-1] = 0;
945                delete s;
946                if(SourceDirDialog(title, ide.workspace.projectDir, frame.file, sourceDir))
947                {
948                   AddSourceDir(sourceDir);
949                   frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file);
950                }
951             }
952             if(!editor && frame.absoluteFile)
953                editor = (CodeEditor)ide.OpenFile(frame.absoluteFile, normal, true, null, no, normal, false);
954             if(editor)
955                ide.RepositionWindows(false);
956             ide.Update(null);
957             if(editor && frame.line)
958             {
959                EditBox editBox = editor.editBox;
960                editBox.GoToLineNum(frame.line - 1);
961                editBox.GoToPosition(editBox.line, frame.line - 1, 0);
962                return true;
963             }
964          }
965       }
966       return false;
967    }
968
969    void SelectThread(int thread)
970    {
971       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SelectThread(", thread, ")");
972       _ChangeUserAction(selectThread);
973       if(state == stopped)
974       {
975          if(thread != activeThread)
976          {
977             activeFrameLevel = -1;
978             ide.callStackView.Clear();
979             GdbCommand(false, "-thread-select %d", thread);
980             GdbGetStack();
981             InternalSelectFrame(activeFrameLevel);
982             GoToStackFrameLine(activeFrameLevel, true, false);
983             EvaluateWatches();
984             ide.Update(null);
985          }
986          ide.callStackView.Show();
987       }
988    }
989
990    void SelectFrame(int frame)
991    {
992       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SelectFrame(", frame, ")");
993       _ChangeUserAction(selectFrame);
994       if(state == stopped)
995       {
996          if(frame != activeFrameLevel)
997          {
998             InternalSelectFrame(frame);
999             EvaluateWatches();
1000             ide.Update(null);
1001          }
1002       }
1003    }
1004
1005    void InternalSelectFrame(int frame)
1006    {
1007       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::InternalSelectFrame(", frame, ")");
1008       activeFrameLevel = frame;  // there is no active frame number in the gdb reply
1009       GdbCommand(false, "-stack-select-frame %d", activeFrameLevel);
1010       for(activeFrame = stackFrames.first; activeFrame; activeFrame = activeFrame.next)
1011          if(activeFrame.level == activeFrameLevel)
1012             break;
1013    }
1014
1015    void HandleExit(char * reason, char * code)
1016    {
1017       bool returnedExitCode = false;
1018       char verboseExitCode[128];
1019
1020       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::HandleExit(", reason, ", ", code, ")");
1021       _ChangeState(loaded); // this state change seems to be superfluous, might be in case of gdb crash
1022       targetProcessId = 0;
1023
1024       if(code)
1025       {
1026          snprintf(verboseExitCode, sizeof(verboseExitCode), $" with exit code %s", code);
1027          verboseExitCode[sizeof(verboseExitCode)-1] = 0;
1028       }
1029       else
1030          verboseExitCode[0] = '\0';
1031
1032       event = exit;
1033
1034       // ClearBreakDisplay();
1035
1036       if(ide.workspace)
1037       {
1038          for(wh : ide.workspace.watches)
1039          {
1040             if(wh.type) FreeType(wh.type);
1041             wh.type = null;
1042             delete wh.value;
1043             ide.watchesView.UpdateWatch(wh);
1044          }
1045       }
1046
1047 #if defined(__unix__)
1048       if(!usingValgrind)
1049       {
1050          progThread.terminate = true;
1051          if(fifoFile)
1052          {
1053             fifoFile.CloseInput();
1054             app.Unlock();
1055             progThread.Wait();
1056             app.Lock();
1057             delete fifoFile;
1058          }
1059       }
1060 #endif
1061
1062       {
1063          char program[MAX_LOCATION];
1064          GetSystemPathBuffer(program, targetFile);
1065          if(!reason)
1066             ide.outputView.debugBox.Logf($"The program %s has exited%s.\n", program, verboseExitCode);
1067          else if(!strcmp(reason, "exited-normally"))
1068             ide.outputView.debugBox.Logf($"The program %s has exited normally%s.\n", program, verboseExitCode);
1069          else if(!strcmp(reason, "exited"))
1070             ide.outputView.debugBox.Logf($"The program %s has exited%s.\n", program, verboseExitCode);
1071          else if(!strcmp(reason, "exited-signalled"))
1072             ide.outputView.debugBox.Logf($"The program %s has exited with a signal%s.\n", program, verboseExitCode);
1073          else
1074             ide.outputView.debugBox.Logf($"The program %s has exited (gdb provided an unknown reason)%s.\n", program, verboseExitCode);
1075       }
1076       ide.Update(null);
1077    }
1078
1079    DebuggerState StartSession(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, bool restart, bool ignoreBreakpoints)
1080    {
1081       DebuggerState result = none;
1082       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StartSession(restart(", restart, "), ignoreBreakpoints(", ignoreBreakpoints, ")");
1083       if(restart && state == running && targetProcessId)
1084       {
1085          breakType = DebuggerAction::restart;
1086          GdbDebugBreak(false);
1087       }
1088       else
1089       {
1090          if(restart && state == stopped)
1091             GdbAbortExec();
1092          if(needReset && state == loaded)
1093             GdbExit(); // this reset is to get a clean state with all the breakpoints until a better state can be maintained on program exit
1094          result = state;
1095          if(result == none || result == terminated)
1096          {
1097             ide.outputView.ShowClearSelectTab(debug);
1098             ide.outputView.debugBox.Logf($"Starting debug mode\n");
1099
1100             for(bp : sysBPs)
1101             {
1102                bp.hits = 0;
1103                bp.breaks = 0;
1104             }
1105             for(bp : ide.workspace.breakpoints)
1106             {
1107                bp.hits = 0;
1108                bp.breaks = 0;
1109             }
1110
1111             if(GdbInit(compiler, config, bitDepth, useValgrind))
1112                result = state;
1113             else
1114                result = error;
1115          }
1116          this.ignoreBreakpoints = ignoreBreakpoints;
1117       }
1118       return result;
1119    }
1120
1121    void Start(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind)
1122    {
1123       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Start()");
1124       _ChangeUserAction(start);
1125       if(StartSession(compiler, config, bitDepth, useValgrind, true, false) == loaded)
1126          GdbExecRun();
1127    }
1128
1129    void StepInto(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind)
1130    {
1131       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepInto()");
1132       _ChangeUserAction(stepInto);
1133       switch(StartSession(compiler, config, bitDepth, useValgrind, false, false))
1134       {
1135          case loaded:  GdbExecRun();  break;
1136          case stopped: GdbExecStep(); break;
1137       }
1138    }
1139
1140    void StepOver(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, bool ignoreBreakpoints)
1141    {
1142       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepOver()");
1143       _ChangeUserAction(stepOver);
1144       switch(StartSession(compiler, config, bitDepth, useValgrind, false, ignoreBreakpoints))
1145       {
1146          case loaded:  GdbExecRun();  break;
1147          case stopped: GdbExecNext(); break;
1148       }
1149    }
1150
1151    void StepUntil(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, bool ignoreBreakpoints)
1152    {
1153       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepUntil()");
1154       _ChangeUserAction(stepUntil);
1155       switch(StartSession(compiler, config, bitDepth, useValgrind, false, ignoreBreakpoints))
1156       {
1157          case loaded:  GdbExecRun();          break;
1158          case stopped: GdbExecUntil(null, 0); break;
1159       }
1160    }
1161
1162    void StepOut(bool ignoreBreakpoints)
1163    {
1164       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepOut()");
1165       _ChangeUserAction(stepOut);
1166       if(state == stopped)
1167       {
1168          this.ignoreBreakpoints = ignoreBreakpoints;
1169          if(frameCount > 1)
1170             GdbExecFinish();
1171          else
1172             GdbExecContinue(true);
1173       }
1174    }
1175
1176    void RunToCursor(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, char * absoluteFilePath, int lineNumber, bool ignoreBreakpoints, bool atSameLevel, bool oldImplementation)
1177    {
1178       char relativeFilePath[MAX_LOCATION];
1179       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::RunToCursor()");
1180       _ChangeUserAction(runToCursor);
1181       WorkspaceGetRelativePath(absoluteFilePath, relativeFilePath, null);
1182
1183       if(bpRunToCursor && bpRunToCursor.inserted && symbols)
1184       {
1185          UnsetBreakpoint(bpRunToCursor);
1186          delete bpRunToCursor;
1187       }
1188
1189       StartSession(compiler, config, bitDepth, useValgrind, false, ignoreBreakpoints);
1190
1191 #if 0
1192       if(oldImplementation)
1193       {
1194          bpRunToCursor = Breakpoint { };
1195          bpRunToCursor.absoluteFilePath = absoluteFilePath;
1196          bpRunToCursor.relativeFilePath = relativeFilePath;
1197          bpRunToCursor.line = lineNumber;
1198          bpRunToCursor.type = runToCursor;
1199          bpRunToCursor.enabled = true;
1200          bpRunToCursor.level = atSameLevel ? frameCount - activeFrameLevel -1 : -1;
1201       }
1202 #endif
1203       if(state == loaded)
1204       {
1205          breakType = advance;
1206          breakString = PrintString(relativeFilePath, ":", lineNumber);
1207          GdbExecRun();
1208       }
1209       else if(state == stopped)
1210       {
1211          if(oldImplementation)
1212             GdbExecContinue(true);
1213          else
1214          {
1215             if(atSameLevel)
1216                GdbExecUntil(absoluteFilePath, lineNumber);
1217             else
1218                GdbExecAdvance(absoluteFilePath, lineNumber);
1219          }
1220       }
1221    }
1222
1223    void GetCallStackCursorLine(bool * error, int * lineCursor, int * lineTopFrame)
1224    {
1225       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GetCallStackCursorLine()");
1226       if(activeFrameLevel == -1)
1227       {
1228          *error = false;
1229          *lineCursor = 0;
1230          *lineTopFrame = 0;
1231       }
1232       else
1233       {
1234          *error = signalOn && activeThread == signalThread;
1235          *lineCursor = activeFrameLevel - ((frameCount > 192 && activeFrameLevel > 191) ? frameCount - 192 - 1 : 0) + 1;
1236          *lineTopFrame = activeFrameLevel ? 1 : 0;
1237       }
1238    }
1239
1240    int GetMarginIconsLineNumbers(char * fileName, int lines[], bool enabled[], int max, bool * error, int * lineCursor, int * lineTopFrame)
1241    {
1242       char winFilePath[MAX_LOCATION];
1243       char * absoluteFilePath = GetSlashPathBuffer(winFilePath, fileName);
1244       int count = 0;
1245       Iterator<Breakpoint> it { ide.workspace.breakpoints };
1246       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GetMarginIconsLineNumbers()");
1247       while(it.Next() && count < max)
1248       {
1249          Breakpoint bp = it.data;
1250          if(bp.type == user)
1251          {
1252             if(bp.absoluteFilePath && bp.absoluteFilePath[0] && !fstrcmp(bp.absoluteFilePath, absoluteFilePath))
1253             {
1254                lines[count] = bp.line;
1255                enabled[count] = bp.enabled;
1256                count++;
1257             }
1258          }
1259       }
1260       if(activeFrameLevel == -1)
1261       {
1262          *error = false;
1263          *lineCursor = 0;
1264          *lineTopFrame = 0;
1265       }
1266       else
1267       {
1268          *error = signalOn && activeThread == signalThread;
1269          if(activeFrame && activeFrame.absoluteFile && !fstrcmp(absoluteFilePath, activeFrame.absoluteFile))
1270             *lineCursor = activeFrame.line;
1271          else
1272             *lineCursor = 0;
1273          if(activeFrame && stopItem && stopItem.frame && activeFrame.level == stopItem.frame.level)
1274             *lineTopFrame = 0;
1275          else if(stopItem && stopItem.frame && stopItem.frame.absoluteFile && !fstrcmp(absoluteFilePath, stopItem.frame.absoluteFile))
1276             *lineTopFrame = stopItem.frame.line;
1277          else
1278             *lineTopFrame = 0;
1279
1280          if(*lineTopFrame == *lineCursor && *lineTopFrame)
1281             *lineTopFrame = 0;
1282       }
1283       return count;
1284    }
1285
1286    void ChangeWatch(DataRow row, char * expression)
1287    {
1288       Watch wh = (Watch)row.tag;
1289       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ChangeWatch(", expression, ")");
1290       if(wh)
1291       {
1292          delete wh.expression;
1293          if(expression)
1294             wh.expression = CopyString(expression);
1295          else
1296          {
1297             Iterator<Watch> it { ide.workspace.watches };
1298             if(it.Find(wh))
1299                ide.workspace.watches.Delete(it.pointer);
1300          }
1301       }
1302       else if(expression)
1303       {
1304          wh = Watch { };
1305          row.tag = (int64)wh;
1306          ide.workspace.watches.Add(wh);
1307          wh.row = row;
1308          wh.expression = CopyString(expression);
1309       }
1310       ide.workspace.Save();
1311       //if(expression && state == stopped)
1312       if(expression)
1313          ResolveWatch(wh);
1314    }
1315
1316    void MoveIcons(char * fileName, int lineNumber, int move, bool start)
1317    {
1318       char winFilePath[MAX_LOCATION];
1319       char * absoluteFilePath = GetSlashPathBuffer(winFilePath, fileName);
1320
1321       Link bpLink, next;
1322       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::MoveIcons()");
1323       for(bpLink = ide.workspace.breakpoints.first; bpLink; bpLink = next)
1324       {
1325          Breakpoint bp = (Breakpoint)bpLink.data;
1326          next = bpLink.next;
1327
1328          if(bp.type == user && bp.absoluteFilePath && !fstrcmp(bp.absoluteFilePath, absoluteFilePath))
1329          {
1330             if(bp.line > lineNumber || (bp.line == lineNumber && start))
1331             {
1332                if(move < 0 && (bp.line < lineNumber - move))
1333                   ide.workspace.RemoveBreakpoint(bp);
1334                else
1335                {
1336                   bp.line += move;
1337                   ide.breakpointsView.UpdateBreakpoint(bp.row);
1338                   ide.workspace.Save();
1339                }
1340             }
1341          }
1342       }
1343
1344       // moving code cursors is futile, on next step, stop, hit, cursors will be offset anyways
1345    }
1346
1347    bool SourceDirDialog(char * title, char * startDir, char * test, char * sourceDir)
1348    {
1349       bool result;
1350       bool retry;
1351       String srcDir = null;
1352
1353       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SourceDirDialog()");
1354       debuggerFileDialog.text = title;
1355       debuggerFileDialog.currentDirectory = startDir;
1356       debuggerFileDialog.master = ide;
1357
1358       while(debuggerFileDialog.Modal())
1359       {
1360          strcpy(sourceDir, debuggerFileDialog.filePath);
1361          if(!fstrcmp(ide.workspace.projectDir, sourceDir) &&
1362                   MessageBox { type = yesNo, master = ide,
1363                               contents = $"This is the project directory.\nWould you like to try again?",
1364                               text = $"Invalid Source Directory" }.Modal() == no)
1365             return false;
1366          else
1367          {
1368             for(dir : ide.workspace.sourceDirs)
1369             {
1370                if(!fstrcmp(dir, sourceDir))
1371                {
1372                   srcDir = dir;
1373                   break;
1374                }
1375             }
1376
1377             if(srcDir &&
1378                   MessageBox { type = yesNo, master = ide,
1379                               contents = $"This source directory is already specified.\nWould you like to try again?",
1380                               text = $"Invalid Source Directory" }.Modal() == no)
1381                return false;
1382             else
1383             {
1384                if(test)
1385                {
1386                   char file[MAX_LOCATION];
1387                   strcpy(file, sourceDir);
1388                   PathCat(file, test);
1389                   result = FileExists(file);
1390                   if(!result &&
1391                         MessageBox { type = yesNo, master = ide,
1392                                     contents = $"Unable to locate source file.\nWould you like to try again?",
1393                                     text = $"Invalid Source Directory" }.Modal() == no)
1394                         return false;
1395                }
1396                else
1397                   result = true;
1398
1399                if(result)
1400                   return true;
1401             }
1402          }
1403       }
1404       return false;
1405    }
1406
1407    void AddSourceDir(char * sourceDir)
1408    {
1409       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::AddSourceDir(", sourceDir, ")");
1410       ide.workspace.sourceDirs.Add(CopyString(sourceDir));
1411       ide.workspace.Save();
1412
1413       if(targeted)
1414       {
1415          DebuggerState oldState = state;
1416          switch(state)
1417          {
1418             case running:
1419                if(targetProcessId)
1420                   GdbDebugBreak(true);
1421             case stopped:
1422             case loaded:
1423                GdbCommand(false, "-environment-directory \"%s\"", sourceDir);
1424                break;
1425          }
1426          if(oldState == running)
1427             GdbExecContinue(false);
1428       }
1429    }
1430
1431    void ToggleBreakpoint(char * fileName, int lineNumber)
1432    {
1433       char absolutePath[MAX_LOCATION];
1434       Breakpoint bp = null;
1435
1436       _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::ToggleBreakpoint(", fileName, ":", lineNumber, ")");
1437
1438       GetSlashPathBuffer(absolutePath, fileName);
1439       for(i : ide.workspace.breakpoints; i.type == user && i.absoluteFilePath && !fstrcmp(i.absoluteFilePath, absolutePath) && i.line == lineNumber)
1440       {
1441          bp = i;
1442          break;
1443       }
1444       if(bp)
1445       {
1446          if(bp.enabled)
1447          {
1448             ide.workspace.RemoveBreakpoint(bp);
1449             bp = null;
1450          }
1451          else
1452             bp.enabled = true;
1453       }
1454       else
1455       {
1456          Project owner;
1457          char relativePath[MAX_LOCATION];
1458
1459          WorkspaceGetRelativePath(absolutePath, relativePath, &owner);
1460
1461          if(!owner && !FileExists(absolutePath))
1462          {
1463             char title[MAX_LOCATION];
1464             char directory[MAX_LOCATION];
1465             char sourceDir[MAX_LOCATION];
1466             StripLastDirectory(absolutePath, directory);
1467             snprintf(title, sizeof(title), $"Provide source files location directory for %s", relativePath);
1468             title[sizeof(title)-1] = 0;
1469             while(true)
1470             {
1471                String srcDir = null;
1472                for(dir : ide.workspace.sourceDirs)
1473                {
1474                   if(IsPathInsideOf(absolutePath, dir))
1475                   {
1476                      MakePathRelative(absolutePath, dir, relativePath);
1477                      srcDir = dir;
1478                      break;
1479                   }
1480                }
1481                if(srcDir)
1482                   break;
1483
1484                if(SourceDirDialog(title, directory, null, sourceDir))
1485                {
1486                   if(IsPathInsideOf(absolutePath, sourceDir))
1487                   {
1488                      AddSourceDir(sourceDir);
1489                      MakePathRelative(absolutePath, sourceDir, relativePath);
1490                      break;
1491                   }
1492                   else if(MessageBox { type = yesNo, master = ide,
1493                                  contents = $"You must provide a valid source directory in order to place a breakpoint in this file.\nWould you like to try again?",
1494                                  text = $"Invalid Source Directory" }.Modal() == no)
1495                      return;
1496                }
1497                else
1498                   return;
1499             }
1500          }
1501          ide.workspace.bpCount++;
1502          bp = { line = lineNumber, type = user, enabled = true, level = -1, project = owner };
1503          ide.workspace.breakpoints.Add(bp);
1504          bp.absoluteFilePath = absolutePath;
1505          bp.relativeFilePath = relativePath;
1506          ide.breakpointsView.AddBreakpoint(bp);
1507       }
1508
1509       if(bp && targeted)
1510       {
1511          DebuggerState oldState = state;
1512          switch(state)
1513          {
1514             case running:
1515                if(targetProcessId)
1516                   GdbDebugBreak(true);
1517             case stopped:
1518             case loaded:
1519                SetBreakpoint(bp, false);
1520                break;
1521          }
1522          if(oldState == running)
1523             GdbExecContinue(false);
1524       }
1525
1526       ide.workspace.Save();
1527    }
1528
1529    void UpdateRemovedBreakpoint(Breakpoint bp)
1530    {
1531       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::UpdateRemovedBreakpoint()");
1532       if(targeted && bp.inserted)
1533       {
1534          DebuggerState oldState = state;
1535          switch(state)
1536          {
1537             case running:
1538                if(targetProcessId)
1539                   GdbDebugBreak(true);
1540             case stopped:
1541             case loaded:
1542                UnsetBreakpoint(bp);
1543                break;
1544          }
1545          if(oldState == running)
1546             GdbExecContinue(false);
1547       }
1548    }
1549
1550    // PRIVATE MEMBERS
1551
1552    void ParseFrame(Frame frame, char * string)
1553    {
1554       int i, j, k;
1555       Array<char *> frameTokens { minAllocSize = 50 };
1556       Array<char *> argsTokens { minAllocSize = 50 };
1557       Array<char *> argumentTokens { minAllocSize = 50 };
1558       DebugListItem item { };
1559       Argument arg;
1560
1561       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ParseFrame()");
1562       TokenizeList(string, ',', frameTokens);
1563       for(i = 0; i < frameTokens.count; i++)
1564       {
1565          if(TokenizeListItem(frameTokens[i], item))
1566          {
1567             StripQuotes(item.value, item.value);
1568             if(!strcmp(item.name, "level"))
1569                frame.level = atoi(item.value);
1570             else if(!strcmp(item.name, "addr"))
1571                frame.addr = item.value;
1572             else if(!strcmp(item.name, "func"))
1573                frame.func = item.value;
1574             else if(!strcmp(item.name, "args"))
1575             {
1576                if(!strcmp(item.value, "[]"))
1577                   frame.argsCount = 0;
1578                else
1579                {
1580                   item.value = StripBrackets(item.value);
1581                   TokenizeList(item.value, ',', argsTokens);
1582                   for(j = 0; j < argsTokens.count; j++)
1583                   {
1584                      argsTokens[j] = StripCurlies(argsTokens[j]);
1585                      TokenizeList(argsTokens[j], ',', argumentTokens);
1586                      for(k = 0; k < argumentTokens.count; k++)
1587                      {
1588                         arg = Argument { };
1589                         frame.args.Add(arg);
1590                         if(TokenizeListItem(argumentTokens[k], item))
1591                         {
1592                            if(!strcmp(item.name, "name"))
1593                            {
1594                               StripQuotes(item.value, item.value);
1595                               arg.name = item.value;
1596                            }
1597                            else if(!strcmp(item.name, "value"))
1598                            {
1599                               StripQuotes(item.value, item.value);
1600                               arg.val = item.value;
1601                            }
1602                            else
1603                               _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "frame args item (", item.name, "=", item.value, ") is unheard of");
1604                         }
1605                         else
1606                            _dpl(0, "Bad frame args item");
1607                      }
1608                      argumentTokens.RemoveAll();
1609                   }
1610                   frame.argsCount = argsTokens.count;
1611                   argsTokens.RemoveAll();
1612                }
1613             }
1614             else if(!strcmp(item.name, "from"))
1615                frame.from = item.value;
1616             else if(!strcmp(item.name, "file"))
1617                frame.file = item.value;
1618             else if(!strcmp(item.name, "line"))
1619                frame.line = atoi(item.value);
1620             else if(!strcmp(item.name, "fullname"))
1621                frame.absoluteFile = item.value;
1622             /*{
1623                // GDB 6.3 on OS X is giving "fullname" and "dir", all in absolute, but file name only in 'file'
1624                String path = ide.workspace.GetPathWorkspaceRelativeOrAbsolute(item.value);
1625                if(strcmp(frame.file, path))
1626                {
1627                   frame.file = path;
1628                   frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file);
1629                }
1630                delete path;
1631             }*/
1632             else
1633                _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "frame member (", item.name, "=", item.value, ") is unheard of");
1634          }
1635          else
1636             _dpl(0, "Bad frame");
1637       }
1638
1639       delete frameTokens;
1640       delete argsTokens;
1641       delete argumentTokens;
1642       delete item;
1643    }
1644
1645    Breakpoint GetBreakpointById(int id, bool * isInternal)
1646    {
1647       Breakpoint bp = null;
1648       //_dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::GetBreakpointById(", id, ")");
1649       if(isInternal)
1650          *isInternal = false;
1651       if(id)
1652       {
1653          for(i : sysBPs; i.bp && i.bp.id == id)
1654          {
1655             if(isInternal)
1656                *isInternal = true;
1657             bp = i;
1658             break;
1659          }
1660          if(!bp && bpRunToCursor && bpRunToCursor.bp && bpRunToCursor.bp.id == id)
1661             bp = bpRunToCursor;
1662          if(!bp)
1663          {
1664             for(i : ide.workspace.breakpoints; i.bp && i.bp.id == id)
1665             {
1666                bp = i;
1667                break;
1668             }
1669          }
1670       }
1671       return bp;
1672    }
1673
1674    GdbDataBreakpoint ParseBreakpoint(char * string, Array<char *> outTokens)
1675    {
1676       int i;
1677       GdbDataBreakpoint bp { };
1678       DebugListItem item { };
1679       Array<char *> bpTokens { minAllocSize = 16 };
1680       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ParseBreakpoint()");
1681       string = StripCurlies(string);
1682       TokenizeList(string, ',', bpTokens);
1683       for(i = 0; i < bpTokens.count; i++)
1684       {
1685          if(TokenizeListItem(bpTokens[i], item))
1686          {
1687             StripQuotes(item.value, item.value);
1688             if(!strcmp(item.name, "number"))
1689             {
1690                if(!strchr(item.value, '.'))
1691                   bp.id = atoi(item.value);
1692                bp.number = item.value;
1693             }
1694             else if(!strcmp(item.name, "type"))
1695                bp.type = item.value;
1696             else if(!strcmp(item.name, "disp"))
1697                bp.disp = item.value;
1698             else if(!strcmp(item.name, "enabled"))
1699                bp.enabled = (!strcmpi(item.value, "y"));
1700             else if(!strcmp(item.name, "addr"))
1701             {
1702                if(outTokens && !strcmp(item.value, "<MULTIPLE>"))
1703                {
1704                   int c = 1;
1705                   Array<GdbDataBreakpoint> bpArray = bp.multipleBPs = { };
1706                   while(outTokens.count > ++c)
1707                   {
1708                      GdbDataBreakpoint multBp = ParseBreakpoint(outTokens[c], null);
1709                      bpArray.Add(multBp);
1710                   }
1711                }
1712                else
1713                   bp.addr = item.value;
1714             }
1715             else if(!strcmp(item.name, "func"))
1716                bp.func = item.value;
1717             else if(!strcmp(item.name, "file"))
1718                bp.file = item.value;
1719             else if(!strcmp(item.name, "fullname"))
1720                bp.fullname = item.value;
1721             else if(!strcmp(item.name, "line"))
1722                bp.line = atoi(item.value);
1723             else if(!strcmp(item.name, "at"))
1724                bp.at = item.value;
1725             else if(!strcmp(item.name, "times"))
1726                bp.times = atoi(item.value);
1727             else if(!strcmp(item.name, "original-location") || !strcmp(item.name, "thread-groups"))
1728                _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "breakpoint member (", item.name, "=", item.value, ") is ignored");
1729             else
1730                _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "breakpoint member (", item.name, "=", item.value, ") is unheard of");
1731          }
1732       }
1733       delete bpTokens;
1734       delete item;
1735       return bp;
1736    }
1737
1738    void ShowDebuggerViews()
1739    {
1740       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ShowDebuggerViews()");
1741       ide.outputView.Show();
1742       ide.outputView.SelectTab(debug);
1743       ide.threadsView.Show();
1744       ide.callStackView.Show();
1745       ide.watchesView.Show();
1746       ide.Update(null);
1747    }
1748
1749    void HideDebuggerViews()
1750    {
1751       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::HideDebuggerViews()");
1752       ide.RepositionWindows(true);
1753    }
1754
1755    void ::GdbCommand(bool focus, char * format, ...)
1756    {
1757       if(gdbHandle)
1758       {
1759          // TODO: Improve this limit
1760          static char string[MAX_F_STRING*4];
1761          va_list args;
1762          va_start(args, format);
1763          vsnprintf(string, sizeof(string), format, args);
1764          string[sizeof(string)-1] = 0;
1765          va_end(args);
1766
1767          gdbReady = false;
1768          ide.debugger.serialSemaphore.TryWait();
1769
1770 #ifdef GDB_DEBUG_CONSOLE
1771          _dpl2(_dpct, dplchan::gdbCommand, 0, string);
1772 #endif
1773 #ifdef GDB_DEBUG_OUTPUT
1774          ide.outputView.gdbBox.Logf("cmd: %s\n", string);
1775 #endif
1776 #ifdef GDB_DEBUG_GUI
1777          if(ide.gdbDialog)
1778             ide.gdbDialog.AddCommand(string);
1779 #endif
1780
1781          strcat(string,"\n");
1782          gdbHandle.Puts(string);
1783
1784          if(focus)
1785             Process_ShowWindows(targetProcessId);
1786
1787          app.Unlock();
1788          ide.debugger.serialSemaphore.Wait();
1789          app.Lock();
1790       }
1791    }
1792
1793    bool ValidateBreakpoint(Breakpoint bp)
1794    {
1795       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ValidateBreakpoint()");
1796       if(modules && bp.line && bp.bp)
1797       {
1798          if(bp.bp.line != bp.line)
1799          {
1800             if(!bp.bp.line)
1801             {
1802 #ifdef _DEBUG
1803                //here
1804                ide.outputView.debugBox.Logf("WOULD HAVE -- Invalid breakpoint disabled: %s:%d\n", bp.relativeFilePath, bp.line);
1805 #endif
1806                //UnsetBreakpoint(bp);
1807                //bp.enabled = false;
1808                return false;
1809             }
1810             else
1811             {
1812                //here
1813                ide.outputView.debugBox.Logf("Debugger Error: ValidateBreakpoint error\n");
1814                bp.line = bp.bp.line;
1815             }
1816          }
1817       }
1818       return true;
1819    }
1820
1821    void BreakpointsMaintenance()
1822    {
1823       //_dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::BreakpointsMaintenance()");
1824       if(symbols)
1825       {
1826          if(gdbExecution.suspendInternalBreakpoints)
1827          {
1828             for(bp : sysBPs; bp.inserted)
1829                UnsetBreakpoint(bp);
1830          }
1831          else
1832          {
1833             DirExpression objDir = ide.project.GetObjDir(currentCompiler, prjConfig, bitDepth);
1834             for(bp : sysBPs; !bp.inserted)
1835             {
1836                bool insert = false;
1837                if(bp.type == internalModulesLoaded)
1838                {
1839                   char path[MAX_LOCATION];
1840                   char name[MAX_LOCATION];
1841                   char fixedModuleName[MAX_FILENAME];
1842                   char line[16384];
1843                   int lineNumber;
1844                   bool moduleLoadBlock = false;
1845                   File f;
1846                   ReplaceSpaces(fixedModuleName, ide.project.moduleName);
1847                   snprintf(name, sizeof(name),"%s.main.ec", fixedModuleName);
1848                   name[sizeof(name)-1] = 0;
1849                   strcpy(path, ide.workspace.projectDir);
1850                   PathCatSlash(path, objDir.dir);
1851                   PathCatSlash(path, name);
1852                   f = FileOpen(path, read);
1853                   if(f)
1854                   {
1855                      for(lineNumber = 1; !f.Eof(); lineNumber++)
1856                      {
1857                         if(f.GetLine(line, sizeof(line) - 1))
1858                         {
1859                            bool moduleLoadLine;
1860                            TrimLSpaces(line, line);
1861                            moduleLoadLine = !strncmp(line, "eModule_Load", strlen("eModule_Load"));
1862                            if(!moduleLoadBlock && moduleLoadLine)
1863                               moduleLoadBlock = true;
1864                            else if(moduleLoadBlock && !moduleLoadLine && strlen(line) > 0)
1865                               break;
1866                         }
1867                      }
1868                      if(!f.Eof())
1869                      {
1870                         char relative[MAX_LOCATION];
1871                         bp.absoluteFilePath = path;
1872                         MakePathRelative(path, ide.workspace.projectDir, relative);
1873                         bp.relativeFilePath = relative;
1874                         bp.line = lineNumber;
1875                         insert = true;
1876                      }
1877                      delete f;
1878                   }
1879                }
1880                else if(bp.type == internalModuleLoad)
1881                {
1882                   if(modules)
1883                   {
1884                      for(prj : ide.workspace.projects)
1885                      {
1886                         if(!strcmp(prj.moduleName, "ecere"))
1887                         {
1888                            ProjectNode node = prj.topNode.Find("instance.c", false);
1889                            if(node)
1890                            {
1891                               char path[MAX_LOCATION];
1892                               char relative[MAX_LOCATION];
1893                               node.GetFullFilePath(path);
1894                               bp.absoluteFilePath = path;
1895                               MakePathRelative(path, prj.topNode.path, relative);
1896                               bp.relativeFilePath = relative;
1897                               insert = true;
1898                               break;
1899                            }
1900                         }
1901                      }
1902                   }
1903                }
1904                else
1905                   insert = true;
1906                if(insert)
1907                   SetBreakpoint(bp, false);
1908             }
1909             delete objDir;
1910          }
1911
1912          if(userAction != runToCursor && bpRunToCursor && bpRunToCursor.inserted)
1913             UnsetBreakpoint(bpRunToCursor);
1914          if(bpRunToCursor && !bpRunToCursor.inserted)
1915             SetBreakpoint(bpRunToCursor, false);
1916
1917          if(ignoreBreakpoints)
1918          {
1919             for(bp : ide.workspace.breakpoints; bp.inserted)
1920                UnsetBreakpoint(bp);
1921          }
1922          else
1923          {
1924             for(bp : ide.workspace.breakpoints; !bp.inserted && bp.type == user)
1925             {
1926                if(bp.enabled)
1927                {
1928                   if(!SetBreakpoint(bp, false))
1929                      SetBreakpoint(bp, true);
1930                }
1931                else
1932                {
1933 #ifdef _DEBUG
1934                   if(bp.bp)
1935                      _dpl(0, "problem");
1936 #endif
1937                   delete bp.bp;
1938                   bp.bp = GdbDataBreakpoint { };
1939                }
1940             }
1941          }
1942       }
1943    }
1944
1945    void UnsetBreakpoint(Breakpoint bp)
1946    {
1947       char * s = null; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::UnsetBreakpoint(", s=bp.CopyLocationString(false), ") -- ", bp.type); delete s;
1948       if(symbols && bp.inserted)
1949       {
1950          GdbCommand(false, "-break-delete %s", bp.bp.number);
1951          bp.inserted = false;
1952          delete bp.bp;
1953          bp.bp = { };
1954       }
1955    }
1956
1957    bool SetBreakpoint(Breakpoint bp, bool removePath)
1958    {
1959       char * s = null; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::SetBreakpoint(", s=bp.CopyLocationString(false), ", ", removePath ? "**** removePath(true) ****" : "", ") -- ", bp.type); delete s;
1960       breakpointError = false;
1961       if(symbols && bp.enabled && (!bp.project || bp.project.GetTargetType(bp.project.config) == staticLibrary || bp.project == ide.project || projectsLibraryLoaded[bp.project.name]))
1962       {
1963          sentBreakInsert = true;
1964          if(bp.address)
1965             GdbCommand(false, "-break-insert *%s", bp.address);
1966          else
1967          {
1968             char * location = bp.CopyLocationString(removePath);
1969             GdbCommand(false, "-break-insert %s", location);
1970             delete location;
1971          }
1972          if(!breakpointError)
1973          {
1974             char * address = null;
1975             if(bpItem && bpItem.multipleBPs && bpItem.multipleBPs.count)
1976             {
1977                int count = 0;
1978                GdbDataBreakpoint first = null;
1979                for(n : bpItem.multipleBPs)
1980                {
1981                   if(!fstrcmp(n.fullname, bp.absoluteFilePath) && !first)
1982                   {
1983                      count++;
1984                      first = n;
1985                      break;
1986                   }
1987                   /*else
1988                   {
1989                      if(n.enabled)
1990                      {
1991                         GdbCommand(false, "-break-disable %s", n.number);
1992                         n.enabled = false;
1993                      }
1994                      else
1995                         _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error breakpoint already disabled.");
1996                   }*/
1997                }
1998                if(first)
1999                {
2000                   address = CopyString(first.addr);
2001                   bpItem.addr = first.addr;
2002                   bpItem.func = first.func;
2003                   bpItem.file = first.file;
2004                   bpItem.fullname = first.fullname;
2005                   bpItem.line = first.line;
2006                   //bpItem.thread-groups = first.thread-groups;*/
2007                }
2008                else if(count == 0)
2009                   _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error multiple breakpoints all disabled.");
2010                else
2011                   _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error multiple breakpoints in exact same file not supported.");
2012                bpItem.multipleBPs.Free();
2013                delete bpItem.multipleBPs;
2014             }
2015             delete bp.bp;
2016             bp.bp = bpItem;
2017             bpItem = null;
2018             bp.inserted = (bp.bp && bp.bp.number && strcmp(bp.bp.number, "0"));
2019             if(bp.inserted)
2020                ValidateBreakpoint(bp);
2021
2022             if(address)
2023             {
2024                UnsetBreakpoint(bp);
2025                bp.address = address;
2026                delete address;
2027                SetBreakpoint(bp, removePath);
2028             }
2029          }
2030       }
2031       return !breakpointError;
2032    }
2033
2034    void GdbGetStack()
2035    {
2036       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbGetStack()");
2037       activeFrame = null;
2038       stackFrames.Free(Frame::Free);
2039       GdbCommand(false, "-stack-info-depth");
2040       if(!frameCount)
2041          GdbCommand(false, "-stack-info-depth 192");
2042       if(frameCount && frameCount <= 192)
2043          GdbCommand(false, "-stack-list-frames 0 %d", Min(frameCount-1, 191));
2044       else
2045       {
2046          GdbCommand(false, "-stack-list-frames 0 %d", Min(frameCount-1, 95));
2047          GdbCommand(false, "-stack-list-frames %d %d", Max(frameCount - 96, 96), frameCount - 1);
2048       }
2049       GdbCommand(false, "");
2050    }
2051
2052    bool GdbTargetSet()
2053    {
2054       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbTargetSet()");
2055       if(!targeted)
2056       {
2057          char escaped[MAX_LOCATION];
2058          strescpy(escaped, targetFile);
2059          GdbCommand(false, "file \"%s\"", escaped); //GDB/MI Missing Implementation in 5.1.1 but we now have -file-exec-and-symbols / -file-exec-file / -file-symbol-file
2060
2061          if(!symbols)
2062             return true;
2063
2064          if(usingValgrind)
2065          {
2066             const char *vgdbCommand = "/usr/bin/vgdb"; // TODO: vgdb command config option
2067             //GdbCommand(false, "-target-select remote | %s --pid=%d", "vgdb", targetProcessId);
2068             printf("target remote | %s --pid=%d\n", vgdbCommand, targetProcessId);
2069             GdbCommand(false, "target remote | %s --pid=%d", vgdbCommand, targetProcessId); // TODO: vgdb command config option
2070          }
2071          else
2072             GdbCommand(false, "info target"); //GDB/MI Missing Implementation -file-list-symbol-files and -file-list-exec-sections
2073
2074          /*for(prj : ide.workspace.projects; prj != ide.workspace.projects.firstIterator.data)
2075             GdbCommand(false, "-environment-directory \"%s\"", prj.topNode.path);*/
2076
2077          for(dir : ide.workspace.sourceDirs; dir && dir[0])
2078          {
2079            bool interference = false;
2080            for(prj : ide.workspace.projects)
2081            {
2082               if(!fstrcmp(prj.topNode.path, dir))
2083               {
2084                  interference = true;
2085                  break;
2086               }
2087            }
2088            if(!interference && dir[0])
2089               GdbCommand(false, "-environment-directory \"%s\"", dir);
2090          }
2091
2092          targeted = true;
2093       }
2094       return true;
2095    }
2096
2097    /*void GdbTargetRelease()
2098    {
2099       if(targeted)
2100       {
2101          BreakpointsDeleteAll();
2102          GdbCommand(false, "file");  //GDB/MI Missing Implementation -target-detach
2103          targeted = false;
2104          symbols = true;
2105       }
2106    }*/
2107
2108    void GdbDebugBreak(bool internal)
2109    {
2110       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbDebugBreak()");
2111       if(targetProcessId)
2112       {
2113          if(internal)
2114             breakType = DebuggerAction::internal;
2115
2116          if(ide) ide.Update(null);
2117          app.Unlock();
2118          if(Process_Break(targetProcessId))  //GdbCommand(false, "-exec-interrupt");
2119             serialSemaphore.Wait();
2120          else
2121          {
2122             _ChangeState(loaded);
2123             targetProcessId = 0;
2124          }
2125          app.Lock();
2126       }
2127       else
2128          ide.outputView.debugBox.Logf("Debugger Error: GdbDebugBreak with not target id should never happen\n");
2129    }
2130
2131    void GdbExecRun()
2132    {
2133       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecRun()");
2134       GdbTargetSet();
2135       if(!usingValgrind)
2136          gdbExecution = run;
2137       GdbExecCommon();
2138       ShowDebuggerViews();
2139       if(usingValgrind)
2140          GdbExecContinue(true);
2141       else
2142          GdbCommand(true, "-exec-run");
2143    }
2144
2145    void GdbExecContinue(bool focus)
2146    {
2147       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecContinue()");
2148       gdbExecution = run;
2149       GdbExecCommon();
2150       GdbCommand(focus, "-exec-continue");
2151    }
2152
2153    void GdbExecNext()
2154    {
2155       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecNext()");
2156       gdbExecution = next;
2157       GdbExecCommon();
2158       GdbCommand(true, "-exec-next");
2159    }
2160
2161    void GdbExecUntil(char * absoluteFilePath, int lineNumber)
2162    {
2163       char relativeFilePath[MAX_LOCATION];
2164       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecUntil()");
2165       gdbExecution = until;
2166       GdbExecCommon();
2167       if(absoluteFilePath)
2168       {
2169          WorkspaceGetRelativePath(absoluteFilePath, relativeFilePath, null);
2170          GdbCommand(true, "-exec-until %s:%d", relativeFilePath, lineNumber);
2171       }
2172       else
2173          GdbCommand(true, "-exec-until");
2174    }
2175
2176    void GdbExecAdvance(char * absoluteFilePathOrLocation, int lineNumber)
2177    {
2178       char relativeFilePath[MAX_LOCATION];
2179       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecAdvance()");
2180       gdbExecution = advance;
2181       GdbExecCommon();
2182       if(lineNumber)
2183       {
2184          WorkspaceGetRelativePath(absoluteFilePathOrLocation, relativeFilePath, null);
2185          GdbCommand(true, "advance %s:%d", relativeFilePath, lineNumber); // should use -exec-advance -- GDB/MI implementation missing
2186       }
2187       else
2188          GdbCommand(true, "advance %s", absoluteFilePathOrLocation); // should use -exec-advance -- GDB/MI implementation missing
2189    }
2190
2191    void GdbExecStep()
2192    {
2193       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecStep()");
2194       gdbExecution = step;
2195       GdbExecCommon();
2196       GdbCommand(true, "-exec-step");
2197    }
2198
2199    void GdbExecFinish()
2200    {
2201       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecFinish()");
2202       gdbExecution = finish;
2203       GdbExecCommon();
2204       GdbCommand(true, "-exec-finish");
2205    }
2206
2207    void GdbExecCommon()
2208    {
2209       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecCommon()");
2210       BreakpointsMaintenance();
2211    }
2212
2213 #ifdef GDB_DEBUG_GUI
2214    void SendGDBCommand(char * command)
2215    {
2216       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SendGDBCommand()");
2217       DebuggerState oldState = state;
2218       switch(state)
2219       {
2220          case running:
2221             if(targetProcessId)
2222                GdbDebugBreak(true);
2223          case stopped:
2224          case loaded:
2225             GdbCommand(false, command);
2226             break;
2227       }
2228       if(oldState == running)
2229          GdbExecContinue(false);
2230    }
2231 #endif
2232
2233    void ClearBreakDisplay()
2234    {
2235       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ClearBreakDisplay()");
2236       activeThread = 0;
2237       activeFrameLevel = -1;
2238       hitThread = 0;
2239       signalThread = 0;
2240       signalOn = false;
2241       frameCount = 0;
2242       if(stopItem)
2243          stopItem.Free();
2244       delete stopItem;
2245       event = none;
2246       activeFrame = null;
2247       stackFrames.Free(Frame::Free);
2248       ide.callStackView.Clear();
2249       ide.threadsView.Clear();
2250       ide.Update(null);
2251    }
2252
2253    bool GdbAbortExec()
2254    {
2255       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbAbortExec()");
2256       sentKill = true;
2257       GdbCommand(false, "-interpreter-exec console \"kill\""); // should use -exec-abort -- GDB/MI implementation incomplete
2258       return true;
2259    }
2260
2261    bool GdbInit(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind)
2262    {
2263       bool result = true;
2264       char oldDirectory[MAX_LOCATION];
2265       char tempPath[MAX_LOCATION];
2266       char command[MAX_F_STRING*4];
2267       Project project = ide.project;
2268       DirExpression targetDirExp = project.GetTargetDir(compiler, config, bitDepth);
2269       PathBackup pathBackup { };
2270
2271       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbInit()");
2272       if(currentCompiler != compiler)
2273       {
2274          delete currentCompiler;
2275          currentCompiler = compiler;
2276          incref currentCompiler;
2277       }
2278       prjConfig = config;
2279       this.bitDepth = bitDepth;
2280       usingValgrind = useValgrind;
2281
2282       _ChangeState(loaded);
2283       sentKill = false;
2284       sentBreakInsert = false;
2285       breakpointError = false;
2286       ignoreBreakpoints = false;
2287       symbols = true;
2288       targeted = false;
2289       modules = false;
2290       needReset = false;
2291       projectsLibraryLoaded.Free();
2292
2293       ide.outputView.ShowClearSelectTab(debug);
2294       ide.outputView.debugBox.Logf($"Starting debug mode\n");
2295
2296 #ifdef GDB_DEBUG_OUTPUT
2297       ide.outputView.gdbBox.Logf("run: Starting GDB\n");
2298 #endif
2299
2300       strcpy(tempPath, ide.workspace.projectDir);
2301       PathCatSlash(tempPath, targetDirExp.dir);
2302       delete targetDir;
2303       targetDir = CopyString(tempPath);
2304       project.CatTargetFileName(tempPath, compiler, config);
2305       delete targetFile;
2306       targetFile = CopyString(tempPath);
2307
2308       GetWorkingDir(oldDirectory, MAX_LOCATION);
2309       if(ide.workspace.debugDir && ide.workspace.debugDir[0])
2310       {
2311          char temp[MAX_LOCATION];
2312          strcpy(temp, ide.workspace.projectDir);
2313          PathCatSlash(temp, ide.workspace.debugDir);
2314          ChangeWorkingDir(temp);
2315       }
2316       else
2317          ChangeWorkingDir(ide.workspace.projectDir);
2318
2319       ide.SetPath(true, compiler, config, bitDepth);
2320
2321       // TODO: This pollutes the environment, but at least it works
2322       // It shouldn't really affect the IDE as the PATH gets restored and other variables set for testing will unlikely cause problems
2323       // What is the proper solution for this? DualPipeOpenEnv?
2324       // gdb set environment commands don't seem to take effect
2325       for(e : ide.workspace.environmentVars)
2326       {
2327          SetEnvironment(e.name, e.string);
2328       }
2329
2330       if(usingValgrind)
2331       {
2332          char * clArgs = ide.workspace.commandLineArgs;
2333          const char *valgrindCommand = "valgrind"; // TODO: valgrind command config option //TODO: valgrind options
2334          ValgrindLeakCheck vgLeakCheck = ide.workspace.vgLeakCheck;
2335          int vgRedzoneSize = ide.workspace.vgRedzoneSize;
2336          bool vgTrackOrigins = ide.workspace.vgTrackOrigins;
2337          vgLogFile = CreateTemporaryFile(vgLogPath, "ecereidevglog");
2338          if(vgLogFile)
2339          {
2340             incref vgLogFile;
2341             vgLogThread.Create();
2342          }
2343          else
2344          {
2345             ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't open temporary log file for Valgrind output\n");
2346             result = false;
2347          }
2348          if(result && !CheckCommandAvailable(valgrindCommand))
2349          {
2350             ide.outputView.debugBox.Logf($"Debugger Fatal Error: Command %s for Valgrind is not available.\n", valgrindCommand);
2351             result = false;
2352          }
2353          if(result)
2354          {
2355             char * vgRedzoneSizeFlag = vgRedzoneSize == -1 ? "" : PrintString(" --redzone-size=", vgRedzoneSize);
2356             sprintf(command, "%s --vgdb=yes --vgdb-error=0 --log-file=%s --leak-check=%s%s --track-origins=%s %s%s%s",
2357                   valgrindCommand, vgLogPath, (char*)vgLeakCheck, vgRedzoneSizeFlag, vgTrackOrigins ? "yes" : "no", targetFile, clArgs ? " " : "", clArgs ? clArgs : "");
2358             if(vgRedzoneSize != -1)
2359                delete vgRedzoneSizeFlag;
2360             vgTargetHandle = DualPipeOpen(PipeOpenMode { output = true, /*error = true, */input = true }, command);
2361             if(!vgTargetHandle)
2362             {
2363                ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't start Valgrind\n");
2364                result = false;
2365             }
2366          }
2367          if(result)
2368          {
2369             incref vgTargetHandle;
2370             vgTargetThread.Create();
2371
2372             targetProcessId = vgTargetHandle.GetProcessID();
2373             waitingForPID = false;
2374             if(!targetProcessId)
2375             {
2376                ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't get Valgrind process ID\n");
2377                result = false;
2378             }
2379          }
2380          if(result)
2381          {
2382             app.Unlock();
2383             serialSemaphore.Wait();
2384             app.Lock();
2385          }
2386       }
2387
2388       if(result)
2389       {
2390          strcpy(command,
2391             (compiler.targetPlatform == win32 && bitDepth == 64) ? "x86_64-w64-mingw32-gdb" :
2392             (compiler.targetPlatform == win32 && bitDepth == 32) ? "i686-w64-mingw32-gdb" :
2393             "gdb");
2394          if(!CheckCommandAvailable(command))
2395          {
2396             ide.outputView.debugBox.Logf($"Debugger Fatal Error: Command %s for GDB is not available.\n", command);
2397             result = false;
2398          }
2399          else
2400          {
2401             strcat(command, " -n -silent --interpreter=mi2"); //-async //\"%s\"
2402             gdbTimer.Start();
2403             gdbHandle = DualPipeOpen(PipeOpenMode { output = true, /*error = true, */input = true }, command);
2404             if(!gdbHandle)
2405             {
2406                ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't start GDB\n");
2407                result = false;
2408             }
2409          }
2410       }
2411       if(result)
2412       {
2413          incref gdbHandle;
2414          gdbThread.Create();
2415
2416          gdbProcessId = gdbHandle.GetProcessID();
2417          if(!gdbProcessId)
2418          {
2419             ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't get GDB process ID\n");
2420             result = false;
2421          }
2422       }
2423       if(result)
2424       {
2425          app.Unlock();
2426          serialSemaphore.Wait();
2427          app.Lock();
2428
2429          GdbCommand(false, "-gdb-set verbose off");
2430          //GdbCommand(false, "-gdb-set exec-done-display on");
2431          GdbCommand(false, "-gdb-set step-mode off");
2432          GdbCommand(false, "-gdb-set unwindonsignal on");
2433          //GdbCommand(false, "-gdb-set shell on");
2434          GdbCommand(false, "set print elements 992");
2435          GdbCommand(false, "-gdb-set backtrace limit 100000");
2436
2437          if(!GdbTargetSet())
2438          {
2439             //_ChangeState(terminated);
2440             result = false;
2441          }
2442       }
2443       if(result)
2444       {
2445 #if defined(__unix__)
2446          {
2447             CreateTemporaryDir(progFifoDir, "ecereide");
2448             strcpy(progFifoPath, progFifoDir);
2449             PathCat(progFifoPath, "ideprogfifo");
2450             if(!mkfifo(progFifoPath, 0600))
2451             {
2452                //fileCreated = true;
2453             }
2454             else
2455             {
2456                //app.Lock();
2457                ide.outputView.debugBox.Logf(createFIFOMsg, progFifoPath);
2458                //app.Unlock();
2459             }
2460          }
2461
2462          if(!usingValgrind)
2463          {
2464             progThread.terminate = false;
2465             progThread.Create();
2466          }
2467 #endif
2468
2469 #if defined(__WIN32__)
2470          GdbCommand(false, "-gdb-set new-console on");
2471 #endif
2472
2473 #if defined(__unix__)
2474          if(!usingValgrind)
2475             GdbCommand(false, "-inferior-tty-set %s", progFifoPath);
2476 #endif
2477
2478          if(!usingValgrind)
2479             GdbCommand(false, "-gdb-set args %s", ide.workspace.commandLineArgs ? ide.workspace.commandLineArgs : "");
2480          /*
2481          for(e : ide.workspace.environmentVars)
2482          {
2483             GdbCommand(false, "set environment %s=%s", e.name, e.string);
2484          }
2485          */
2486       }
2487
2488       ChangeWorkingDir(oldDirectory);
2489
2490       delete pathBackup;
2491
2492       if(!result)
2493          GdbExit();
2494       delete targetDirExp;
2495       return result;
2496    }
2497
2498    void GdbExit()
2499    {
2500       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExit()");
2501       if(gdbHandle && gdbProcessId)
2502       {
2503          gdbTimer.Stop();
2504          GdbCommand(false, "-gdb-exit");
2505
2506          if(gdbThread)
2507          {
2508             app.Unlock();
2509             gdbThread.Wait();
2510             app.Lock();
2511          }
2512          if(vgLogThread)
2513          {
2514             app.Unlock();
2515             vgLogThread.Wait();
2516             app.Lock();
2517          }
2518          if(vgTargetThread)
2519          {
2520             app.Unlock();
2521             vgTargetThread.Wait();
2522             app.Lock();
2523          }
2524
2525          if(vgLogFile)
2526             delete vgLogFile;
2527          if(gdbHandle)
2528          {
2529             gdbHandle.Wait();
2530             delete gdbHandle;
2531          }
2532       }
2533       gdbTimer.Stop();
2534       _ChangeState(terminated); // this state change seems to be superfluous, is it safety for something?
2535       prjConfig = null;
2536       needReset = false;
2537
2538       if(ide.workspace)
2539       {
2540          for(bp : ide.workspace.breakpoints)
2541             bp.Reset();
2542       }
2543       for(bp : sysBPs)
2544          bp.Reset();
2545       if(bpRunToCursor)
2546          bpRunToCursor.Reset();
2547
2548       ide.outputView.debugBox.Logf($"Debugging stopped\n");
2549       ClearBreakDisplay();
2550       ide.Update(null);
2551
2552 #if defined(__unix__)
2553       if(!usingValgrind && FileExists(progFifoPath)) //fileCreated)
2554       {
2555          progThread.terminate = true;
2556          if(fifoFile)
2557          {
2558             fifoFile.CloseInput();
2559             app.Unlock();
2560             progThread.Wait();
2561             app.Lock();
2562             delete fifoFile;
2563          }
2564          DeleteFile(progFifoPath);
2565          progFifoPath[0] = '\0';
2566          rmdir(progFifoDir);
2567       }
2568 #endif
2569    }
2570
2571    bool WatchesLinkCodeEditor()
2572    {
2573       bool goodFrame = activeFrame && activeFrame.absoluteFile;
2574       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::WatchesLinkCodeEditor()");
2575       if(codeEditor && (!goodFrame || fstrcmp(codeEditor.fileName, activeFrame.absoluteFile)))
2576          WatchesReleaseCodeEditor();
2577
2578       if(!codeEditor && goodFrame)
2579       {
2580          codeEditor = (CodeEditor)ide.OpenFile(activeFrame.absoluteFile, normal, false, null, no, normal, false);
2581          if(codeEditor)
2582          {
2583             codeEditor.inUseDebug = true;
2584             incref codeEditor;
2585          }
2586       }
2587       return codeEditor != null;
2588    }
2589
2590    void WatchesReleaseCodeEditor()
2591    {
2592       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::WatchesReleaseCodeEditor()");
2593       if(codeEditor)
2594       {
2595          codeEditor.inUseDebug = false;
2596          if(!codeEditor.visible)
2597             codeEditor.Destroy(0);
2598          delete codeEditor;
2599       }
2600    }
2601
2602    bool ResolveWatch(Watch wh)
2603    {
2604       bool result = false;
2605
2606       _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::ResolveWatch()");
2607       wh.Reset();
2608
2609       /*delete wh.value;
2610       if(wh.type)
2611       {
2612          FreeType(wh.type);
2613          wh.type = null;
2614       }*/
2615
2616       if(wh.expression)
2617       {
2618          char watchmsg[MAX_F_STRING];
2619          if(state == stopped && !codeEditor)
2620             wh.value = CopyString($"No source file found for selected frame");
2621          //if(codeEditor && state == stopped || state != stopped)
2622          else
2623          {
2624             Module backupPrivateModule;
2625             Context backupContext;
2626             Class backupThisClass;
2627             Expression exp;
2628             parseError = false;
2629
2630             backupPrivateModule = GetPrivateModule();
2631             backupContext = GetCurrentContext();
2632             backupThisClass = GetThisClass();
2633             if(codeEditor)
2634             {
2635                SetPrivateModule(codeEditor.privateModule);
2636                SetCurrentContext(codeEditor.globalContext);
2637                SetTopContext(codeEditor.globalContext);
2638                SetGlobalContext(codeEditor.globalContext);
2639                SetGlobalData(&codeEditor.globalData);
2640             }
2641
2642             exp = ParseExpressionString(wh.expression);
2643
2644             if(exp && !parseError)
2645             {
2646                char expString[4096];
2647                expString[0] = 0;
2648                PrintExpression(exp, expString);
2649
2650                if(GetPrivateModule())
2651                {
2652                   if(codeEditor)
2653                      DebugFindCtxTree(codeEditor.ast, activeFrame.line, 0);
2654                   ProcessExpressionType(exp);
2655                }
2656                wh.type = exp.expType;
2657                if(wh.type)
2658                   wh.type.refCount++;
2659                DebugComputeExpression(exp);
2660                if(ExpressionIsError(exp))
2661                {
2662                   GDBFallBack(exp, expString);
2663                }
2664
2665                /*if(exp.hasAddress)
2666                {
2667                   char temp[MAX_F_STRING];
2668                   sprintf(temp, "0x%x", exp.address);
2669                   wh.address = CopyString(temp);
2670                   // wh.address = CopyStringf("0x%x", exp.address);
2671                }*/
2672 /*
2673 //#ifdef _DEBUG
2674                {
2675                   Type dataType = exp.expType;
2676                   if(dataType)
2677                   {
2678                      char temp[MAX_F_STRING];
2679                      switch(dataType.kind)
2680                      {
2681                         case charType:
2682                            sprintf(temp, "%i", exp.val.c);
2683                            break;
2684                         case shortType:
2685                            sprintf(temp, "%i", exp.val.s);
2686                            break;
2687                         case intType:
2688                         case longType:
2689                         case enumType:
2690                            sprintf(temp, "%i", exp.val.i);
2691                            break;
2692                         case int64Type:
2693                            sprintf(temp, "%i", exp.val.i64);
2694                            break;
2695                         case pointerType:
2696                            sprintf(temp, "%i", exp.val.p);
2697                            break;
2698
2699                         case floatType:
2700                         {
2701                            long v = (long)exp.val.f;
2702                            sprintf(temp, "%i", v);
2703                            break;
2704                         }
2705                         case doubleType:
2706                         {
2707                            long v = (long)exp.val.d;
2708                            sprintf(temp, "%i", v);
2709                            break;
2710                         }
2711                      }
2712                      if(temp)
2713                         wh.intVal = CopyString(temp);
2714                      switch(dataType.kind)
2715                      {
2716                         case charType:
2717                            sprintf(temp, "0x%x", exp.val.c);
2718                            break;
2719                         case shortType:
2720                            sprintf(temp, "0x%x", exp.val.s);
2721                            break;
2722                         case enumType:
2723                         case intType:
2724                            sprintf(temp, "0x%x", exp.val.i);
2725                            break;
2726                         case int64Type:
2727                            sprintf(temp, "0x%x", exp.val.i64);
2728                            break;
2729                         case longType:
2730                            sprintf(temp, "0x%x", exp.val.i64);
2731                            break;
2732                         case pointerType:
2733                            sprintf(temp, "0x%x", exp.val.p);
2734                            break;
2735
2736                         case floatType:
2737                         {
2738                            long v = (long)exp.val.f;
2739                            sprintf(temp, "0x%x", v);
2740                            break;
2741                         }
2742                         case doubleType:
2743                         {
2744                            long v = (long)exp.val.d;
2745                            sprintf(temp, "0x%x", v);
2746                            break;
2747                         }
2748                      }
2749                      if(temp)
2750                         wh.hexVal = CopyString(temp);
2751                      switch(dataType.kind)
2752                      {
2753                         case charType:
2754                            sprintf(temp, "0o%o", exp.val.c);
2755                            break;
2756                         case shortType:
2757                            sprintf(temp, "0o%o", exp.val.s);
2758                            break;
2759                         case enumType:
2760                         case intType:
2761                            sprintf(temp, "0o%o", exp.val.i);
2762                            break;
2763                         case int64Type:
2764                            sprintf(temp, "0o%o", exp.val.i64);
2765                            break;
2766                         case longType:
2767                            sprintf(temp, "0o%o", exp.val.i64);
2768                            break;
2769                         case pointerType:
2770                            sprintf(temp, "0o%o", exp.val.p);
2771                            break;
2772
2773                         case floatType:
2774                         {
2775                            long v = (long)exp.val.f;
2776                            sprintf(temp, "0o%o", v);
2777                            break;
2778                         }
2779                         case doubleType:
2780                         {
2781                            long v = (long)exp.val.d;
2782                            sprintf(temp, "0o%o", v);
2783                            break;
2784                         }
2785                      }
2786                      if(temp)
2787                         wh.octVal = CopyString(temp);
2788                   }
2789                }
2790                // WHATS THIS HERE ?
2791                if(exp.type == constantExp && exp.constant)
2792                   wh.constant = CopyString(exp.constant);
2793 //#endif
2794 */
2795
2796                switch(exp.type)
2797                {
2798                   case symbolErrorExp:
2799                      snprintf(watchmsg, sizeof(watchmsg), $"Symbol \"%s\" not found", exp.identifier.string);
2800                      break;
2801                   case structMemberSymbolErrorExp:
2802                      // todo get info as in next case (ExpClassMemberSymbolError)
2803                      snprintf(watchmsg, sizeof(watchmsg), $"Error: Struct member not found for \"%s\"", wh.expression);
2804                      break;
2805                   case classMemberSymbolErrorExp:
2806                      {
2807                         Class _class;
2808                         Expression memberExp = exp.member.exp;
2809                         Identifier memberID = exp.member.member;
2810                         Type type = memberExp.expType;
2811                         if(type)
2812                         {
2813                            _class = (memberID && memberID.classSym) ? memberID.classSym.registered : ((type.kind == classType && type._class) ? type._class.registered : null);
2814                            if(!_class)
2815                            {
2816                               char string[256] = "";
2817                               Symbol classSym;
2818                               PrintTypeNoConst(type, string, false, true);
2819                               classSym = FindClass(string);
2820                               _class = classSym ? classSym.registered : null;
2821                            }
2822                            if(_class)
2823                               snprintf(watchmsg, sizeof(watchmsg), $"Member \"%s\" not found in class \"%s\"", memberID ? memberID.string : "", _class.name);
2824                            else
2825                               snprintf(watchmsg, sizeof(watchmsg), "Member \"%s\" not found in unregistered class? (Should never get this message)", memberID ? memberID.string : "");
2826                         }
2827                         else
2828                            snprintf(watchmsg, sizeof(watchmsg), "Member \"%s\" not found in no type? (Should never get this message)", memberID ? memberID.string : "");
2829                      }
2830                      break;
2831                   case memoryErrorExp:
2832                      // Need to ensure when set to memoryErrorExp, constant is set
2833                      snprintf(watchmsg, sizeof(watchmsg), $"Memory can't be read at %s", /*(exp.type == constantExp) ? */exp.constant /*: null*/);
2834                      break;
2835                   case dereferenceErrorExp:
2836                      snprintf(watchmsg, sizeof(watchmsg), $"Dereference failure for \"%s\"", wh.expression);
2837                      break;
2838                   case unknownErrorExp:
2839                      snprintf(watchmsg, sizeof(watchmsg), $"Unknown error for \"%s\"", wh.expression);
2840                      break;
2841                   case noDebuggerErrorExp:
2842                      snprintf(watchmsg, sizeof(watchmsg), $"Debugger required for symbol evaluation in \"%s\"", wh.expression);
2843                      break;
2844                   case debugStateErrorExp:
2845                      snprintf(watchmsg, sizeof(watchmsg), $"Incorrect debugger state for symbol evaluation in \"%s\"", wh.expression);
2846                      break;
2847                   case 0:
2848                      snprintf(watchmsg, sizeof(watchmsg), $"Null type for \"%s\"", wh.expression);
2849                      break;
2850                   case constantExp:
2851                   case stringExp:
2852                      // Temporary Code for displaying Strings
2853                      if((exp.expType && ((exp.expType.kind == pointerType ||
2854                               exp.expType.kind == arrayType) && exp.expType.type.kind == charType)) ||
2855                            (wh.type && wh.type.kind == classType && wh.type._class &&
2856                               wh.type._class.registered && wh.type._class.registered.type == normalClass &&
2857                               !strcmp(wh.type._class.registered.name, "String")))
2858                      {
2859
2860                         if(exp.expType.kind != arrayType || exp.hasAddress)
2861                         {
2862                            uint64 address;
2863                            char * string;
2864                            char value[4196];
2865                            int len;
2866                            //char temp[MAX_F_STRING * 32];
2867
2868                            ExpressionType evalError = dummyExp;
2869                            /*if(exp.expType.kind == arrayType)
2870                               sprintf(temp, "(char*)0x%x", exp.address);
2871                            else
2872                               sprintf(temp, "(char*)%s", exp.constant);*/
2873
2874                            //evaluation = Debugger::EvaluateExpression(temp, &evalError);
2875                            // address = strtoul(exp.constant, null, 0);
2876                            address = _strtoui64(exp.constant, null, 0);
2877                            //_dpl(0, "0x", address);
2878                            // snprintf(value, sizeof(value), "0x%08x ", address);
2879
2880                            if(address > 0xFFFFFFFFLL)
2881                               snprintf(value, sizeof(value), (GetRuntimePlatform() == win32) ? "0x%016I64x " : "0x%016llx ", address);
2882                            else
2883                               snprintf(value, sizeof(value), (GetRuntimePlatform() == win32) ? "0x%08I64x " : "0x%08llx ", address);
2884                            value[sizeof(value)-1] = 0;
2885
2886                            if(!address)
2887                               strcat(value, $"Null string");
2888                            else
2889                            {
2890                               int size = 4096;
2891                               len = strlen(value);
2892                               string = null;
2893                               while(!string && size > 2)
2894                               {
2895                                  string = GdbReadMemory(address, size);
2896                                  size /= 2;
2897                               }
2898                               if(string && string[0])
2899                               {
2900                                  value[len++] = '(';
2901                                  if(UTF8Validate(string))
2902                                  {
2903                                     int c;
2904                                     char ch;
2905
2906                                     for(c = 0; (ch = string[c]) && c<4096; c++)
2907                                        value[len++] = ch;
2908                                     value[len++] = ')';
2909                                     value[len++] = '\0';
2910
2911                                  }
2912                                  else
2913                                  {
2914                                     ISO8859_1toUTF8(string, value + len, 4096 - len - 30);
2915                                     strcat(value, ") (ISO8859-1)");
2916                                  }
2917
2918                                  delete string;
2919                               }
2920                               else if(string)
2921                               {
2922                                  strcat(value, $"Empty string");
2923                                  delete string;
2924                               }
2925                               else
2926                                  strcat(value, $"Couldn't read memory");
2927                            }
2928                            wh.value = CopyString(value);
2929                         }
2930                      }
2931                      else if(wh.type && wh.type.kind == classType && wh.type._class &&
2932                               wh.type._class.registered && wh.type._class.registered.type == enumClass)
2933                      {
2934                         uint64 value = strtoul(exp.constant, null, 0);
2935                         Class enumClass = eSystem_FindClass(GetPrivateModule(), wh.type._class.registered.name);
2936                         EnumClassData enumeration = (EnumClassData)enumClass.data;
2937                         NamedLink item;
2938                         for(item = enumeration.values.first; item; item = item.next)
2939                            if((int)item.data == value)
2940                               break;
2941                         if(item)
2942                            wh.value = CopyString(item.name);
2943                         else
2944                            wh.value = CopyString($"Invalid Enum Value");
2945                         result = true;
2946                      }
2947                      else if(wh.type && (wh.type.kind == charType || (wh.type.kind == classType && wh.type._class &&
2948                               wh.type._class.registered && !strcmp(wh.type._class.registered.fullName, "ecere::com::unichar"))) )
2949                      {
2950                         unichar value;
2951                         int signedValue;
2952                         char charString[5];
2953                         char string[256];
2954
2955                         if(exp.constant[0] == '\'')
2956                         {
2957                            if((int)((byte *)exp.constant)[1] > 127)
2958                            {
2959                               int nb;
2960                               value = UTF8GetChar(exp.constant + 1, &nb);
2961                               if(nb < 2) value = exp.constant[1];
2962                               signedValue = value;
2963                            }
2964                            else
2965                            {
2966                               signedValue = exp.constant[1];
2967                               {
2968                                  // Precomp Syntax error with boot strap here:
2969                                  byte b = (byte)(char)signedValue;
2970                                  value = (unichar) b;
2971                               }
2972                            }
2973                         }
2974                         else
2975                         {
2976                            if(wh.type.kind == charType && wh.type.isSigned)
2977                            {
2978                               signedValue = (int)(char)strtol(exp.constant, null, 0);
2979                               {
2980                                  // Precomp Syntax error with boot strap here:
2981                                  byte b = (byte)(char)signedValue;
2982                                  value = (unichar) b;
2983                               }
2984                            }
2985                            else
2986                            {
2987                               value = (uint)strtoul(exp.constant, null, 0);
2988                               signedValue = (int)value;
2989                            }
2990                         }
2991                         charString[0] = 0;
2992                         UTF32toUTF8Len(&value, 1, charString, 5);
2993                         if(value == '\0')
2994                            snprintf(string, sizeof(string), "\'\\0' (0)");
2995                         else if(value == '\t')
2996                            snprintf(string, sizeof(string), "\'\\t' (%d)", value);
2997                         else if(value == '\n')
2998                            snprintf(string, sizeof(string), "\'\\n' (%d)", value);
2999                         else if(value == '\r')
3000                            snprintf(string, sizeof(string), "\'\\r' (%d)", value);
3001                         else if(wh.type.kind == charType && wh.type.isSigned)
3002                            snprintf(string, sizeof(string), "\'%s\' (%d)", charString, signedValue);
3003                         else if(value > 256 || wh.type.kind != charType)
3004                         {
3005                            if(value > 0x10FFFF || !GetCharCategory(value))
3006                               snprintf(string, sizeof(string), $"Invalid Unicode Keypoint (0x%08X)", value);
3007                            else
3008                               snprintf(string, sizeof(string), "\'%s\' (U+%04X)", charString, value);
3009                         }
3010                         else
3011                            snprintf(string, sizeof(string), "\'%s\' (%d)", charString, value);
3012                         string[sizeof(string)-1] = 0;
3013
3014                         wh.value = CopyString(string);
3015                         result = true;
3016                      }
3017                      else
3018                      {
3019                         wh.value = CopyString(exp.constant);
3020                         result = true;
3021                      }
3022                      break;
3023                   default:
3024                      if(exp.hasAddress)
3025                      {
3026                         wh.value = PrintHexUInt64(exp.address);
3027                         result = true;
3028                      }
3029                      else
3030                      {
3031                         char tempString[256];
3032                         if(exp.member.memberType == propertyMember)
3033                            snprintf(watchmsg, sizeof(watchmsg), $"Missing property evaluation support for \"%s\"", wh.expression);
3034                         else
3035                            snprintf(watchmsg, sizeof(watchmsg), $"Evaluation failed for \"%s\" of type \"%s\"", wh.expression,
3036                                  exp.type.OnGetString(tempString, null, null));
3037                      }
3038                      break;
3039                }
3040             }
3041             else
3042                snprintf(watchmsg, sizeof(watchmsg), $"Invalid expression: \"%s\"", wh.expression);
3043             if(exp) FreeExpression(exp);
3044
3045
3046             SetPrivateModule(backupPrivateModule);
3047             SetCurrentContext(backupContext);
3048             SetTopContext(backupContext);
3049             SetGlobalContext(backupContext);
3050             SetThisClass(backupThisClass);
3051          }
3052          //else
3053          //   wh.value = CopyString("No source file found for selected frame");
3054
3055          watchmsg[sizeof(watchmsg)-1] = 0;
3056          if(!wh.value)
3057             wh.value = CopyString(watchmsg);
3058       }
3059       ide.watchesView.UpdateWatch(wh);
3060       return result;
3061    }
3062
3063    void EvaluateWatches()
3064    {
3065       _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::EvaluateWatches()");
3066       WatchesLinkCodeEditor();
3067       if(state == stopped)
3068       {
3069          for(wh : ide.workspace.watches)
3070             ResolveWatch(wh);
3071       }
3072    }
3073
3074    char * ::GdbEvaluateExpression(char * expression)
3075    {
3076       _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::GdbEvaluateExpression(", expression, ")");
3077       eval.active = true;
3078       eval.error = none;
3079       GdbCommand(false, "-data-evaluate-expression \"%s\"", expression);
3080       if(eval.active)
3081          ide.outputView.debugBox.Logf("Debugger Error: GdbEvaluateExpression\n");
3082       return eval.result;
3083    }
3084
3085    // to be removed... use GdbReadMemory that returns a byte array instead
3086    char * ::GdbReadMemoryString(uint64 address, int size, char format, int rows, int cols)
3087    {
3088       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbReadMemoryString(", address, ")");
3089       eval.active = true;
3090       eval.error = none;
3091 #ifdef _DEBUG
3092       if(!size)
3093          _dpl(0, "GdbReadMemoryString called with size = 0!");
3094 #endif
3095       // GdbCommand(false, "-data-read-memory 0x%08x %c, %d, %d, %d", address, format, size, rows, cols);
3096       if(GetRuntimePlatform() == win32)
3097          GdbCommand(false, "-data-read-memory 0x%016I64x %c, %d, %d, %d", address, format, size, rows, cols);
3098       else
3099          GdbCommand(false, "-data-read-memory 0x%016llx %c, %d, %d, %d", address, format, size, rows, cols);
3100       if(eval.active)
3101          ide.outputView.debugBox.Logf("Debugger Error: GdbReadMemoryString\n");
3102       return eval.result;
3103    }
3104
3105    byte * ::GdbReadMemory(uint64 address, int bytes)
3106    {
3107       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbReadMemory(", address, ")");
3108       eval.active = true;
3109       eval.error = none;
3110       //GdbCommand(false, "-data-read-memory 0x%08x %c, 1, 1, %d", address, 'u', bytes);
3111       if(GetRuntimePlatform() == win32)
3112          GdbCommand(false, "-data-read-memory 0x%016I64x %c, 1, 1, %d", address, 'u', bytes);
3113       else
3114          GdbCommand(false, "-data-read-memory 0x%016llx %c, 1, 1, %d", address, 'u', bytes);
3115 #ifdef _DEBUG
3116       if(!bytes)
3117          _dpl(0, "GdbReadMemory called with bytes = 0!");
3118 #endif
3119       if(eval.active)
3120          ide.outputView.debugBox.Logf("Debugger Error: GdbReadMemory\n");
3121       else if(eval.result && strcmp(eval.result, "N/A"))
3122       {
3123          byte * result = new byte[bytes];
3124          byte * string = eval.result;
3125          int c = 0;
3126          while(true)
3127          {
3128             result[c++] = (byte)strtol(string, &string, 10);
3129             if(string)
3130             {
3131                if(*string == ',')
3132                   string++;
3133                 else
3134                   break;
3135             }
3136             else
3137                break;
3138          }
3139          return result;
3140       }
3141       return null;
3142    }
3143
3144    bool BreakpointHit(GdbDataStop stopItem, Breakpoint bpInternal, Breakpoint bpUser)
3145    {
3146       bool result = true;
3147       char * s1 = null; char * s2 = null;
3148       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::BreakpointHit(",
3149             "bpInternal(", bpInternal ? s1=bpInternal.CopyLocationString(false) : null, "), ",
3150             "bpUser(", bpUser ? s2=bpUser.CopyLocationString(false) : null, ")) -- ",
3151             "ignoreBreakpoints(", ignoreBreakpoints, "), ",
3152             "hitCursorBreakpoint(", bpUser && bpUser.type == runToCursor,  ")");
3153       delete s1; delete s2;
3154
3155       if(bpUser)
3156       {
3157          bool conditionMet = true;
3158          if(bpUser.condition)
3159          {
3160             if(WatchesLinkCodeEditor())
3161                conditionMet = ResolveWatch(bpUser.condition);
3162             else
3163                conditionMet = false;
3164          }
3165          bpUser.hits++;
3166          if(conditionMet)
3167          {
3168             if(!bpUser.ignore)
3169                bpUser.breaks++;
3170             else
3171             {
3172                bpUser.ignore--;
3173                result = false;
3174             }
3175          }
3176          else
3177             result = false;
3178          if(stopItem.frame.line && bpUser.line != stopItem.frame.line)
3179          {
3180             // updating user breakpoint on hit location difference
3181             // todo, print something?
3182             bpUser.line = stopItem.frame.line;
3183             ide.breakpointsView.UpdateBreakpoint(bpUser.row);
3184             ide.workspace.Save();
3185          }
3186          else
3187             ide.breakpointsView.UpdateBreakpoint(bpUser.row);
3188       }
3189       if(bpInternal)
3190       {
3191          bpInternal.hits++;
3192          if(bpInternal.type == internalModulesLoaded)
3193             modules = true;
3194          if(userAction == stepOver)
3195          {
3196             if((bpInternal.type == internalEntry && ((intBpMain && intBpMain.inserted) || (intBpWinMain && intBpWinMain.inserted))) ||
3197                   (bpInternal.type == internalMain && intBpWinMain && intBpWinMain.inserted))
3198                result = false;
3199          }
3200          if(!bpUser && !userAction.breaksOnInternalBreakpoint)
3201          {
3202             if(userAction == stepOut)
3203                StepOut(ignoreBreakpoints);
3204             else
3205                result = false;
3206          }
3207       }
3208
3209       if(!bpUser && !bpInternal)
3210          result = false;
3211
3212       return result;
3213    }
3214
3215    void ValgrindTargetThreadExit()
3216    {
3217       ide.outputView.debugBox.Logf($"ValgrindTargetThreadExit\n");
3218       if(vgTargetHandle)
3219       {
3220          vgTargetHandle.Wait();
3221          delete vgTargetHandle;
3222       }
3223       HandleExit(null, null);
3224    }
3225
3226    void GdbThreadExit()
3227    {
3228       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbThreadExit()");
3229       if(state != terminated)
3230       {
3231          _ChangeState(terminated);
3232          targetProcessId = 0;
3233          ClearBreakDisplay();
3234
3235          if(vgLogFile)
3236             delete vgLogFile;
3237          if(gdbHandle)
3238          {
3239             serialSemaphore.Release();
3240             gdbTimer.Stop();
3241             gdbHandle.Wait();
3242             delete gdbHandle;
3243
3244             ide.outputView.debugBox.Logf($"Debugger Fatal Error: GDB lost\n");
3245             ide.outputView.debugBox.Logf($"Debugging stopped\n");
3246             ide.Update(null);
3247             HideDebuggerViews();
3248          }
3249          //_ChangeState(terminated);
3250       }
3251    }
3252
3253    void GdbThreadMain(char * output)
3254    {
3255       int i;
3256       char * t;
3257       Array<char *> outTokens { minAllocSize = 50 };
3258       Array<char *> subTokens { minAllocSize = 50 };
3259       DebugListItem item { };
3260       DebugListItem item2 { };
3261       bool setWaitingForPID = false;
3262
3263 #if defined(GDB_DEBUG_CONSOLE) || defined(GDB_DEBUG_GUI)
3264 #ifdef GDB_DEBUG_CONSOLE
3265       // _dpl2(_dpct, dplchan::gdbOutput, 0, output);
3266       puts(output);
3267 #endif
3268 #ifdef GDB_DEBUG_OUTPUT
3269       {
3270          int len = strlen(output);
3271          if(len > 1024)
3272          {
3273             int c;
3274             char * start;
3275             char tmp[1025];
3276             tmp[1024] = '\0';
3277             start = output;
3278             for(c = 0; c < len / 1024; c++)
3279             {
3280                strncpy(tmp, start, 1024);
3281                ide.outputView.gdbBox.Logf("out: %s\n", tmp);
3282                start += 1024;
3283             }
3284             ide.outputView.gdbBox.Logf("out: %s\n", start);
3285          }
3286          else
3287          {
3288             ide.outputView.gdbBox.Logf("out: %s\n", output);
3289          }
3290       }
3291 #endif
3292 #ifdef GDB_DEBUG_CONSOLE
3293          strcpy(lastGdbOutput, output);
3294 #endif
3295 #ifdef GDB_DEBUG_GUI
3296          if(ide.gdbDialog) ide.gdbDialog.AddOutput(output);
3297 #endif
3298 #endif
3299
3300       switch(output[0])
3301       {
3302          case '~':
3303             if(strstr(output, "No debugging symbols found") || strstr(output, "(no debugging symbols found)"))
3304             {
3305                symbols = false;
3306                ide.outputView.debugBox.Logf($"Target doesn't contain debug information!\n");
3307                ide.Update(null);
3308             }
3309             if(!entryPoint && (t = strstr(output, "Entry point:")))
3310             {
3311                char * addr = t + strlen("Entry point:");
3312                t = addr;
3313                if(*t++ == ' ' && *t++ == '0' && *t == 'x')
3314                {
3315                   *addr = '*';
3316                   while(isxdigit(*++t));
3317                   *t = '\0';
3318                   for(bp : sysBPs; bp.type == internalEntry)
3319                   {
3320                      bp.function = addr;
3321                      bp.enabled = entryPoint = true;
3322                      break;
3323                   }
3324                }
3325             }
3326             break;
3327          case '^':
3328             gdbReady = false;
3329             if(TokenizeList(output, ',', outTokens) && !strcmp(outTokens[0], "^done"))
3330             {
3331                //if(outTokens.count == 1)
3332                {
3333                   if(sentKill)
3334                   {
3335                      sentKill = false;
3336                      _ChangeState(loaded);
3337                      targetProcessId = 0;
3338                      if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item))
3339                      {
3340                         if(!strcmp(item.name, "reason"))
3341                         {
3342                            char * reason = item.value;
3343                            StripQuotes(reason, reason);
3344                            if(!strcmp(reason, "exited-normally") || !strcmp(reason, "exited") || !strcmp(reason, "exited-signalled"))
3345                            {
3346                               char * exitCode;
3347                               if(outTokens.count > 2 && TokenizeListItem(outTokens[2], item2))
3348                               {
3349                                  StripQuotes(item2.value, item2.value);
3350                                  if(!strcmp(item2.name, "exit-code"))
3351                                     exitCode = item2.value;
3352                                  else
3353                                     exitCode = null;
3354                               }
3355                               else
3356                                  exitCode = null;
3357                               HandleExit(reason, exitCode);
3358                            }
3359                         }
3360                         else
3361                            _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "kill reply (", item.name, "=", item.value, ") is unheard of");
3362                      }
3363                      else
3364                         HandleExit(null, null);
3365                   }
3366                }
3367                if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item))
3368                {
3369                   if(!strcmp(item.name, "bkpt"))
3370                   {
3371                      sentBreakInsert = false;
3372 #ifdef _DEBUG
3373                      if(bpItem)
3374                         _dpl(0, "problem");
3375 #endif
3376                      delete bpItem;
3377                      bpItem = ParseBreakpoint(item.value, outTokens);
3378                      //breakType = bpValidation;
3379                   }
3380                   else if(!strcmp(item.name, "depth"))
3381                   {
3382                      StripQuotes(item.value, item.value);
3383                      frameCount = atoi(item.value);
3384                      activeFrame = null;
3385                      stackFrames.Free(Frame::Free);
3386                   }
3387                   else if(!strcmp(item.name, "stack"))
3388                   {
3389                      Frame frame;
3390                      if(stackFrames.count)
3391                         ide.callStackView.Logf("...\n");
3392                      else
3393                         activeFrame = null;
3394                      item.value = StripBrackets(item.value);
3395                      TokenizeList(item.value, ',', subTokens);
3396                      for(i = 0; i < subTokens.count; i++)
3397                      {
3398                         if(TokenizeListItem(subTokens[i], item))
3399                         {
3400                            if(!strcmp(item.name, "frame"))
3401                            {
3402                               frame = Frame { };
3403                               stackFrames.Add(frame);
3404                               item.value = StripCurlies(item.value);
3405                               ParseFrame(frame, item.value);
3406                               if(frame.file && frame.from)
3407                                  _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "unexpected frame file and from members present");
3408                               if(frame.file)
3409                               {
3410                                  char * s = null;
3411                                  if(activeFrameLevel == -1)
3412                                  {
3413                                     if(ide.projectView.IsModuleInProject(frame.file));
3414                                     {
3415                                        if(frame.level != 0)
3416                                        {
3417                                           //stopItem.frame = frame;
3418                                           breakType = selectFrame;
3419                                        }
3420                                        else
3421                                           activeFrame = frame;
3422                                        activeFrameLevel = frame.level;
3423                                     }
3424                                  }
3425                                  ide.callStackView.Logf("%3d ", frame.level);
3426                                  if(!strncmp(frame.func, "__ecereMethod_", strlen("__ecereMethod_")))
3427                                     ide.callStackView.Logf($"%s Method, %s:%d\n", &frame.func[strlen("__ecereMethod_")], (s = CopySystemPath(frame.file)), frame.line);
3428                                  else if(!strncmp(frame.func, "__ecereProp_", strlen("__ecereProp_")))
3429                                     ide.callStackView.Logf($"%s Property, %s:%d\n", &frame.func[strlen("__ecereProp_")], (s = CopySystemPath(frame.file)), frame.line);
3430                                  else if(!strncmp(frame.func, "__ecereConstructor_", strlen("__ecereConstructor_")))
3431                                     ide.callStackView.Logf($"%s Constructor, %s:%d\n", &frame.func[strlen("__ecereConstructor_")], (s = CopySystemPath(frame.file)), frame.line);
3432                                  else if(!strncmp(frame.func, "__ecereDestructor_", strlen("__ecereDestructor_")))
3433                                     ide.callStackView.Logf($"%s Destructor, %s:%d\n", &frame.func[strlen("__ecereDestructor_")], (s = CopySystemPath(frame.file)), frame.line);
3434                                  else
3435                                     ide.callStackView.Logf($"%s Function, %s:%d\n", frame.func, (s = CopySystemPath(frame.file)), frame.line);
3436                                  delete s;
3437                               }
3438                               else
3439                               {
3440                                  ide.callStackView.Logf("%3d ", frame.level);
3441
3442                                  if(frame.from)
3443                                  {
3444                                     char * s = null;
3445                                     ide.callStackView.Logf($"inside %s, %s\n", frame.func, (s = CopySystemPath(frame.from)));
3446                                     delete s;
3447                                  }
3448                                  else if(frame.func)
3449                                     ide.callStackView.Logf("%s\n", frame.func);
3450                                  else
3451                                     ide.callStackView.Logf($"unknown source\n");
3452                               }
3453                            }
3454                            else
3455                               _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "stack content (", item.name, "=", item.value, ") is unheard of");
3456                         }
3457                      }
3458                      if(activeFrameLevel == -1)
3459                      {
3460                         activeFrameLevel = 0;
3461                         activeFrame = stackFrames.first;
3462                      }
3463                      ide.callStackView.Home();
3464                      ide.Update(null);
3465                      subTokens.RemoveAll();
3466                   }
3467                   /*else if(!strcmp(item.name, "frame"))
3468                   {
3469                      Frame frame { };
3470                      item.value = StripCurlies(item.value);
3471                      ParseFrame(&frame, item.value);
3472                   }*/
3473                   else if(!strcmp(item.name, "thread-ids"))
3474                   {
3475                      ide.threadsView.Clear();
3476                      item.value = StripCurlies(item.value);
3477                      TokenizeList(item.value, ',', subTokens);
3478                      for(i = subTokens.count - 1; ; i--)
3479                      {
3480                         if(TokenizeListItem(subTokens[i], item))
3481                         {
3482                            if(!strcmp(item.name, "thread-id"))
3483                            {
3484                               int value;
3485                               StripQuotes(item.value, item.value);
3486                               value = atoi(item.value);
3487                               ide.threadsView.Logf("%3d \n", value);
3488                            }
3489                            else
3490                               _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "threads content (", item.name, "=", item.value, ") is unheard of");
3491                         }
3492                         if(!i)
3493                            break;
3494                      }
3495                      ide.threadsView.Home();
3496                      ide.Update(null);
3497                      subTokens.RemoveAll();
3498                      //if(!strcmp(outTokens[2], "number-of-threads"))
3499                   }
3500                   else if(!strcmp(item.name, "new-thread-id"))
3501                   {
3502                      StripQuotes(item.value, item.value);
3503                      activeThread = atoi(item.value);
3504                   }
3505                   else if(!strcmp(item.name, "value"))
3506                   {
3507                      StripQuotes(item.value, item.value);
3508                      eval.result = CopyString(item.value);
3509                      eval.active = false;
3510                   }
3511                   else if(!strcmp(item.name, "addr"))
3512                   {
3513                      for(i = 2; i < outTokens.count; i++)
3514                      {
3515                         if(TokenizeListItem(outTokens[i], item))
3516                         {
3517                            if(!strcmp(item.name, "total-bytes"))
3518                            {
3519                               StripQuotes(item.value, item.value);
3520                               eval.bytes = atoi(item.value);
3521                            }
3522                            else if(!strcmp(item.name, "next-row"))
3523                            {
3524                               StripQuotes(item.value, item.value);
3525                               eval.nextBlockAddress = _strtoui64(item.value, null, 0);
3526                            }
3527                            else if(!strcmp(item.name, "memory"))
3528                            {
3529                               int j;
3530                               //int value;
3531                               //StripQuotes(item.value, item.value);
3532                               item.value = StripBrackets(item.value);
3533                               // this should be treated as a list...
3534                               item.value = StripCurlies(item.value);
3535                               TokenizeList(item.value, ',', subTokens);
3536                               for(j = 0; j < subTokens.count; j++)
3537                               {
3538                                  if(TokenizeListItem(subTokens[j], item))
3539                                  {
3540                                     if(!strcmp(item.name, "data"))
3541                                     {
3542                                        item.value = StripBrackets(item.value);
3543                                        StripQuotes2(item.value, item.value);
3544                                        eval.result = CopyString(item.value);
3545                                        eval.active = false;
3546                                     }
3547                                  }
3548                               }
3549                               subTokens.RemoveAll();
3550                            }
3551                         }
3552                      }
3553                   }
3554                   else if(!strcmp(item.name, "source-path") || !strcmp(item.name, "BreakpointTable"))
3555                      _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "command reply (", item.name, "=", item.value, ") is ignored");
3556                   else
3557                      _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "command reply (", item.name, "=", item.value, ") is unheard of");
3558                }
3559             }
3560             else if(!strcmp(outTokens[0], "^running"))
3561             {
3562                waitingForPID = true;
3563                setWaitingForPID = true;
3564                ClearBreakDisplay();
3565             }
3566             else if(!strcmp(outTokens[0], "^exit"))
3567             {
3568                _ChangeState(terminated);
3569                // ide.outputView.debugBox.Logf("Exit\n");
3570                // ide.Update(null);
3571                gdbReady = true;
3572                serialSemaphore.Release();
3573             }
3574             else if(!strcmp(outTokens[0], "^error"))
3575             {
3576                if(sentBreakInsert)
3577                {
3578                   sentBreakInsert = false;
3579                   breakpointError = true;
3580                }
3581
3582                if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item))
3583                {
3584                   if(!strcmp(item.name, "msg"))
3585                   {
3586                      StripQuotes(item.value, item.value);
3587                      if(eval.active)
3588                      {
3589                         eval.active = false;
3590                         eval.result = null;
3591                         if(strstr(item.value, "No symbol") && strstr(item.value, "in current context"))
3592                            eval.error = symbolNotFound;
3593                         else if(strstr(item.value, "Cannot access memory at address"))
3594                            eval.error = memoryCantBeRead;
3595                         else
3596                            eval.error = unknown;
3597                      }
3598                      else if(!strcmp(item.value, "Previous frame inner to this frame (corrupt stack?)"))
3599                      {
3600                      }
3601                      else if(!strncmp(item.value, "Cannot access memory at address", 31))
3602                      {
3603                      }
3604                      else if(!strcmp(item.value, "Cannot find bounds of current function"))
3605                      {
3606                         _ChangeState(stopped);
3607                         gdbHandle.Printf("-exec-continue\n");
3608                      }
3609                      else if(!strcmp(item.value, "ptrace: No such process."))
3610                      {
3611                         _ChangeState(loaded);
3612                         targetProcessId = 0;
3613                      }
3614                      else if(!strcmp(item.value, "Function \\\"WinMain\\\" not defined."))
3615                      {
3616                      }
3617                      else if(!strcmp(item.value, "You can't do that without a process to debug."))
3618                      {
3619                         _ChangeState(loaded);
3620                         targetProcessId = 0;
3621                      }
3622                      else if(strstr(item.value, "No such file or directory."))
3623                      {
3624                         _ChangeState(loaded);
3625                         targetProcessId = 0;
3626                      }
3627                      else if(strstr(item.value, "During startup program exited with code "))
3628                      {
3629                         _ChangeState(loaded);
3630                         targetProcessId = 0;
3631                      }
3632                      else
3633                      {
3634 #ifdef _DEBUG
3635                         if(strlen(item.value) < MAX_F_STRING)
3636                         {
3637                            char * s = null;
3638                            ide.outputView.debugBox.Logf("GDB: %s\n", (s = CopyUnescapedString(item.value)));
3639                            delete s;
3640                         }
3641                         else
3642                            ide.outputView.debugBox.Logf("GDB: %s\n", item.value);
3643 #endif
3644                      }
3645                   }
3646                }
3647                else
3648                   _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "error content (", item.name, "=", item.value, ") is unheard of");
3649             }
3650             else
3651                _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "result-record: ", outTokens[0]);
3652             outTokens.RemoveAll();
3653             break;
3654          case '+':
3655             _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "status-async-output: ", outTokens[0]);
3656             break;
3657          case '=':
3658             if(TokenizeList(output, ',', outTokens))
3659             {
3660                if(!strcmp(outTokens[0], "=library-loaded"))
3661                   FGODetectLoadedLibraryForAddedProjectIssues(outTokens);
3662                else if(!strcmp(outTokens[0], "=thread-group-created") || !strcmp(outTokens[0], "=thread-group-added") ||
3663                         !strcmp(outTokens[0], "=thread-group-started") || !strcmp(outTokens[0], "=thread-group-exited") ||
3664                         !strcmp(outTokens[0], "=thread-created") || !strcmp(outTokens[0], "=thread-exited") ||
3665                         !strcmp(outTokens[0], "=cmd-param-changed") || !strcmp(outTokens[0], "=library-unloaded") ||
3666                         !strcmp(outTokens[0], "=breakpoint-modified"))
3667                   _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, outTokens[0], outTokens.count>1 ? outTokens[1] : "",
3668                            outTokens.count>2 ? outTokens[2] : "", outTokens.count>3 ? outTokens[3] : "",
3669                            outTokens.count>4 ? outTokens[4] : "", outTokens.count>5 ? outTokens[5] : "",
3670                            outTokens.count>6 ? outTokens[6] : "", outTokens.count>7 ? outTokens[7] : "",
3671                            outTokens.count>8 ? outTokens[8] : "", outTokens.count>9 ? outTokens[9] : "");
3672                else
3673                   _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "notify-async-output: ", outTokens[0]);
3674             }
3675             outTokens.RemoveAll();
3676             break;
3677          case '*':
3678             gdbReady = false;
3679             if(TokenizeList(output, ',', outTokens))
3680             {
3681                if(!strcmp(outTokens[0],"*running"))
3682                {
3683                   waitingForPID = true;
3684                   setWaitingForPID = true;
3685                }
3686                else if(!strcmp(outTokens[0], "*stopped"))
3687                {
3688                   int tk;
3689                   _ChangeState(stopped);
3690
3691                   for(tk = 1; tk < outTokens.count; tk++)
3692                   {
3693                      if(TokenizeListItem(outTokens[tk], item))
3694                      {
3695                         if(!strcmp(item.name, "reason"))
3696                         {
3697                            char * reason = item.value;
3698                            StripQuotes(reason, reason);
3699                            if(!strcmp(reason, "exited-normally") || !strcmp(reason, "exited") || !strcmp(reason, "exited-signalled"))
3700                            {
3701                               char * exitCode;
3702                               if(outTokens.count > tk+1 && TokenizeListItem(outTokens[tk+1], item2))
3703                               {
3704                                  tk++;
3705                                  StripQuotes(item2.value, item2.value);
3706                                  if(!strcmp(item2.name, "exit-code"))
3707                                     exitCode = item2.value;
3708                                  else
3709                                     exitCode = null;
3710                               }
3711                               else
3712                                  exitCode = null;
3713                               HandleExit(reason, exitCode);
3714                               needReset = true;
3715                            }
3716                            else if(!strcmp(reason, "breakpoint-hit") ||
3717                                    !strcmp(reason, "function-finished") ||
3718                                    !strcmp(reason, "end-stepping-range") ||
3719                                    !strcmp(reason, "location-reached") ||
3720                                    !strcmp(reason, "signal-received"))
3721                            {
3722                               char r = reason[0];
3723 #ifdef _DEBUG
3724                               if(stopItem) _dpl(0, "problem");
3725 #endif
3726                               stopItem = GdbDataStop { };
3727                               stopItem.reason = r == 'b' ? breakpointHit : r == 'f' ? functionFinished : r == 'e' ? endSteppingRange : r == 'l' ? locationReached : signalReceived;
3728
3729                               for(i = tk+1; i < outTokens.count; i++)
3730                               {
3731                                  TokenizeListItem(outTokens[i], item);
3732                                  StripQuotes(item.value, item.value);
3733                                  if(!strcmp(item.name, "thread-id"))
3734                                     stopItem.threadid = atoi(item.value);
3735                                  else if(!strcmp(item.name, "frame"))
3736                                  {
3737                                     item.value = StripCurlies(item.value);
3738                                     ParseFrame(stopItem.frame, item.value);
3739                                  }
3740                                  else if(stopItem.reason == breakpointHit && !strcmp(item.name, "bkptno"))
3741                                     stopItem.bkptno = atoi(item.value);
3742                                  else if(stopItem.reason == functionFinished && !strcmp(item.name, "gdb-result-var"))
3743                                     stopItem.gdbResultVar = CopyString(item.value);
3744                                  else if(stopItem.reason == functionFinished && !strcmp(item.name, "return-value"))
3745                                     stopItem.returnValue = CopyString(item.value);
3746                                  else if(stopItem.reason == signalReceived && !strcmp(item.name, "signal-name"))
3747                                     stopItem.name = CopyString(item.value);
3748                                  else if(stopItem.reason == signalReceived && !strcmp(item.name, "signal-meaning"))
3749                                     stopItem.meaning = CopyString(item.value);
3750                                  else if(!strcmp(item.name, "stopped-threads"))
3751                                     _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, reason, ": Advanced thread debugging not handled");
3752                                  else if(!strcmp(item.name, "core"))
3753                                     _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, reason, ": Information (core) not used");
3754                                  else if(!strcmp(item.name, "disp"))
3755                                     _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, reason, ": (", item.name, "=", item.value, ")");
3756                                  else
3757                                     _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown ", reason, " item name (", item.name, "=", item.value, ")");
3758                               }
3759
3760                               if(stopItem.reason == signalReceived && !strcmp(stopItem.name, "SIGTRAP"))
3761                               {
3762                                  switch(breakType)
3763                                  {
3764                                     case internal:
3765                                        breakType = none;
3766                                        break;
3767                                     case restart:
3768                                     case stop:
3769                                        break;
3770                                     default:
3771                                        event = breakEvent;
3772                                  }
3773                               }
3774                               else
3775                               {
3776                                  event = r == 'b' ? hit : r == 'f' ? functionEnd : r == 'e' ? stepEnd : r == 'l' ? locationReached : signal;
3777                                  ide.Update(null);
3778                               }
3779                            }
3780                            else if(!strcmp(reason, "watchpoint-trigger"))
3781                               _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason watchpoint trigger not handled");
3782                            else if(!strcmp(reason, "read-watchpoint-trigger"))
3783                               _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason read watchpoint trigger not handled");
3784                            else if(!strcmp(reason, "access-watchpoint-trigger"))
3785                               _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason access watchpoint trigger not handled");
3786                            else if(!strcmp(reason, "watchpoint-scope"))
3787                               _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason watchpoint scope not handled");
3788                            else
3789                               _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown reason: ", reason);
3790                         }
3791                         else
3792                         {
3793                            PrintLn(output);
3794                         }
3795                      }
3796                   }
3797                   if(usingValgrind && event == none && !stopItem)
3798                      event = valgrindStartPause;
3799                   app.SignalEvent();
3800                }
3801             }
3802             else
3803                _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown exec-async-output: ", outTokens[0]);
3804             outTokens.RemoveAll();
3805             break;
3806          case '(':
3807             if(!strcmpi(output, "(gdb) "))
3808             {
3809                if(waitingForPID)
3810                {
3811                   Time startTime = GetTime();
3812                   char exeFile[MAX_LOCATION];
3813                   int oldProcessID = targetProcessId;
3814                   GetLastDirectory(targetFile, exeFile);
3815
3816                   while(!targetProcessId/*true*/)
3817                   {
3818                      targetProcessId = Process_GetChildExeProcessId(gdbProcessId, exeFile);
3819                      if(targetProcessId) break;
3820                      // Can't break on Peek(), GDB is giving us =library and other info before the process is listed in /proc
3821                      // if(gdbHandle.Peek()) break;
3822                      Sleep(0.01);
3823                      if(gdbHandle.Peek() && GetTime() - startTime > 2.5)  // Give the process 2.5 seconds to show up in /proc
3824                         break;
3825                   }
3826
3827                   if(targetProcessId)
3828                      _ChangeState(running);
3829                   else if(!oldProcessID)
3830                   {
3831                      ide.outputView.debugBox.Logf($"Debugger Error: No target process ID\n");
3832                      // TO VERIFY: The rest of this block has not been thoroughly tested in this particular location
3833                      gdbHandle.Printf("-gdb-exit\n");
3834                      gdbTimer.Stop();
3835                      _ChangeState(terminated); //loaded;
3836                      prjConfig = null;
3837
3838                      if(ide.workspace)
3839                      {
3840                         for(bp : ide.workspace.breakpoints)
3841                            bp.inserted = false;
3842                      }
3843                      for(bp : sysBPs)
3844                         bp.inserted = false;
3845                      if(bpRunToCursor)
3846                         bpRunToCursor.inserted = false;
3847
3848                      ide.outputView.debugBox.Logf($"Debugging stopped\n");
3849                      ClearBreakDisplay();
3850
3851                #if defined(__unix__)
3852                      if(!usingValgrind && FileExists(progFifoPath)) //fileCreated)
3853                      {
3854                         progThread.terminate = true;
3855                         if(fifoFile)
3856                         {
3857                            fifoFile.CloseInput();
3858                            app.Unlock();
3859                            progThread.Wait();
3860                            app.Lock();
3861                            delete fifoFile;
3862                         }
3863
3864                         DeleteFile(progFifoPath);
3865                         progFifoPath[0] = '\0';
3866                         rmdir(progFifoDir);
3867                      }
3868                #endif
3869                   }
3870                }
3871                gdbReady = true;
3872                serialSemaphore.Release();
3873             }
3874             else
3875                _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, $"Unknown prompt", output);
3876
3877             break;
3878          case '&':
3879             if(!strncmp(output, "&\"warning:", 10))
3880             {
3881                char * content;
3882                content = strstr(output, "\"");
3883                StripQuotes(content, content);
3884                content = strstr(content, ":");
3885                if(content)
3886                   content++;
3887                if(content)
3888                {
3889                   char * s = null;
3890                   ide.outputView.debugBox.LogRaw((s = CopyUnescapedString(content)));
3891                   delete s;
3892                   ide.Update(null);
3893                }
3894             }
3895             break;
3896          default:
3897             _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, $"Unknown output: ", output);
3898       }
3899       if(!setWaitingForPID)
3900          waitingForPID = false;
3901       setWaitingForPID = false;
3902
3903       delete outTokens;
3904       delete subTokens;
3905       delete item;
3906       delete item2;
3907    }
3908
3909    // From GDB Output functions
3910    void FGODetectLoadedLibraryForAddedProjectIssues(Array<char *> outTokens)
3911    {
3912       char path[MAX_LOCATION] = "";
3913       char file[MAX_FILENAME] = "";
3914       bool symbolsLoaded;
3915       DebugListItem item { };
3916       //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::FGODetectLoadedLibraryForAddedProjectIssues()");
3917       for(token : outTokens)
3918       {
3919          if(TokenizeListItem(token, item))
3920          {
3921             if(!strcmp(item.name, "target-name"))
3922             {
3923                StripQuotes(item.value, path);
3924                MakeSystemPath(path);
3925                GetLastDirectory(path, file);
3926             }
3927             else if(!strcmp(item.name, "symbols-loaded"))
3928             {
3929                symbolsLoaded = (atoi(item.value) == 1);
3930             }
3931          }
3932       }
3933       delete item;
3934       if(path[0] && file[0])
3935       {
3936          for(prj : ide.workspace.projects; prj != ide.workspace.projects.firstIterator.data)
3937          {
3938             bool match;
3939             char * dot;
3940             char prjTargetPath[MAX_LOCATION];
3941             char prjTargetFile[MAX_FILENAME];
3942             DirExpression targetDirExp = prj.GetTargetDir(currentCompiler, prj.config, bitDepth);
3943             strcpy(prjTargetPath, prj.topNode.path);
3944             PathCat(prjTargetPath, targetDirExp.dir);
3945             delete targetDirExp;
3946             prjTargetFile[0] = '\0';
3947             prj.CatTargetFileName(prjTargetFile, currentCompiler, prj.config);
3948             PathCat(prjTargetPath, prjTargetFile);
3949             MakeSystemPath(prjTargetPath);
3950
3951             match = !fstrcmp(prjTargetFile, file);
3952             if(!match && (dot = strstr(prjTargetFile, ".so.")))
3953             {
3954                char * dot3 = strstr(dot+4, ".");
3955                if(dot3)
3956                {
3957                   dot3[0] = '\0';
3958                   match = !fstrcmp(prjTargetFile, file);
3959                }
3960                if(!match)
3961                {
3962                   dot[3] = '\0';
3963                   match = !fstrcmp(prjTargetFile, file);
3964                }
3965             }
3966             if(match)
3967             {
3968                // TODO: nice visual feedback to better warn user. use some ide notification system or other means.
3969                /* -- this is disabled because we can't trust gdb's symbols-loaded="0" field for =library-loaded (http://sourceware.org/bugzilla/show_bug.cgi?id=10693)
3970                if(!symbolsLoaded)
3971                   ide.outputView.debugBox.Logf($"Attention! No symbols for loaded library %s matched to the %s added project.\n", path, prj.topNode.name);
3972                */
3973                match = !fstrcmp(prjTargetPath, path);
3974                if(!match && (dot = strstr(prjTargetPath, ".so.")))
3975                {
3976                   char * dot3 = strstr(dot+4, ".");
3977                   if(dot3)
3978                   {
3979                      dot3[0] = '\0';
3980                      match = !fstrcmp(prjTargetPath, path);
3981                   }
3982                   if(!match)
3983                   {
3984                      dot[3] = '\0';
3985                      match = !fstrcmp(prjTargetPath, path);
3986                   }
3987                }
3988                if(match)
3989                   projectsLibraryLoaded[prj.name] = true;
3990                else
3991                   ide.outputView.debugBox.Logf($"Loaded library %s doesn't match the %s target of the %s added project.\n", path, prjTargetPath, prj.topNode.name);
3992                break;
3993             }
3994          }
3995       }
3996    }
3997
3998    void FGOBreakpointModified(Array<char *> outTokens)
3999    {
4000       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::FGOBreakpointModified() -- TODO only if needed: support breakpoint modified");
4001 #if 0
4002       DebugListItem item { };
4003       if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item))
4004       {
4005          if(!strcmp(item.name, "bkpt"))
4006          {
4007             GdbDataBreakpoint modBp = ParseBreakpoint(item.value, outTokens);
4008             delete modBp;
4009          }
4010       }
4011 #endif
4012    }
4013
4014
4015    ExpressionType ::DebugEvalExpTypeError(char * result)
4016    {
4017       _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::DebugEvalExpTypeError()");
4018       if(result)
4019          return dummyExp;
4020       switch(eval.error)
4021       {
4022          case symbolNotFound:
4023             return symbolErrorExp;
4024          case memoryCantBeRead:
4025             return memoryErrorExp;
4026       }
4027       return unknownErrorExp;
4028    }
4029
4030    char * ::EvaluateExpression(char * expression, ExpressionType * error)
4031    {
4032       char * result;
4033       _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::EvaluateExpression(", expression, ")");
4034       if(ide.projectView && ide.debugger.state == stopped)
4035       {
4036          result = GdbEvaluateExpression(expression);
4037          *error = DebugEvalExpTypeError(result);
4038       }
4039       else
4040       {
4041          result = null;
4042          *error = noDebuggerErrorExp;
4043       }
4044       return result;
4045    }
4046
4047    char * ::ReadMemory(uint64 address, int size, char format, ExpressionType * error)
4048    {
4049       // check for state
4050       char * result = GdbReadMemoryString(address, size, format, 1, 1);
4051       _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ReadMemory(", address, ")");
4052       if(!result || !strcmp(result, "N/A"))
4053          *error = memoryErrorExp;
4054       else
4055          *error = DebugEvalExpTypeError(result);
4056       return result;
4057    }
4058 }
4059
4060 class ValgrindLogThread : Thread
4061 {
4062    Debugger debugger;
4063
4064    unsigned int Main()
4065    {
4066       static char output[4096];
4067       Array<char> dynamicBuffer { minAllocSize = 4096 };
4068       File oldValgrindHandle = vgLogFile;
4069       incref oldValgrindHandle;
4070
4071       app.Lock();
4072       while(debugger.state != terminated && vgLogFile)
4073       {
4074          int result;
4075          app.Unlock();
4076          result = vgLogFile.Read(output, 1, sizeof(output));
4077          app.Lock();
4078          if(debugger.state == terminated || !vgLogFile/* || vgLogFile.Eof()*/)
4079             break;
4080          if(result)
4081          {
4082             int c;
4083             int start = 0;
4084
4085             for(c = 0; c<result; c++)
4086             {
4087                if(output[c] == '\n')
4088                {
4089                   int pos = dynamicBuffer.size;
4090                   dynamicBuffer.size += c - start;
4091                   memcpy(&dynamicBuffer[pos], output + start, c - start);
4092                   if(dynamicBuffer.count && dynamicBuffer[dynamicBuffer.count - 1] != '\r')
4093                   // COMMENTED OUT DUE TO ISSUE #135, FIXED
4094                   //if(dynamicBuffer.array[dynamicBuffer.count - 1] != '\r')
4095                      dynamicBuffer.size++;
4096                   dynamicBuffer[dynamicBuffer.count - 1] = '\0';
4097 #ifdef _DEBUG
4098                   // printf("%s\n", dynamicBuffer.array);
4099 #endif
4100                   if(strstr(&dynamicBuffer[0], "vgdb me"))
4101                      debugger.serialSemaphore.Release();
4102                   {
4103                      char * msg = strstr(&dynamicBuffer[0], "==");
4104                      if(msg)
4105                         msg = strstr(msg+2, "== ");
4106                      if(msg)
4107                      {
4108                         msg += 3;
4109                         switch(msg[0])
4110                         {
4111                            case '(':
4112                               msg = strstr(msg, "(action ");
4113                               if(msg)
4114                               {
4115                                  msg += 8;
4116                                  if(!strstr(msg, "at startup) vgdb me ...") || !strstr(msg, "on error) vgdb me ..."))
4117                                     msg = null;
4118                               }
4119                               break;
4120                            case 'T':
4121                               if(!strstr(msg, "TO DEBUG THIS PROCESS USING GDB: start GDB like this"))
4122                                  msg = null;
4123                               break;
4124                            case 'a':
4125                               if(!strstr(msg, "and then give GDB the following command"))
4126                                  msg = null;
4127                               break;
4128                            case ' ':
4129                               if(!strstr(msg, "/path/to/gdb") && !strstr(msg, "target remote | /usr/lib/valgrind/../../bin/vgdb --pid="))
4130                                  msg = null;
4131                               break;
4132                            case '-':
4133                               if(!strstr(msg, "--pid is optional if only one valgrind process is running"))
4134                                  msg = null;
4135                               break;
4136                            default:
4137                               msg = null;
4138                               break;
4139                         }
4140                      }
4141                      if(!msg)
4142                         ide.outputView.debugBox.Logf("%s\n", &dynamicBuffer[0]);
4143                   }
4144                   dynamicBuffer.size = 0;
4145                   start = c + 1;
4146                }
4147             }
4148             if(c == result)
4149             {
4150                int pos = dynamicBuffer.size;
4151                dynamicBuffer.size += c - start;
4152                memcpy(&dynamicBuffer[pos], output + start, c - start);
4153             }
4154          }
4155          else if(debugger.state == stopped)
4156          {
4157 /*#ifdef _DEBUG
4158             printf("Got end of file from GDB!\n");
4159 #endif*/
4160             app.Unlock();
4161             Sleep(0.2);
4162             app.Lock();
4163          }
4164       }
4165       delete dynamicBuffer;
4166       ide.outputView.debugBox.Logf($"ValgrindLogThreadExit\n");
4167       //if(oldValgrindHandle == vgLogFile)
4168          debugger.GdbThreadExit/*ValgrindLogThreadExit*/();
4169       delete oldValgrindHandle;
4170       app.Unlock();
4171       return 0;
4172    }
4173 }
4174
4175 class ValgrindTargetThread : Thread
4176 {
4177    Debugger debugger;
4178
4179    unsigned int Main()
4180    {
4181       static char output[4096];
4182       Array<char> dynamicBuffer { minAllocSize = 4096 };
4183       DualPipe oldValgrindHandle = vgTargetHandle;
4184       incref oldValgrindHandle;
4185
4186       app.Lock();
4187       while(debugger.state != terminated && vgTargetHandle && !vgTargetHandle.Eof())
4188       {
4189          int result;
4190          app.Unlock();
4191          result = vgTargetHandle.Read(output, 1, sizeof(output));
4192          app.Lock();
4193          if(debugger.state == terminated || !vgTargetHandle || vgTargetHandle.Eof())
4194             break;
4195          if(result)
4196          {
4197             int c;
4198             int start = 0;
4199
4200             for(c = 0; c<result; c++)
4201             {
4202                if(output[c] == '\n')
4203                {
4204                   int pos = dynamicBuffer.size;
4205                   dynamicBuffer.size += c - start;
4206                   memcpy(&dynamicBuffer[pos], output + start, c - start);
4207                   if(dynamicBuffer.count && dynamicBuffer[dynamicBuffer.count - 1] != '\r')
4208                   // COMMENTED OUT DUE TO ISSUE #135, FIXED
4209                   //if(dynamicBuffer.array[dynamicBuffer.count - 1] != '\r')
4210                      dynamicBuffer.size++;
4211                   dynamicBuffer[dynamicBuffer.count - 1] = '\0';
4212 #ifdef _DEBUG
4213                   // printf("%s\n", dynamicBuffer.array);
4214 #endif
4215                   ide.outputView.debugBox.Logf("%s\n", &dynamicBuffer[0]);
4216
4217                   dynamicBuffer.size = 0;
4218                   start = c + 1;
4219                }
4220             }
4221             if(c == result)
4222             {
4223                int pos = dynamicBuffer.size;
4224                dynamicBuffer.size += c - start;
4225                memcpy(&dynamicBuffer[pos], output + start, c - start);
4226             }
4227          }
4228          else
4229          {
4230 #ifdef _DEBUG
4231             printf("Got end of file from GDB!\n");
4232 #endif
4233          }
4234       }
4235       delete dynamicBuffer;
4236       //if(oldValgrindHandle == vgTargetHandle)
4237          debugger.ValgrindTargetThreadExit();
4238       delete oldValgrindHandle;
4239       app.Unlock();
4240       return 0;
4241    }
4242 }
4243
4244 class GdbThread : Thread
4245 {
4246    Debugger debugger;
4247
4248    unsigned int Main()
4249    {
4250       static char output[4096];
4251       Array<char> dynamicBuffer { minAllocSize = 4096 };
4252       DualPipe oldGdbHandle = gdbHandle;
4253       incref oldGdbHandle;
4254
4255       app.Lock();
4256       while(debugger.state != terminated && gdbHandle && !gdbHandle.Eof())
4257       {
4258          int result;
4259          app.Unlock();
4260          result = gdbHandle.Read(output, 1, sizeof(output));
4261          app.Lock();
4262          if(debugger.state == terminated || !gdbHandle || gdbHandle.Eof())
4263             break;
4264          if(result)
4265          {
4266             int c;
4267             int start = 0;
4268
4269             for(c = 0; c<result; c++)
4270             {
4271                if(output[c] == '\n')
4272                {
4273                   int pos = dynamicBuffer.size;
4274                   dynamicBuffer.size += c - start;
4275                   memcpy(&dynamicBuffer[pos], output + start, c - start);
4276                   if(dynamicBuffer.count && dynamicBuffer[dynamicBuffer.count - 1] != '\r')
4277                   // COMMENTED OUT DUE TO ISSUE #135, FIXED
4278                   //if(dynamicBuffer.array[dynamicBuffer.count - 1] != '\r')
4279                      dynamicBuffer.size++;
4280                   dynamicBuffer[dynamicBuffer.count - 1] = '\0';
4281 #ifdef _DEBUG
4282                   // _dpl(0, dynamicBuffer.array);
4283 #endif
4284                   debugger.GdbThreadMain(&dynamicBuffer[0]);
4285                   dynamicBuffer.size = 0;
4286                   start = c + 1;
4287                }
4288             }
4289             if(c == result)
4290             {
4291                int pos = dynamicBuffer.size;
4292                dynamicBuffer.size += c - start;
4293                memcpy(&dynamicBuffer[pos], output + start, c - start);
4294             }
4295          }
4296          else
4297          {
4298 #ifdef _DEBUG
4299             _dpl(0, "Got end of file from GDB!");
4300 #endif
4301          }
4302       }
4303       delete dynamicBuffer;
4304       //if(oldGdbHandle == gdbHandle)
4305          debugger.GdbThreadExit();
4306       delete oldGdbHandle;
4307       app.Unlock();
4308       return 0;
4309    }
4310 }
4311
4312 static define createFIFOMsg = $"err: Unable to create FIFO %s\n";
4313 static define openFIFOMsg = $"err: Unable to open FIFO %s for read\n";
4314
4315 #if defined(__unix__)
4316 #define uint _uint
4317 #include <errno.h>
4318 #include <stdio.h>
4319 #include <fcntl.h>
4320 #include <sys/types.h>
4321 #undef uint
4322
4323 File fifoFile;
4324
4325 class ProgramThread : Thread
4326 {
4327    bool terminate;
4328    unsigned int Main()
4329    {
4330       bool result = true;
4331       bool fileCreated = false;
4332       mode_t mask = 0600;
4333       static char output[1000];
4334       int fd;
4335
4336       /*if(!mkfifo(progFifoPath, mask))
4337       {
4338          fileCreated = true;
4339       }
4340       else
4341       {
4342          app.Lock();
4343          ide.outputView.debugBox.Logf($"err: Unable to create FIFO %s\n", progFifoPath);
4344          app.Unlock();
4345       }*/
4346
4347       if(FileExists(progFifoPath)) //fileCreated)
4348       {
4349          fifoFile = FileOpen(progFifoPath, read);
4350          if(!fifoFile)
4351          {
4352             app.Lock();
4353             ide.outputView.debugBox.Logf(openFIFOMsg, progFifoPath);
4354             app.Unlock();
4355          }
4356          else
4357          {
4358             fd = fileno((FILE *)fifoFile.input);
4359             //fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
4360          }
4361       }
4362
4363       while(!terminate && fifoFile && !fifoFile.Eof())
4364       {
4365          fd_set rs, es;
4366          struct timeval time;
4367          int selectResult;
4368          time.tv_sec = 1;
4369          time.tv_usec = 0;
4370          FD_ZERO(&rs);
4371          FD_ZERO(&es);
4372          FD_SET(fd, &rs);
4373          FD_SET(fd, &es);
4374          selectResult = select(fd + 1, &rs, null, null, &time);
4375          if(FD_ISSET(fd, &rs))
4376          {
4377             int result = (int)read(fd, output, sizeof(output)-1);
4378             if(!result || (result < 0 && errno != EAGAIN))
4379                break;
4380             if(result > 0)
4381             {
4382                output[result] = '\0';
4383                if(strcmp(output,"&\"warning: GDB: Failed to set controlling terminal: Invalid argument\\n\"\n"))
4384                {
4385                   app.Lock();
4386                   ide.outputView.debugBox.Log(output);
4387                   app.Unlock();
4388                }
4389             }
4390          }
4391       }
4392
4393       //if(fifoFile)
4394       {
4395          //fifoFile.CloseInput();
4396          //delete fifoFile;
4397          app.Lock();
4398          ide.outputView.debugBox.Log("\n");
4399          app.Unlock();
4400       }
4401       /*
4402       if(FileExists(progFifoPath)) //fileCreated)
4403       {
4404          DeleteFile(progFifoPath);
4405          progFifoPath[0] = '\0';
4406       }
4407       */
4408       return 0;
4409    }
4410 }
4411 #endif
4412
4413 class Argument : struct
4414 {
4415    Argument prev, next;
4416    char * name;
4417    property char * name { set { delete name; if(value) name = CopyString(value); } }
4418    char * val;
4419    property char * val { set { delete val; if(value) val = CopyString(value); } }
4420
4421    void Free()
4422    {
4423       delete name;
4424       delete val;
4425    }
4426
4427    ~Argument()
4428    {
4429       Free();
4430    }
4431 }
4432
4433 class Frame : struct
4434 {
4435    Frame prev, next;
4436    int level;
4437    char * addr;
4438    property char * addr { set { delete addr; if(value) addr = CopyString(value); } }
4439    char * func;
4440    property char * func { set { delete func; if(value) func = CopyString(value); } }
4441    int argsCount;
4442    OldList args;
4443    char * from;
4444    property char * from { set { delete from; if(value) from = CopyUnescapedUnixPath(value); } }
4445    char * file;
4446    property char * file { set { delete file; if(value) file = CopyUnescapedUnixPath(value); } }
4447    char * absoluteFile;
4448    property char * absoluteFile { set { delete absoluteFile; if(value) absoluteFile = CopyUnescapedUnixPath(value); } }
4449    int line;
4450
4451    void Free()
4452    {
4453       delete addr;
4454       delete func;
4455       delete from;
4456       delete file;
4457       delete absoluteFile;
4458       args.Free(Argument::Free);
4459    }
4460
4461    ~Frame()
4462    {
4463       Free();
4464    }
4465 }
4466
4467 class GdbDataStop : struct
4468 {
4469    DebuggerReason reason;
4470    int threadid;
4471    union
4472    {
4473       struct
4474       {
4475          int bkptno;
4476       };
4477       struct
4478       {
4479          char * name;
4480          char * meaning;
4481       };
4482       struct
4483       {
4484          char * gdbResultVar;
4485          char * returnValue;
4486       };
4487    };
4488    Frame frame { };
4489
4490    void Free()
4491    {
4492       if(reason)
4493       {
4494          if(reason == signalReceived)
4495          {
4496             delete name;
4497             delete meaning;
4498          }
4499          else if(reason == functionFinished)
4500          {
4501             delete gdbResultVar;
4502             delete returnValue;
4503          }
4504       }
4505       if(frame) frame.Free();
4506    }
4507
4508    ~GdbDataStop()
4509    {
4510       Free();
4511    }
4512 }
4513
4514 class GdbDataBreakpoint : struct
4515 {
4516    int id;
4517    char * number;
4518    property char * number { set { delete number; if(value) number = CopyString(value); } }
4519    char * type;
4520    property char * type { set { delete type; if(value) type = CopyString(value); } }
4521    char * disp;
4522    property char * disp { set { delete disp; if(value) disp = CopyString(value); } }
4523    bool enabled;
4524    char * addr;
4525    property char * addr { set { delete addr; if(value) addr = CopyString(value); } }
4526    char * func;
4527    property char * func { set { delete func; if(value) func = CopyString(value); } }
4528    char * file;
4529    property char * file { set { delete file; if(value) file = CopyUnescapedUnixPath(value); } }
4530    char * fullname;
4531    property char * fullname { set { delete fullname; if(value) fullname = CopyUnescapedUnixPath(value); } }
4532    int line;
4533    char * at;
4534    property char * at { set { delete at; if(value) at = CopyString(value); } }
4535    int times;
4536
4537    Array<GdbDataBreakpoint> multipleBPs;
4538
4539    void Print()
4540    {
4541    _dpl(0, "");
4542       PrintLn("{", "#", number, " T", type, " D", disp, " E", enabled, " H", times, " (", func, ") (", file, ":", line, ") (", fullname, ") (", addr, ") (", at, ")", "}");
4543    }
4544
4545    void Free()
4546    {
4547       delete type;
4548       delete disp;
4549       delete addr;
4550       delete func;
4551       delete file;
4552       delete at;
4553       if(multipleBPs) multipleBPs.Free();
4554       delete multipleBPs;
4555       delete number;
4556       delete fullname;
4557    }
4558
4559    ~GdbDataBreakpoint()
4560    {
4561       Free();
4562    }
4563 }
4564
4565 class Breakpoint : struct
4566 {
4567    class_no_expansion;
4568
4569    char * function;
4570    property char * function { set { delete function; if(value) function = CopyString(value); } }
4571    char * relativeFilePath;
4572    property char * relativeFilePath { set { delete relativeFilePath; if(value) relativeFilePath = CopyString(value); } }
4573    char * absoluteFilePath;
4574    property char * absoluteFilePath { set { delete absoluteFilePath; if(value) absoluteFilePath = CopyString(value); } }
4575    char * location;
4576    property char * location { set { delete location; if(value) location = CopyString(value); } }
4577    int line;
4578    bool enabled;
4579    int hits;
4580    int breaks;
4581    int ignore;
4582    int level;
4583    Watch condition;
4584    bool inserted;
4585    BreakpointType type;
4586    DataRow row;
4587    GdbDataBreakpoint bp;
4588    Project project;
4589    char * address;
4590    property char * address { set { delete address; if(value) address = CopyString(value); } }
4591
4592    void ParseLocation()
4593    {
4594       char * prjName = null;
4595       char * filePath = null;
4596       char * file;
4597       char * line;
4598       char fullPath[MAX_LOCATION];
4599       if(location[0] == '(' && location[1] && (file = strchr(location+2, ')')) && file[1])
4600       {
4601          prjName = new char[file-location];
4602          strncpy(prjName, location+1, file-location-1);
4603          prjName[file-location-1] = '\0';
4604          file++;
4605       }
4606       else
4607          file = location;
4608       if((line = strchr(file+1, ':')))
4609       {
4610          filePath = new char[strlen(file)+1];
4611          strncpy(filePath, file, line-file);
4612          filePath[line-file] = '\0';
4613          line++;
4614       }
4615       else
4616          filePath = CopyString(file);
4617       property::relativeFilePath = filePath;
4618       if(prjName)
4619       {
4620          for(prj : ide.workspace.projects)
4621          {
4622             if(!strcmp(prjName, prj.name))
4623             {
4624                if(ProjectGetAbsoluteFromRelativePath(prj, filePath, fullPath))
4625                {
4626                   property::absoluteFilePath = fullPath;
4627                   project = prj;
4628                   break;
4629                }
4630             }
4631          }
4632          if(line[0])
4633             this.line = atoi(line);
4634       }
4635       else
4636       {
4637          Project prj = ide.project;
4638          if(ProjectGetAbsoluteFromRelativePath(prj, filePath, fullPath))
4639          {
4640             property::absoluteFilePath = fullPath;
4641             project = prj;
4642          }
4643       }
4644       if(!absoluteFilePath)
4645          property::absoluteFilePath = "";
4646       delete prjName;
4647       delete filePath;
4648    }
4649
4650    char * CopyLocationString(bool removePath)
4651    {
4652       char * location;
4653       char * file = relativeFilePath ? relativeFilePath : absoluteFilePath;
4654       bool removingPath = removePath && file;
4655       if(removingPath)
4656       {
4657          char * fileName = new char[MAX_FILENAME];
4658          GetLastDirectory(file, fileName);
4659          file = fileName;
4660       }
4661       if(function)
4662       {
4663          if(file)
4664             location = PrintString(file, ":", function);
4665          else
4666             location = CopyString(function);
4667       }
4668       else
4669          location = PrintString(file, ":", line);
4670       if(removingPath)
4671          delete file;
4672       return location;
4673    }
4674
4675    char * CopyUserLocationString()
4676    {
4677       char * location;
4678       char * loc = CopyLocationString(false);
4679       Project prj = null;
4680       if(absoluteFilePath)
4681       {
4682          for(p : ide.workspace.projects; p != ide.workspace.projects.firstIterator.data)
4683          {
4684             if(p.topNode.FindByFullPath(absoluteFilePath, false))
4685             {
4686                prj = p;
4687                break;
4688             }
4689          }
4690       }
4691       if(prj)
4692       {
4693          location = PrintString("(", prj.name, ")", loc);
4694          delete loc;
4695       }
4696       else
4697          location = loc;
4698       return location;
4699    }
4700
4701    void Save(File f)
4702    {
4703       if(relativeFilePath && relativeFilePath[0])
4704       {
4705          char * location = CopyUserLocationString();
4706          f.Printf("    * %d,%d,%d,%d,%s\n", enabled ? 1 : 0, ignore, level, line, location);
4707          delete location;
4708          if(condition)
4709             f.Printf("       ~ %s\n", condition.expression);
4710       }
4711    }
4712
4713    void Free()
4714    {
4715       Reset();
4716       delete function;
4717       delete relativeFilePath;
4718       delete absoluteFilePath;
4719       delete location;
4720    }
4721
4722    void Reset()
4723    {
4724       inserted = false;
4725       delete address;
4726       if(bp)
4727          bp.Free();
4728       delete bp;
4729    }
4730
4731    ~Breakpoint()
4732    {
4733       Free();
4734    }
4735
4736 }
4737
4738 class Watch : struct
4739 {
4740    class_no_expansion;
4741
4742    Type type;
4743    char * expression;
4744    char * value;
4745    DataRow row;
4746
4747    void Save(File f)
4748    {
4749       f.Printf("    ~ %s\n", expression);
4750    }
4751
4752    void Free()
4753    {
4754       delete expression;
4755       delete value;
4756       FreeType(type);
4757       type = null;
4758    }
4759
4760    void Reset()
4761    {
4762       delete value;
4763       FreeType(type);
4764       type = null;
4765    }
4766
4767    ~Watch()
4768    {
4769       Free();
4770    }
4771 }
4772
4773 class DebugListItem : struct
4774 {
4775    char * name;
4776    char * value;
4777 }
4778
4779 struct DebugEvaluationData
4780 {
4781    bool active;
4782    char * result;
4783    int bytes;
4784    uint64 nextBlockAddress;
4785
4786    DebuggerEvaluationError error;
4787 };
4788
4789 class CodeLocation : struct
4790 {
4791    char * file;
4792    char * absoluteFile;
4793    int line;
4794
4795    CodeLocation ::ParseCodeLocation(char * location)
4796    {
4797       if(location)
4798       {
4799          char * colon = null;
4800          char * temp;
4801          char loc[MAX_LOCATION];
4802          strcpy(loc, location);
4803          for(temp = loc; temp = strstr(temp, ":"); temp++)
4804             colon = temp;
4805          if(colon)
4806          {
4807             colon[0] = '\0';
4808             colon++;
4809             if(colon)
4810             {
4811                int line = atoi(colon);
4812                if(line)
4813                {
4814                   CodeLocation codloc { line = line };
4815                   codloc.file = CopyString(loc);
4816                   codloc.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(loc);
4817                   return codloc;
4818                }
4819             }
4820          }
4821       }
4822       return null;
4823    }
4824
4825    void Free()
4826    {
4827       delete file;
4828       delete absoluteFile;
4829    }
4830
4831    ~CodeLocation()
4832    {
4833       Free();
4834    }
4835 }
4836
4837 void GDBFallBack(Expression exp, String expString)
4838 {
4839    char * result;
4840    ExpressionType evalError = dummyExp;
4841    result = Debugger::EvaluateExpression(expString, &evalError);
4842    if(result)
4843    {
4844       exp.constant = result;
4845       exp.type = constantExp;
4846    }
4847 }
4848
4849 static Project WorkspaceGetFileOwner(char * absolutePath)
4850 {
4851    Project owner = null;
4852    for(prj : ide.workspace.projects)
4853    {
4854       if(prj.topNode.FindByFullPath(absolutePath, false))
4855       {
4856          owner = prj;
4857          break;
4858       }
4859    }
4860    if(!owner)
4861       WorkspaceGetObjectFileNode(absolutePath, &owner);
4862    return owner;
4863 }
4864
4865 static ProjectNode WorkspaceGetObjectFileNode(char * filePath, Project * project)
4866 {
4867    ProjectNode node = null;
4868    char ext[MAX_EXTENSION];
4869    GetExtension(filePath, ext);
4870    if(ext[0])
4871    {
4872       IntermediateFileType type = IntermediateFileType::FromExtension(ext);
4873       if(type)
4874       {
4875          char fileName[MAX_FILENAME];
4876          GetLastDirectory(filePath, fileName);
4877          if(fileName[0])
4878          {
4879             DotMain dotMain = DotMain::FromFileName(fileName);
4880             for(prj : ide.workspace.projects)
4881             {
4882                if((node = prj.FindNodeByObjectFileName(fileName, type, dotMain, null)))
4883                {
4884                   if(project)
4885                      *project = prj;
4886                   break;
4887                }
4888             }
4889          }
4890       }
4891    }
4892    return node;
4893 }
4894
4895 static ProjectNode ProjectGetObjectFileNode(Project project, char * filePath)
4896 {
4897    ProjectNode node = null;
4898    char ext[MAX_EXTENSION];
4899    GetExtension(filePath, ext);
4900    if(ext[0])
4901    {
4902       IntermediateFileType type = IntermediateFileType::FromExtension(ext);
4903       if(type)
4904       {
4905          char fileName[MAX_FILENAME];
4906          GetLastDirectory(filePath, fileName);
4907          if(fileName[0])
4908          {
4909             DotMain dotMain = DotMain::FromFileName(fileName);
4910             node = project.FindNodeByObjectFileName(fileName, type, dotMain, null);
4911          }
4912       }
4913    }
4914    return node;
4915 }
4916
4917 static void WorkspaceGetRelativePath(char * absolutePath, char * relativePath, Project * owner)
4918 {
4919    Project prj = WorkspaceGetFileOwner(absolutePath);
4920    if(owner)
4921       *owner = prj;
4922    if(!prj)
4923       prj = ide.workspace.projects.firstIterator.data;
4924    if(prj)
4925    {
4926       MakePathRelative(absolutePath, prj.topNode.path, relativePath);
4927       MakeSlashPath(relativePath);
4928    }
4929    else
4930       relativePath[0] = '\0';
4931 }
4932
4933 static bool ProjectGetAbsoluteFromRelativePath(Project project, char * relativePath, char * absolutePath)
4934 {
4935    ProjectNode node = project.topNode.FindWithPath(relativePath, false);
4936    if(!node)
4937       node = ProjectGetObjectFileNode(project, relativePath);
4938    if(node)
4939    {
4940       strcpy(absolutePath, node.project.topNode.path);
4941       PathCat(absolutePath, relativePath);
4942       MakeSlashPath(absolutePath);
4943    }
4944    return node != null;
4945 }