#ifdef ECERE_STATIC public import static "ecere" public import static "ec" #else public import "ecere" public import "ec" #endif import "ide" import "process" import "debugFindCtx" import "debugTools" #ifdef _DEBUG #define GDB_DEBUG_CONSOLE #define _DEBUG_INST #endif extern char * strrchr(const char * s, int c); #define uint _uint #define strlen _strlen #include #include #ifdef __APPLE__ #define __unix__ #endif #if defined(__unix__) #include #include // Required on Apple... #endif #undef uint #undef strlen char * PrintNow() { int c; char * s[6]; char * time; DateTime now; now.GetLocalTime(); for(c=0; c<6; c++) s[c] = new char[8]; sprintf(s[0], "%04d", now.year); sprintf(s[1], "%02d", now.month+1); sprintf(s[2], "%02d", now.day); sprintf(s[3], "%02d", now.hour); sprintf(s[4], "%02d", now.minute); sprintf(s[5], "%02d", now.second); time = PrintString("*", s[0], s[1], s[2], "-", s[3], s[4], s[5], "*"); for(c=0; c<6; c++) delete s[c]; return time; } // use =0 to disable printing of specific channels #ifdef _DEBUG_INST static enum dplchan { none, gdbProtoIgnored=0/*1*/, gdbProtoUnknown=2, gdbOutput=0/*3*/, gdbCommand=0/*4*/, debuggerCall=0/*5*/, debuggerProblem=6, debuggerUserAction=7,debuggerState=8, debuggerBreakpoints=9, debuggerWatches=0/*10*/, debuggerTemp=0 }; #else static enum dplchan { none, gdbProtoIgnored=0, gdbProtoUnknown=0, gdbOutput=0, gdbCommand=0, debuggerCall=0, debuggerProblem=0, debuggerUserAction=0,debuggerState=0, debuggerBreakpoints=0, debuggerWatches=0, debuggerTemp=0 }; #endif static char * _dpct[] = { null, "GDB Protocol Ignored", "GDB Protocol ***Unknown***", "GDB Output", "GDB Command", ""/*Debugger Call*/, "Debugger ***Problem***", "Debugger::ChangeUserAction", "Debugger::ChangeState", "Breakpoints", "Watches", "-----> Temporary Message", null }; // TODO if(strlen(item.value) < MAX_F_STRING) // Debug Print Line #ifdef _DEBUG_INST #define _dpl2(...) __dpl2(__FILE__, __LINE__, ##__VA_ARGS__) #else #define _dpl2(...) #endif static void __dpl2(char * file, int line, char ** channels, int channel, int indent, typed_object object, ...) { bool chan = channel && channels && channels[channel]; if(chan || !channels) { char string[MAX_F_STRING]; int len; char * time = PrintNow(); va_list args; //ide.outputView.debugBox.Logf(); Logf("%s %s:% 5d: %s%s", time, file, line, chan ? channels[channel] : "", chan && channels[channel][0] ? ": " : ""); va_start(args, object); len = PrintStdArgsToBuffer(string, sizeof(string), object, args); Log(string); va_end(args); Log("\n"); delete time; } } #define _dpl(...) __dpl(__FILE__, __LINE__, ##__VA_ARGS__) static void __dpl(char * file, int line, int indent, char * format, ...) { va_list args; char string[MAX_F_STRING]; int c; char * time = PrintNow(); //static File f = null; va_start(args, format); vsnprintf(string, sizeof(string), format, args); string[sizeof(string)-1] = 0; /*if(!f) { char * time = PrintNow(); char * logName; logName = PrintString(time, ".log"); delete time; f = FileOpen(logName, write); delete logName; }*/ /*f.Printf("%s %s:% 5d: ", time, file, line); for(c = 0; c 1 && *string == '[' && string[length - 1] == ']') { *string = '\0'; string[length - 1] = '\0'; return ++string; } else return string; } static char * StripCurlies(char * string) { int length = strlen(string); if(length > 1 && *string == '{' && string[length - 1] == '}') { *string = '\0'; string[length - 1] = '\0'; return ++string; } else return string; } static int StringGetInt(char * string, int start) { char number[8]; int i, len = strlen(string); number[0] = '\0'; for(i = start; i < len && i < start + 8; i++) { 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') strncat(number, &string[i], 1); else break; } return atoi(number); } static int TokenizeList(char * string, const char seperator, Array tokens) { uint level = 0; bool quoted = false, escaped = false; char * start = string, ch; for(; (ch = *string); string++) { if(!start) start = string; if(quoted) { if(escaped || ch != '\"') escaped = !escaped && ch == '\\'; else quoted = false; } else if(ch == '\"') quoted = true; else if(ch == '{' || ch == '[' || ch == '(' || ch == '<') level++; else if(ch == '}' || ch == ']' || ch == ')' || ch == '>') level--; else if(ch == seperator && !level) { tokens.Add(start); *string = '\0'; start = null; } } if(start) { //tokens[count] = start; //tokens[count++] = start; tokens.Add(start); *string = '\0'; } return tokens.count; } static bool TokenizeListItem(char * string, DebugListItem item) { char * equal = strstr(string, "="); if(equal) { item.name = string; *equal = '\0'; equal++; item.value = equal; equal = null; return true; } else return false; } static bool CheckCommandAvailable(const char * command) { bool available = false; int c, count; char * name = new char[MAX_FILENAME]; char * pathVar = new char[maxPathLen]; char * paths[128]; GetEnvironment("PATH", pathVar, maxPathLen); count = TokenizeWith(pathVar, sizeof(paths) / sizeof(char *), paths, pathListSep, false); strcpy(name, command); #ifdef __WIN32__ { int e; const char * extensions[] = { "exe", "com", "bat", null }; for(e=0; extensions[e]; e++) { ChangeExtension(name, extensions[e], name); #endif for(c=0; c sysBPs { }; Breakpoint bpRunToCursor; OldList stackFrames; CompilerConfig currentCompiler; ProjectConfig prjConfig; int bitDepth; CodeEditor codeEditor; ValgrindLogThread vgLogThread { debugger = this }; ValgrindTargetThread vgTargetThread { debugger = this }; GdbThread gdbThread { debugger = this }; Timer gdbTimer { delay = 0.0, userData = this; bool DelayExpired() { bool monitor = false; DebuggerEvent curEvent = event; GdbDataStop stopItem = this.stopItem; Breakpoint bpUser = null; Breakpoint bpInternal = null; if(!gdbReady) return false; event = none; if(this.stopItem) { this.stopItem = null; #ifdef _DEBUG_INST { char * s; DynamicString bpReport { }; for(bp : sysBPs; bp.inserted) { bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")"); delete s; } if(bpRunToCursor && bpRunToCursor.inserted) { Breakpoint bp = bpRunToCursor; bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")"); delete s; } for(bp : ide.workspace.breakpoints; bp.inserted) { bpReport.concatx(",", bp.type, "(", s=bp.CopyLocationString(false), ")"); delete s; } s = bpReport; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "gdbTimer::DelayExpired: ", s+1); if(stopItem.bkptno) { bool isInternal; Breakpoint bp = GetBreakpointById(stopItem.bkptno, &isInternal); if(bp) _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "gdb stopped by a breakpoint: ", bp.type, "(", s=bp.CopyLocationString(false), ")"); delete s; } } #endif } #ifdef _DEBUG_INST else { if(curEvent && curEvent != exit) { _dpl(0, "No stop item"); } } #endif switch(breakType) { case restart: breakType = none; Restart(currentCompiler, prjConfig, bitDepth, usingValgrind); break; case stop: breakType = none; Stop(); break; case selectFrame: { breakType = none; GdbCommand(false, "-stack-select-frame %d", activeFrameLevel); for(activeFrame = stackFrames.first; activeFrame; activeFrame = activeFrame.next) if(activeFrame.level == activeFrameLevel) break; break; } //case bpValidation: // breakType = none; // GdbCommand(false, "-break-info %s", bpItem.number); // break; } if(curEvent == none) return false; switch(curEvent) { case hit: { bool isInternal; Breakpoint bp = stopItem ? GetBreakpointById(stopItem.bkptno, &isInternal) : null; if(bp && bp.inserted && bp.bp.addr) { if(bp.type.isInternal) bpInternal = bp; else bpUser = bp; if(stopItem && stopItem.frame) { if(bpInternal && bpRunToCursor && bpRunToCursor.inserted && !strcmp(bpRunToCursor.bp.addr, bp.bp.addr)) bpUser = bpRunToCursor; else { for(item : (bpInternal ? ide.workspace.breakpoints : sysBPs); item.inserted) { if(item.bp && item.bp.addr && !strcmp(item.bp.addr, bp.bp.addr)) { if(bpInternal) bpUser = item; else bpInternal = item; break; } } } } else _dpl2(_dpct, dplchan::debuggerProblem, 0, "Invalid stopItem!"); if(bpUser && strcmp(stopItem.frame.addr, bpUser.bp.addr)) bpUser = null; } else _dpl2(_dpct, dplchan::debuggerProblem, 0, "Breakpoint bkptno(", stopItem.bkptno, ") invalid or not found!"); if((bpUser && !ignoreBreakpoints) || (bpInternal && userBreakOnInternalBreakpoint)) monitor = true; hitThread = stopItem.threadid; } break; case signal: signalThread = stopItem.threadid; case breakEvent: case stepEnd: case functionEnd: monitor = true; ignoreBreakpoints = false; break; case valgrindStartPause: GdbExecContinue(true); break; case exit: HideDebuggerViews(); break; } if(monitor || (bpUser && bpUser.type == runToCursor)) GdbGetStack(); if(monitor) { activeThread = stopItem.threadid; GdbCommand(false, "-thread-list-ids"); if(activeFrameLevel > 0) GdbCommand(false, "-stack-select-frame %d", activeFrameLevel); WatchesCodeEditorLinkInit(); EvaluateWatches(); } if(curEvent == signal) { char * s; signalOn = true; ide.outputView.debugBox.Logf($"Signal received: %s - %s\n", stopItem.name, stopItem.meaning); ide.outputView.debugBox.Logf(" %s:%d\n", (s = CopySystemPath(stopItem.frame.file)), stopItem.frame.line); ide.outputView.Show(); ide.callStackView.Show(); delete s; } else if(curEvent == breakEvent) { ide.threadsView.Show(); ide.callStackView.Show(); ide.callStackView.Activate(); } if(monitor && curEvent.canBeMonitored) { InternalSelectFrame(activeFrameLevel); GoToStackFrameLine(activeFrameLevel, true, false); ide.ShowCodeEditor(); ideMainFrame.Activate(); // TOFIX: ide.Activate() is not reliable (app inactive) ide.Update(null); } if(curEvent == hit) { if(BreakpointHit(stopItem, bpInternal, bpUser)) { if(bpUser && bpUser.type == runToCursor) { ignoreBreakpoints = false; UnsetBreakpoint(bpUser); delete bpRunToCursor; } } else GdbExecContinue(false); } if(stopItem) { stopItem.Free(); delete stopItem; } if(userBreakOnInternalBreakpoint) userBreakOnInternalBreakpoint = false; return false; } }; #ifdef GDB_DEBUG_CONSOLE char lastGdbOutput[GdbGetLineSize]; #endif #if defined(__unix__) ProgramThread progThread { }; #endif #ifdef _DEBUG_INST #define _ChangeUserAction(value) ChangeUserAction(__FILE__, __LINE__, value) void ChangeUserAction(char * file, int line, DebuggerUserAction value) { bool same = value == userAction; __dpl2(file, line, _dpct, dplchan::debuggerUserAction, 0, userAction, /*same ? " *** == *** " : */" -> ", value); userAction = value; } #else #define _ChangeUserAction(value) userAction = value #endif #ifdef _DEBUG_INST #define _ChangeState(value) ChangeState(__FILE__, __LINE__, value) void ChangeState(char * file, int line, DebuggerState value) #else #define _ChangeState(value) ChangeState(value) void ChangeState(DebuggerState value) #endif { bool same = value == state; #ifdef _DEBUG_INST __dpl2(file, line, _dpct, dplchan::debuggerState, 0, state, same ? " *** == *** " : " -> ", value); #endif state = value; if(!same && ide) ide.AdjustDebugMenus(); } void CleanUp() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::CleanUp"); stackFrames.Free(Frame::Free); delete targetDir; delete targetFile; ClearBreakDisplay(); // Clear Stuff up gdbProcessId = 0; waitingForPID = false; targeted = false; symbols = false; modules = false; sentKill = false; sentBreakInsert = false; ignoreBreakpoints = false; userBreakOnInternalBreakpoint = false; //runToCursorDebugStart = false; signalOn = false; activeFrameLevel = 0; activeThread = 0; hitThread = 0; signalThread = 0; frameCount = 0; targetDir = null; targetFile = null; _ChangeState(none); event = none; breakType = none; stopItem = null; bpItem = null; activeFrame = 0; bpRunToCursor = null; delete currentCompiler; prjConfig = null; codeEditor = null; /*GdbThread gdbThread Timer gdbTimer*/ } Debugger() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::constructor"); ideProcessId = Process_GetCurrentProcessId(); sysBPs.Add(Breakpoint { type = internalMain, function = "main", enabled = true, level = -1 }); #if defined(__WIN32__) sysBPs.Add(Breakpoint { type = internalWinMain, function = "WinMain", enabled = true, level = -1 }); #endif sysBPs.Add(Breakpoint { type = internalModulesLoaded, enabled = true, level = -1 }); sysBPs.Add(Breakpoint { type = internalModuleLoad, function = "InternalModuleLoadBreakpoint", enabled = true, level = -1 }); } ~Debugger() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::destructor"); sysBPs.Free(); Stop(); CleanUp(); } // PUBLIC MEMBERS property bool isActive { get { return state == running || state == stopped; } } property bool isPrepared { get { return state == loaded || state == running || state == stopped; } } void Resume() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Resume"); _ChangeUserAction(resume); GdbExecContinue(true); } void Break() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Break"); _ChangeUserAction(_break); if(state == running) { if(targetProcessId) GdbDebugBreak(false); } } void Stop() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Stop"); _ChangeUserAction(stop); switch(state) { case running: if(targetProcessId) { breakType = stop; GdbDebugBreak(false); } break; case stopped: GdbAbortExec(); HideDebuggerViews(); GdbExit(); break; case loaded: GdbExit(); break; } } void Restart(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Restart"); _ChangeUserAction(restart); if(StartSession(compiler, config, bitDepth, useValgrind, true, false, false/*, false*/) == loaded) GdbExecRun(); } bool GoToCodeLine(char * location) { CodeLocation codloc; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GoToCodeLine(", location, ")"); codloc = CodeLocation::ParseCodeLocation(location); if(codloc) { CodeEditor editor = (CodeEditor)ide.OpenFile(codloc.absoluteFile, normal, true, null, no, normal, false); if(editor) { EditBox editBox = editor.editBox; editBox.GoToLineNum(codloc.line - 1); editBox.GoToPosition(editBox.line, codloc.line - 1, 0); return true; } } return false; } bool GoToStackFrameLine(int stackLevel, bool askForLocation, bool fromCallStack) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GoToStackFrameLine(", stackLevel, ", ", askForLocation, ")"); if(ide) { char filePath[MAX_LOCATION]; char sourceDir[MAX_LOCATION]; Frame frame; CodeEditor editor = null; if(stackLevel == -1) // this (the two lines) is part of that fix that I would not put in for some time return false; for(frame = stackFrames.first; frame; frame = frame.next) if(frame.level == stackLevel) break; if(frame) { if(!fromCallStack) ide.callStackView.Show(); if(frame.absoluteFile) editor = (CodeEditor)ide.OpenFile(frame.absoluteFile, normal, true, null, no, normal, false); if(!editor && frame.file) frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file); if(!frame.absoluteFile && askForLocation && frame.file) { char * s; char title[MAX_LOCATION]; snprintf(title, sizeof(title), $"Provide source file location for %s", (s = CopySystemPath(frame.file))); title[sizeof(title)-1] = 0; delete s; if(SourceDirDialog(title, ide.workspace.projectDir, frame.file, sourceDir)) { AddSourceDir(sourceDir); frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file); } } if(!editor && frame.absoluteFile) editor = (CodeEditor)ide.OpenFile(frame.absoluteFile, normal, true, null, no, normal, false); if(editor) ide.RepositionWindows(false); ide.Update(null); if(editor && frame.line) { EditBox editBox = editor.editBox; editBox.GoToLineNum(frame.line - 1); editBox.GoToPosition(editBox.line, frame.line - 1, 0); return true; } } } return false; } void SelectThread(int thread) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SelectThread(", thread, ")"); _ChangeUserAction(selectThread); if(state == stopped) { if(thread != activeThread) { activeFrameLevel = -1; ide.callStackView.Clear(); GdbCommand(false, "-thread-select %d", thread); GdbGetStack(); InternalSelectFrame(activeFrameLevel); GoToStackFrameLine(activeFrameLevel, true, false); WatchesCodeEditorLinkRelease(); WatchesCodeEditorLinkInit(); EvaluateWatches(); ide.Update(null); } ide.callStackView.Show(); } } void SelectFrame(int frame) { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SelectFrame(", frame, ")"); _ChangeUserAction(selectFrame); InternalSelectFrame(frame); } void InternalSelectFrame(int frame) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::InternalSelectFrame(", frame, ")"); if(state == stopped) { if(frame != activeFrameLevel || !codeEditor || !codeEditor.visible) { activeFrameLevel = frame; // there is no active frame number in the gdb reply GdbCommand(false, "-stack-select-frame %d", activeFrameLevel); for(activeFrame = stackFrames.first; activeFrame; activeFrame = activeFrame.next) if(activeFrame.level == activeFrameLevel) break; WatchesCodeEditorLinkRelease(); WatchesCodeEditorLinkInit(); EvaluateWatches(); ide.Update(null); } } } void HandleExit(char * reason, char * code) { bool returnedExitCode = false; char verboseExitCode[128]; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::HandleExit(", reason, ", ", code, ")"); _ChangeState(loaded); // this state change seems to be superfluous, might be in case of gdb crash targetProcessId = 0; if(code) { snprintf(verboseExitCode, sizeof(verboseExitCode), $" with exit code %s", code); verboseExitCode[sizeof(verboseExitCode)-1] = 0; } else verboseExitCode[0] = '\0'; event = exit; // ClearBreakDisplay(); if(ide.workspace) { for(wh : ide.workspace.watches) { if(wh.type) FreeType(wh.type); wh.type = null; delete wh.value; ide.watchesView.UpdateWatch(wh); } } #if defined(__unix__) if(!usingValgrind) { progThread.terminate = true; if(fifoFile) { fifoFile.CloseInput(); app.Unlock(); progThread.Wait(); app.Lock(); delete fifoFile; } } #endif { char program[MAX_LOCATION]; GetSystemPathBuffer(program, targetFile); if(!reason) ide.outputView.debugBox.Logf($"The program %s has exited%s.\n", program, verboseExitCode); else if(!strcmp(reason, "exited-normally")) ide.outputView.debugBox.Logf($"The program %s has exited normally%s.\n", program, verboseExitCode); else if(!strcmp(reason, "exited")) ide.outputView.debugBox.Logf($"The program %s has exited%s.\n", program, verboseExitCode); else if(!strcmp(reason, "exited-signalled")) ide.outputView.debugBox.Logf($"The program %s has exited with a signal%s.\n", program, verboseExitCode); else ide.outputView.debugBox.Logf($"The program %s has exited (gdb provided an unknown reason)%s.\n", program, verboseExitCode); } ide.Update(null); } DebuggerState StartSession(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, bool restart, bool userBreakOnInternalBreakpoint, bool ignoreBreakpoints/*, bool runToCursorDebugStart*/) { DebuggerState result = none; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StartSession(restart(", restart, "), userBreakOnInternalBreakpoint(", userBreakOnInternalBreakpoint, "), ignoreBreakpoints(", ignoreBreakpoints, ")"/*, runToCursorDebugStart(", runToCursorDebugStart, ")"*/); if(restart && state == running && targetProcessId) { breakType = DebuggerAction::restart; GdbDebugBreak(false); } else { if(restart && state == stopped) GdbAbortExec(); if(needReset && state == loaded) GdbExit(); // this reset is to get a clean state with all the breakpoints until a better state can be maintained on program exit result = state; if(result == none || result == terminated) { ide.outputView.ShowClearSelectTab(debug); ide.outputView.debugBox.Logf($"Starting debug mode\n"); for(bp : sysBPs) { bp.hits = 0; bp.breaks = 0; } for(bp : ide.workspace.breakpoints) { bp.hits = 0; bp.breaks = 0; } //this.runToCursorDebugStart = runToCursorDebugStart; if(GdbInit(compiler, config, bitDepth, useValgrind)) result = state; else result = error; } this.ignoreBreakpoints = ignoreBreakpoints; this.userBreakOnInternalBreakpoint = userBreakOnInternalBreakpoint; if(result == loaded || result == stopped) GdbBreakpointsDelete(false, (userAction == stepOver || userAction == stepOut), ignoreBreakpoints); } return result; } void Start(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::Start()"); _ChangeUserAction(start); if(StartSession(compiler, config, bitDepth, useValgrind, true, false, false/*, false*/) == loaded) GdbExecRun(); } void StepInto(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepInto()"); _ChangeUserAction(stepInto); switch(StartSession(compiler, config, bitDepth, useValgrind, false, true, false/*, false*/)) { case loaded: GdbExecRun(); break; case stopped: GdbExecStep(); break; } } void StepOver(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, bool ignoreBreakpoints) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepOver()"); _ChangeUserAction(stepOver); switch(StartSession(compiler, config, bitDepth, useValgrind, false, true, ignoreBreakpoints/*, false*/)) { case loaded: GdbExecRun(); break; case stopped: GdbExecNext(); break; } } void StepOut(bool ignoreBreakpoints) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::StepOut()"); _ChangeUserAction(stepOut); if(state == stopped) { this.ignoreBreakpoints = ignoreBreakpoints; GdbBreakpointsDelete(true, true, ignoreBreakpoints); if(frameCount > 1) GdbExecFinish(); else GdbExecContinue(true); } } void RunToCursor(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind, char * absoluteFilePath, int lineNumber, bool ignoreBreakpoints, bool atSameLevel) { char relativeFilePath[MAX_LOCATION]; DebuggerState st = state; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::RunToCursor()"); _ChangeUserAction(runToCursor); //if(st == loaded) //{ // ide.outputView.ShowClearSelectTab(debug); // ide.outputView.debugBox.Logf($"Starting debug mode\n"); //} if(!ide.projectView.project.GetRelativePath(absoluteFilePath, relativeFilePath)) strcpy(relativeFilePath, absoluteFilePath); if(bpRunToCursor && bpRunToCursor.inserted && symbols) { UnsetBreakpoint(bpRunToCursor); delete bpRunToCursor; } bpRunToCursor = Breakpoint { }; bpRunToCursor.absoluteFilePath = absoluteFilePath; bpRunToCursor.relativeFilePath = relativeFilePath; bpRunToCursor.line = lineNumber; bpRunToCursor.type = runToCursor; bpRunToCursor.enabled = true; bpRunToCursor.level = atSameLevel ? frameCount - activeFrameLevel -1 : -1; switch(StartSession(compiler, config, bitDepth, useValgrind, false, false, ignoreBreakpoints/*, true*/)) { case loaded: GdbExecRun(); break; case stopped: GdbExecContinue(true); break; } } void GetCallStackCursorLine(bool * error, int * lineCursor, int * lineTopFrame) { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GetCallStackCursorLine()"); if(activeFrameLevel == -1) { *error = false; *lineCursor = 0; *lineTopFrame = 0; } else { *error = signalOn && activeThread == signalThread; *lineCursor = activeFrameLevel - ((frameCount > 192 && activeFrameLevel > 191) ? frameCount - 192 - 1 : 0) + 1; *lineTopFrame = activeFrameLevel ? 1 : 0; } } int GetMarginIconsLineNumbers(char * fileName, int lines[], bool enabled[], int max, bool * error, int * lineCursor, int * lineTopFrame) { char winFilePath[MAX_LOCATION]; char * absoluteFilePath = GetSlashPathBuffer(winFilePath, fileName); int count = 0; Iterator it { ide.workspace.breakpoints }; //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GetMarginIconsLineNumbers()"); while(it.Next() && count < max) { Breakpoint bp = it.data; if(bp.type == user) { if(bp.absoluteFilePath && bp.absoluteFilePath[0] && !fstrcmp(bp.absoluteFilePath, absoluteFilePath)) { lines[count] = bp.line; enabled[count] = bp.enabled; count++; } } } if(activeFrameLevel == -1) { *error = false; *lineCursor = 0; *lineTopFrame = 0; } else { *error = signalOn && activeThread == signalThread; if(activeFrame && activeFrame.absoluteFile && !fstrcmp(absoluteFilePath, activeFrame.absoluteFile)) *lineCursor = activeFrame.line; else *lineCursor = 0; if(activeFrame && stopItem && stopItem.frame && activeFrame.level == stopItem.frame.level) *lineTopFrame = 0; else if(stopItem && stopItem.frame && stopItem.frame.absoluteFile && !fstrcmp(absoluteFilePath, stopItem.frame.absoluteFile)) *lineTopFrame = stopItem.frame.line; else *lineTopFrame = 0; if(*lineTopFrame == *lineCursor && *lineTopFrame) *lineTopFrame = 0; } return count; } void ChangeWatch(DataRow row, char * expression) { Watch wh = (Watch)row.tag; //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ChangeWatch(", expression, ")"); if(wh) { delete wh.expression; if(expression) wh.expression = CopyString(expression); else { Iterator it { ide.workspace.watches }; if(it.Find(wh)) ide.workspace.watches.Delete(it.pointer); } } else if(expression) { wh = Watch { }; row.tag = (int64)wh; ide.workspace.watches.Add(wh); wh.row = row; wh.expression = CopyString(expression); } ide.workspace.Save(); //if(expression && state == stopped) if(expression) ResolveWatch(wh); } void MoveIcons(char * fileName, int lineNumber, int move, bool start) { char winFilePath[MAX_LOCATION]; char * absoluteFilePath = GetSlashPathBuffer(winFilePath, fileName); Link bpLink, next; //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::MoveIcons()"); for(bpLink = ide.workspace.breakpoints.first; bpLink; bpLink = next) { Breakpoint bp = (Breakpoint)bpLink.data; next = bpLink.next; if(bp.type == user && bp.absoluteFilePath && !fstrcmp(bp.absoluteFilePath, absoluteFilePath)) { if(bp.line > lineNumber || (bp.line == lineNumber && start)) { if(move < 0 && (bp.line < lineNumber - move)) ide.workspace.RemoveBreakpoint(bp); else { bp.line += move; ide.breakpointsView.UpdateBreakpoint(bp.row); ide.workspace.Save(); } } } } // moving code cursors is futile, on next step, stop, hit, cursors will be offset anyways } bool SourceDirDialog(char * title, char * startDir, char * test, char * sourceDir) { bool result; bool retry; String srcDir = null; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SourceDirDialog()"); debuggerFileDialog.text = title; debuggerFileDialog.currentDirectory = startDir; debuggerFileDialog.master = ide; while(debuggerFileDialog.Modal()) { strcpy(sourceDir, debuggerFileDialog.filePath); if(!fstrcmp(ide.workspace.projectDir, sourceDir) && MessageBox { type = yesNo, master = ide, contents = $"This is the project directory.\nWould you like to try again?", text = $"Invalid Source Directory" }.Modal() == no) return false; else { for(dir : ide.workspace.sourceDirs) { if(!fstrcmp(dir, sourceDir)) { srcDir = dir; break; } } if(srcDir && MessageBox { type = yesNo, master = ide, contents = $"This source directory is already specified.\nWould you like to try again?", text = $"Invalid Source Directory" }.Modal() == no) return false; else { if(test) { char file[MAX_LOCATION]; strcpy(file, sourceDir); PathCat(file, test); result = FileExists(file); if(!result && MessageBox { type = yesNo, master = ide, contents = $"Unable to locate source file.\nWould you like to try again?", text = $"Invalid Source Directory" }.Modal() == no) return false; } else result = true; if(result) return true; } } } return false; } void AddSourceDir(char * sourceDir) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::AddSourceDir(", sourceDir, ")"); ide.workspace.sourceDirs.Add(CopyString(sourceDir)); ide.workspace.Save(); if(targeted) { DebuggerState oldState = state; switch(state) { case running: if(targetProcessId) GdbDebugBreak(true); case stopped: case loaded: GdbCommand(false, "-environment-directory \"%s\"", sourceDir); break; } if(oldState == running) GdbExecContinue(false); } } void ToggleBreakpoint(char * fileName, int lineNumber, Project prj) { char winFilePath[MAX_LOCATION]; char * absoluteFilePath = GetSlashPathBuffer(winFilePath, fileName); char absolutePath[MAX_LOCATION]; char relativePath[MAX_LOCATION]; char sourceDir[MAX_LOCATION]; Breakpoint bp = null; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::ToggleBreakpoint(", fileName, ":", lineNumber, ")"); strcpy(absolutePath, absoluteFilePath); for(i : ide.workspace.breakpoints; i.type == user && i.absoluteFilePath && !fstrcmp(i.absoluteFilePath, absolutePath) && i.line == lineNumber) { bp = i; break; } if(bp) { if(bp.enabled) { ide.workspace.RemoveBreakpoint(bp); bp = null; } else bp.enabled = true; } else { // FIXED: This is how it should have been... Source locations are only for files not in project // if(IsPathInsideOf(absolutePath, ide.workspace.projectDir)) // MakePathRelative(absolutePath, ide.workspace.projectDir, relativePath); bool result = false; if(prj) result = prj.GetRelativePath(absolutePath, relativePath); else result = ide.projectView.project.GetRelativePath(absolutePath, relativePath); //if(ide.projectView.project.GetRelativePath(absolutePath, relativePath)); //else if(!result) { char title[MAX_LOCATION]; char directory[MAX_LOCATION]; StripLastDirectory(absolutePath, directory); snprintf(title, sizeof(title), $"Provide source files location directory for %s", absolutePath); title[sizeof(title)-1] = 0; while(true) { String srcDir = null; for(dir : ide.workspace.sourceDirs) { if(IsPathInsideOf(absolutePath, dir)) { MakePathRelative(absoluteFilePath, dir, relativePath); srcDir = dir; break; } } if(srcDir) break; if(SourceDirDialog(title, directory, null, sourceDir)) { if(IsPathInsideOf(absolutePath, sourceDir)) { AddSourceDir(sourceDir); MakePathRelative(absoluteFilePath, sourceDir, relativePath); break; } else if(MessageBox { type = yesNo, master = ide, contents = $"You must provide a valid source directory in order to place a breakpoint in this file.\nWould you like to try again?", text = $"Invalid Source Directory" }.Modal() == no) return; } else if(MessageBox { type = yesNo, master = ide, contents = $"You must provide a source directory in order to place a breakpoint in this file.\nWould you like to try again?", text = $"No Source Directory Provided" }.Modal() == no) return; } } ide.workspace.bpCount++; bp = { line = lineNumber, type = user, enabled = true, level = -1 }; ide.workspace.breakpoints.Add(bp); bp.absoluteFilePath = absolutePath; bp.relativeFilePath = relativePath; ide.breakpointsView.AddBreakpoint(bp); } if(bp && targeted) { DebuggerState oldState = state; switch(state) { case running: if(targetProcessId) GdbDebugBreak(true); case stopped: case loaded: SetBreakpoint(bp, false); break; } if(oldState == running) GdbExecContinue(false); } ide.workspace.Save(); } void UpdateRemovedBreakpoint(Breakpoint bp) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::UpdateRemovedBreakpoint()"); if(targeted && bp.inserted) { DebuggerState oldState = state; switch(state) { case running: if(targetProcessId) GdbDebugBreak(true); case stopped: case loaded: UnsetBreakpoint(bp); break; } if(oldState == running) GdbExecContinue(false); } } // PRIVATE MEMBERS void ParseFrame(Frame frame, char * string) { int i, j, k; Array frameTokens { minAllocSize = 50 }; Array argsTokens { minAllocSize = 50 }; Array argumentTokens { minAllocSize = 50 }; DebugListItem item { }; Argument arg; //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ParseFrame()"); TokenizeList(string, ',', frameTokens); for(i = 0; i < frameTokens.count; i++) { if(TokenizeListItem(frameTokens[i], item)) { StripQuotes(item.value, item.value); if(!strcmp(item.name, "level")) frame.level = atoi(item.value); else if(!strcmp(item.name, "addr")) frame.addr = item.value; else if(!strcmp(item.name, "func")) frame.func = item.value; else if(!strcmp(item.name, "args")) { if(!strcmp(item.value, "[]")) frame.argsCount = 0; else { item.value = StripBrackets(item.value); TokenizeList(item.value, ',', argsTokens); for(j = 0; j < argsTokens.count; j++) { argsTokens[j] = StripCurlies(argsTokens[j]); TokenizeList(argsTokens[j], ',', argumentTokens); for(k = 0; k < argumentTokens.count; k++) { arg = Argument { }; frame.args.Add(arg); if(TokenizeListItem(argumentTokens[k], item)) { if(!strcmp(item.name, "name")) { StripQuotes(item.value, item.value); arg.name = item.value; } else if(!strcmp(item.name, "value")) { StripQuotes(item.value, item.value); arg.val = item.value; } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "frame args item (", item.name, "=", item.value, ") is unheard of"); } else _dpl(0, "Bad frame args item"); } argumentTokens.RemoveAll(); } frame.argsCount = argsTokens.count; argsTokens.RemoveAll(); } } else if(!strcmp(item.name, "from")) frame.from = item.value; else if(!strcmp(item.name, "file")) frame.file = item.value; else if(!strcmp(item.name, "line")) frame.line = atoi(item.value); else if(!strcmp(item.name, "fullname")) frame.absoluteFile = item.value; /*{ // GDB 6.3 on OS X is giving "fullname" and "dir", all in absolute, but file name only in 'file' String path = ide.workspace.GetPathWorkspaceRelativeOrAbsolute(item.value); if(strcmp(frame.file, path)) { frame.file = path; frame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(frame.file); } delete path; }*/ else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "frame member (", item.name, "=", item.value, ") is unheard of"); } else _dpl(0, "Bad frame"); } delete frameTokens; delete argsTokens; delete argumentTokens; delete item; } Breakpoint GetBreakpointById(int id, bool * isInternal) { Breakpoint bp = null; //_dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::GetBreakpointById(", id, ")"); if(isInternal) *isInternal = false; if(id) { for(i : sysBPs; i.bp && i.bp.id == id) { if(isInternal) *isInternal = true; bp = i; break; } if(!bp && bpRunToCursor && bpRunToCursor.bp && bpRunToCursor.bp.id == id) bp = bpRunToCursor; if(!bp) { for(i : ide.workspace.breakpoints; i.bp && i.bp.id == id) { bp = i; break; } } } return bp; } GdbDataBreakpoint ParseBreakpoint(char * string, Array outTokens) { int i; GdbDataBreakpoint bp { }; DebugListItem item { }; Array bpTokens { minAllocSize = 16 }; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ParseBreakpoint()"); string = StripCurlies(string); TokenizeList(string, ',', bpTokens); for(i = 0; i < bpTokens.count; i++) { if(TokenizeListItem(bpTokens[i], item)) { StripQuotes(item.value, item.value); if(!strcmp(item.name, "number")) { if(!strchr(item.value, '.')) bp.id = atoi(item.value); bp.number = item.value; } else if(!strcmp(item.name, "type")) bp.type = item.value; else if(!strcmp(item.name, "disp")) bp.disp = item.value; else if(!strcmp(item.name, "enabled")) bp.enabled = (!strcmpi(item.value, "y")); else if(!strcmp(item.name, "addr")) { if(outTokens && !strcmp(item.value, "")) { int c = 1; Array bpArray = bp.multipleBPs = { }; while(outTokens.count > ++c) { GdbDataBreakpoint multBp = ParseBreakpoint(outTokens[c], null); bpArray.Add(multBp); } } else bp.addr = item.value; } else if(!strcmp(item.name, "func")) bp.func = item.value; else if(!strcmp(item.name, "file")) bp.file = item.value; else if(!strcmp(item.name, "fullname")) bp.fullname = item.value; else if(!strcmp(item.name, "line")) bp.line = atoi(item.value); else if(!strcmp(item.name, "at")) bp.at = item.value; else if(!strcmp(item.name, "times")) bp.times = atoi(item.value); else if(!strcmp(item.name, "original-location") || !strcmp(item.name, "thread-groups")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "breakpoint member (", item.name, "=", item.value, ") is ignored"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "breakpoint member (", item.name, "=", item.value, ") is unheard of"); } } return bp; } void ShowDebuggerViews() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ShowDebuggerViews()"); ide.outputView.Show(); ide.outputView.SelectTab(debug); ide.threadsView.Show(); ide.callStackView.Show(); ide.watchesView.Show(); ide.Update(null); } void HideDebuggerViews() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::HideDebuggerViews()"); ide.RepositionWindows(true); } void ::GdbCommand(bool focus, char * format, ...) { if(gdbHandle) { // TODO: Improve this limit static char string[MAX_F_STRING*4]; va_list args; va_start(args, format); vsnprintf(string, sizeof(string), format, args); string[sizeof(string)-1] = 0; va_end(args); gdbReady = false; ide.debugger.serialSemaphore.TryWait(); #ifdef GDB_DEBUG_CONSOLE _dpl2(_dpct, dplchan::gdbCommand, 0, string); #endif #ifdef GDB_DEBUG_OUTPUT ide.outputView.gdbBox.Logf("cmd: %s\n", string); #endif #ifdef GDB_DEBUG_GUI if(ide.gdbDialog) ide.gdbDialog.AddCommand(string); #endif strcat(string,"\n"); gdbHandle.Puts(string); if(focus) Process_ShowWindows(targetProcessId); app.Unlock(); ide.debugger.serialSemaphore.Wait(); app.Lock(); } } bool ValidateBreakpoint(Breakpoint bp) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ValidateBreakpoint()"); if(modules && bp.line && bp.bp) { if(bp.bp.line != bp.line) { if(!bp.bp.line) { #ifdef _DEBUG //here ide.outputView.debugBox.Logf("WOULD HAVE -- Invalid breakpoint disabled: %s:%d\n", bp.relativeFilePath, bp.line); #endif //UnsetBreakpoint(bp); //bp.enabled = false; return false; } else { //here ide.outputView.debugBox.Logf("Debugger Error: ValidateBreakpoint error\n"); bp.line = bp.bp.line; } } } return true; } void GdbBreakpointsInsert() { //_dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::GdbBreakpointsInsert()"); if(symbols) { if(userAction != stepOut && (userAction != stepOver || state == loaded)) { DirExpression objDir = ide.project.GetObjDir(currentCompiler, prjConfig, bitDepth); for(bp : sysBPs; !bp.inserted) { bool insert = false; if(bp.type == internalModulesLoaded) { char path[MAX_LOCATION]; char name[MAX_LOCATION]; char fixedModuleName[MAX_FILENAME]; char line[16384]; int lineNumber; bool moduleLoadBlock = false; File f; ReplaceSpaces(fixedModuleName, ide.project.moduleName); snprintf(name, sizeof(name),"%s.main.ec", fixedModuleName); name[sizeof(name)-1] = 0; strcpy(path, ide.workspace.projectDir); PathCatSlash(path, objDir.dir); PathCatSlash(path, name); f = FileOpen(path, read); if(f) { for(lineNumber = 1; !f.Eof(); lineNumber++) { if(f.GetLine(line, sizeof(line) - 1)) { bool moduleLoadLine; TrimLSpaces(line, line); moduleLoadLine = !strncmp(line, "eModule_Load", strlen("eModule_Load")); if(!moduleLoadBlock && moduleLoadLine) moduleLoadBlock = true; else if(moduleLoadBlock && !moduleLoadLine && strlen(line) > 0) break; } } if(!f.Eof()) { char relative[MAX_LOCATION]; bp.absoluteFilePath = path; MakePathRelative(path, ide.workspace.projectDir, relative); bp.relativeFilePath = relative; bp.line = lineNumber; insert = true; } delete f; } } else if(bp.type == internalModuleLoad) { if(modules) { for(prj : ide.workspace.projects) { if(!strcmp(prj.moduleName, "ecere")) { ProjectNode node = prj.topNode.Find("instance.c", false); if(node) { char path[MAX_LOCATION]; char relative[MAX_LOCATION]; node.GetFullFilePath(path); bp.absoluteFilePath = path; MakePathRelative(path, prj.topNode.path, relative); bp.relativeFilePath = relative; insert = true; break; } } } } } else insert = true; if(insert) SetBreakpoint(bp, false); } delete objDir; } if(bpRunToCursor && !bpRunToCursor.inserted) SetBreakpoint(bpRunToCursor, false); if(!ignoreBreakpoints) { for(bp : ide.workspace.breakpoints; !bp.inserted && bp.type == user) { if(bp.enabled) { if(!SetBreakpoint(bp, false)) SetBreakpoint(bp, true); } else { #ifdef _DEBUG if(bp.bp) _dpl(0, "problem"); #endif bp.bp = GdbDataBreakpoint { }; } } } } } void UnsetBreakpoint(Breakpoint bp) { char * s; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::UnsetBreakpoint(", s=bp.CopyLocationString(false), ") -- ", bp.type); delete s; if(symbols && bp.inserted) { GdbCommand(false, "-break-delete %s", bp.bp.number); bp.inserted = false; bp.bp = { }; } } bool SetBreakpoint(Breakpoint bp, bool removePath) { char * s; _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::SetBreakpoint(", s=bp.CopyLocationString(false), ", ", removePath ? "**** removePath(true) ****" : "", ") -- ", bp.type); delete s; breakpointError = false; if(symbols) { char * location = bp.CopyLocationString(removePath); sentBreakInsert = true; GdbCommand(false, "-break-insert %s", location); delete location; if(!breakpointError) { if(bpItem && bpItem.multipleBPs && bpItem.multipleBPs.count) { int count = 0; GdbDataBreakpoint first = null; for(n : bpItem.multipleBPs) { if(!fstrcmp(n.fullname, bp.absoluteFilePath)) { count++; if(!first) first = n; } else { if(n.enabled) { GdbCommand(false, "-break-disable %s", n.number); n.enabled = false; } else _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error breakpoint already disabled."); } } if(first) { bpItem.addr = first.addr; bpItem.func = first.func; bpItem.file = first.file; bpItem.fullname = first.fullname; bpItem.line = first.line; //bpItem.thread-groups = first.thread-groups; bpItem.multipleBPs.Free(); delete bpItem.multipleBPs; } else if(count == 0) _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error multiple breakpoints all disabled."); else _dpl2(_dpct, dplchan::debuggerProblem, 0, "Debugger::SetBreakpoint -- error multiple breakpoints in exact same file not supported."); } bp.bp = bpItem; bpItem = null; bp.inserted = (bp.bp && bp.bp.number && strcmp(bp.bp.number, "0")); if(bp.inserted) ValidateBreakpoint(bp); /*if(bp == bpRunToCursor) runToCursorDebugStart = false;*/ } } return !breakpointError; } void GdbBreakpointsDelete(bool deleteRunToCursor, bool deleteInternalBreakpoints, bool deleteUserBreakpoints) { _dpl2(_dpct, dplchan::debuggerBreakpoints, 0, "Debugger::GdbBreakpointsDelete(deleteRunToCursor(", deleteRunToCursor, "))"); if(symbols) { if(deleteInternalBreakpoints) { for(bp : sysBPs; bp.inserted) UnsetBreakpoint(bp); } if(deleteUserBreakpoints) { for(bp : ide.workspace.breakpoints; bp.inserted) UnsetBreakpoint(bp); } if(deleteRunToCursor && bpRunToCursor && bpRunToCursor.inserted) UnsetBreakpoint(bpRunToCursor); } } void GdbGetStack() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbGetStack()"); activeFrame = null; stackFrames.Free(Frame::Free); GdbCommand(false, "-stack-info-depth"); if(!frameCount) GdbCommand(false, "-stack-info-depth 192"); if(frameCount && frameCount <= 192) GdbCommand(false, "-stack-list-frames 0 %d", Min(frameCount-1, 191)); else { GdbCommand(false, "-stack-list-frames 0 %d", Min(frameCount-1, 95)); GdbCommand(false, "-stack-list-frames %d %d", Max(frameCount - 96, 96), frameCount - 1); } GdbCommand(false, ""); } bool GdbTargetSet() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbTargetSet()"); if(!targeted) { char escaped[MAX_LOCATION]; strescpy(escaped, targetFile); GdbCommand(false, "file \"%s\"", escaped); //GDB/MI Missing Implementation -symbol-file, -target-attach if(!symbols) return true; if(usingValgrind) { const char *vgdbCommand = "/usr/bin/vgdb"; // TODO: vgdb command config option //GdbCommand(false, "-target-select remote | %s --pid=%d", "vgdb", targetProcessId); printf("target remote | %s --pid=%d\n", vgdbCommand, targetProcessId); GdbCommand(false, "target remote | %s --pid=%d", vgdbCommand, targetProcessId); // TODO: vgdb command config option } /*for(prj : ide.workspace.projects; prj != ide.workspace.projects.firstIterator.data) GdbCommand(false, "-environment-directory \"%s\"", prj.topNode.path);*/ for(dir : ide.workspace.sourceDirs; dir && dir[0]) { bool interference = false; for(prj : ide.workspace.projects) { if(!fstrcmp(prj.topNode.path, dir)) { interference = true; break; } } if(!interference && dir[0]) GdbCommand(false, "-environment-directory \"%s\"", dir); } targeted = true; } return true; } /*void GdbTargetRelease() { if(targeted) { GdbBreakpointsDelete(true, true, true); GdbCommand(false, "file"); //GDB/MI Missing Implementation -target-detach targeted = false; symbols = true; } }*/ void GdbDebugBreak(bool internal) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbDebugBreak()"); if(targetProcessId) { if(internal) breakType = DebuggerAction::internal; if(ide) ide.Update(null); app.Unlock(); if(Process_Break(targetProcessId)) //GdbCommand(false, "-exec-interrupt"); serialSemaphore.Wait(); else { _ChangeState(loaded); targetProcessId = 0; } app.Lock(); } else ide.outputView.debugBox.Logf("Debugger Error: GdbDebugBreak with not target id should never happen\n"); } void GdbExecRun() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecRun()"); GdbTargetSet(); GdbExecCommon(); ShowDebuggerViews(); if(usingValgrind) GdbCommand(true, "-exec-continue"); else GdbCommand(true, "-exec-run"); } void GdbExecContinue(bool focus) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecContinue()"); GdbExecCommon(); GdbCommand(focus, "-exec-continue"); } void GdbExecNext() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecNext()"); GdbExecCommon(); GdbCommand(true, "-exec-next"); } void GdbExecStep() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecStep()"); GdbExecCommon(); GdbCommand(true, "-exec-step"); } void GdbExecFinish() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecFinish()"); GdbExecCommon(); GdbCommand(true, "-exec-finish"); } void GdbExecCommon() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExecCommon()"); GdbBreakpointsInsert(); } #ifdef GDB_DEBUG_GUI void SendGDBCommand(char * command) { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::SendGDBCommand()"); DebuggerState oldState = state; switch(state) { case running: if(targetProcessId) GdbDebugBreak(true); case stopped: case loaded: GdbCommand(false, command); break; } if(oldState == running) GdbExecContinue(false); } #endif void ClearBreakDisplay() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ClearBreakDisplay()"); activeThread = 0; activeFrameLevel = -1; hitThread = 0; signalThread = 0; signalOn = false; frameCount = 0; if(stopItem) stopItem.Free(); delete stopItem; event = none; activeFrame = null; stackFrames.Free(Frame::Free); WatchesCodeEditorLinkRelease(); ide.callStackView.Clear(); ide.threadsView.Clear(); ide.Update(null); } bool GdbAbortExec() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbAbortExec()"); sentKill = true; GdbCommand(false, "-interpreter-exec console \"kill\""); // should use -exec-abort -- GDB/MI implementation incomplete return true; } bool GdbInit(CompilerConfig compiler, ProjectConfig config, int bitDepth, bool useValgrind) { bool result = true; char oldDirectory[MAX_LOCATION]; char tempPath[MAX_LOCATION]; char command[MAX_F_STRING*4]; Project project = ide.project; DirExpression targetDirExp = project.GetTargetDir(compiler, config, bitDepth); PathBackup pathBackup { }; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbInit()"); if(currentCompiler != compiler) { delete currentCompiler; currentCompiler = compiler; incref currentCompiler; } prjConfig = config; this.bitDepth = bitDepth; usingValgrind = useValgrind; _ChangeState(loaded); sentKill = false; sentBreakInsert = false; breakpointError = false; ignoreBreakpoints = false; symbols = true; targeted = false; modules = false; needReset = false; ide.outputView.ShowClearSelectTab(debug); ide.outputView.debugBox.Logf($"Starting debug mode\n"); #ifdef GDB_DEBUG_OUTPUT ide.outputView.gdbBox.Logf("run: Starting GDB\n"); #endif strcpy(tempPath, ide.workspace.projectDir); PathCatSlash(tempPath, targetDirExp.dir); delete targetDir; targetDir = CopyString(tempPath); project.CatTargetFileName(tempPath, compiler, config); delete targetFile; targetFile = CopyString(tempPath); GetWorkingDir(oldDirectory, MAX_LOCATION); if(ide.workspace.debugDir && ide.workspace.debugDir[0]) { char temp[MAX_LOCATION]; strcpy(temp, ide.workspace.projectDir); PathCatSlash(temp, ide.workspace.debugDir); ChangeWorkingDir(temp); } else ChangeWorkingDir(ide.workspace.projectDir); ide.SetPath(true, compiler, config, bitDepth); // TODO: This pollutes the environment, but at least it works // It shouldn't really affect the IDE as the PATH gets restored and other variables set for testing will unlikely cause problems // What is the proper solution for this? DualPipeOpenEnv? // gdb set environment commands don't seem to take effect for(e : ide.workspace.environmentVars) { SetEnvironment(e.name, e.string); } if(usingValgrind) { char * clArgs = ide.workspace.commandLineArgs; const char *valgrindCommand = "valgrind"; // TODO: valgrind command config option //TODO: valgrind options ValgrindLeakCheck vgLeakCheck = ide.workspace.vgLeakCheck; int vgRedzoneSize = ide.workspace.vgRedzoneSize; bool vgTrackOrigins = ide.workspace.vgTrackOrigins; vgLogFile = CreateTemporaryFile(vgLogPath, "ecereidevglog"); if(vgLogFile) { incref vgLogFile; vgLogThread.Create(); } else { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't open temporary log file for Valgrind output\n"); result = false; } if(result && !CheckCommandAvailable(valgrindCommand)) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Command %s for Valgrind is not available.\n", valgrindCommand); result = false; } if(result) { char * vgRedzoneSizeFlag = vgRedzoneSize == -1 ? "" : PrintString(" --redzone-size=", vgRedzoneSize); sprintf(command, "%s --vgdb=yes --vgdb-error=0 --log-file=%s --leak-check=%s%s --track-origins=%s %s%s%s", valgrindCommand, vgLogPath, (char*)vgLeakCheck, vgRedzoneSizeFlag, vgTrackOrigins ? "yes" : "no", targetFile, clArgs ? " " : "", clArgs ? clArgs : ""); if(vgRedzoneSize != -1) delete vgRedzoneSizeFlag; vgTargetHandle = DualPipeOpen(PipeOpenMode { output = true, /*error = true, */input = true }, command); if(!vgTargetHandle) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't start Valgrind\n"); result = false; } } if(result) { incref vgTargetHandle; vgTargetThread.Create(); targetProcessId = vgTargetHandle.GetProcessID(); waitingForPID = false; if(!targetProcessId) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't get Valgrind process ID\n"); result = false; } } if(result) { app.Unlock(); serialSemaphore.Wait(); app.Lock(); } } if(result) { strcpy(command, (compiler.targetPlatform == win32 && bitDepth == 64) ? "x86_64-w64-mingw32-gdb" : (compiler.targetPlatform == win32 && bitDepth == 32) ? "i686-w64-mingw32-gdb" : "gdb"); if(!CheckCommandAvailable(command)) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Command %s for GDB is not available.\n", command); result = false; } else { strcat(command, " -n -silent --interpreter=mi2"); //-async //\"%s\" gdbTimer.Start(); gdbHandle = DualPipeOpen(PipeOpenMode { output = true, /*error = true, */input = true }, command); if(!gdbHandle) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't start GDB\n"); result = false; } } } if(result) { incref gdbHandle; gdbThread.Create(); gdbProcessId = gdbHandle.GetProcessID(); if(!gdbProcessId) { ide.outputView.debugBox.Logf($"Debugger Fatal Error: Couldn't get GDB process ID\n"); result = false; } } if(result) { app.Unlock(); serialSemaphore.Wait(); app.Lock(); GdbCommand(false, "-gdb-set verbose off"); //GdbCommand(false, "-gdb-set exec-done-display on"); GdbCommand(false, "-gdb-set step-mode off"); GdbCommand(false, "-gdb-set unwindonsignal on"); //GdbCommand(false, "-gdb-set shell on"); GdbCommand(false, "set print elements 992"); GdbCommand(false, "-gdb-set backtrace limit 100000"); if(!GdbTargetSet()) { //_ChangeState(terminated); result = false; } } if(result) { #if defined(__unix__) { CreateTemporaryDir(progFifoDir, "ecereide"); strcpy(progFifoPath, progFifoDir); PathCat(progFifoPath, "ideprogfifo"); if(!mkfifo(progFifoPath, 0600)) { //fileCreated = true; } else { //app.Lock(); ide.outputView.debugBox.Logf(createFIFOMsg, progFifoPath); //app.Unlock(); } } if(!usingValgrind) { progThread.terminate = false; progThread.Create(); } #endif #if defined(__WIN32__) GdbCommand(false, "-gdb-set new-console on"); #endif #if defined(__unix__) if(!usingValgrind) GdbCommand(false, "-inferior-tty-set %s", progFifoPath); #endif if(!usingValgrind) GdbCommand(false, "-gdb-set args %s", ide.workspace.commandLineArgs ? ide.workspace.commandLineArgs : ""); /* for(e : ide.workspace.environmentVars) { GdbCommand(false, "set environment %s=%s", e.name, e.string); } */ } ChangeWorkingDir(oldDirectory); delete pathBackup; if(!result) GdbExit(); delete targetDirExp; return result; } void GdbExit() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbExit()"); if(gdbHandle && gdbProcessId) { GdbCommand(false, "-gdb-exit"); if(gdbThread) { app.Unlock(); gdbThread.Wait(); app.Lock(); } if(gdbHandle) { gdbHandle.Wait(); delete gdbHandle; } } gdbTimer.Stop(); _ChangeState(terminated); // this state change seems to be superfluous, is it safety for something? prjConfig = null; needReset = false; if(ide.workspace) { for(bp : ide.workspace.breakpoints) { bp.inserted = false; delete bp.bp; } } for(bp : sysBPs) { bp.inserted = false; delete bp.bp; } if(bpRunToCursor) { bpRunToCursor.inserted = false; delete bpRunToCursor.bp; } ide.outputView.debugBox.Logf($"Debugging stopped\n"); ClearBreakDisplay(); ide.Update(null); #if defined(__unix__) if(!usingValgrind && FileExists(progFifoPath)) //fileCreated) { progThread.terminate = true; if(fifoFile) { fifoFile.CloseInput(); app.Unlock(); progThread.Wait(); app.Lock(); delete fifoFile; } DeleteFile(progFifoPath); progFifoPath[0] = '\0'; rmdir(progFifoDir); } #endif } void WatchesCodeEditorLinkInit() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::WatchesCodeEditorLinkInit()"); /* char tempPath[MAX_LOCATION]; char path[MAX_LOCATION]; //void MakeFilePathProjectRelative(char * path, char * relativePath) if(!ide.projectView.project.GetRelativePath(activeFrame.file, tempPath)) strcpy(tempPath, activeFrame.file); strcpy(path, ide.workspace.projectDir); PathCat(path, tempPath); codeEditor = (CodeEditor)ide.OpenFile(path, Normal, false, null, no, normal, false); if(!codeEditor) { for(srcDir : ide.workspace.sourceDirs) { strcpy(path, srcDir); PathCat(path, tempPath); codeEditor = (CodeEditor)ide.OpenFile(path, Normal, false, null, no, normal, false); if(codeEditor) break; } } */ /*if(activeFrame && !activeFrame.absoluteFile && activeFrame.file) activeFrame.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(activeFrame.file);*/ if(!activeFrame || !activeFrame.absoluteFile) codeEditor = null; else codeEditor = (CodeEditor)ide.OpenFile(activeFrame.absoluteFile, normal, false, null, no, normal, false); if(codeEditor) { codeEditor.inUseDebug = true; incref codeEditor; } //watchesInit = true; } void WatchesCodeEditorLinkRelease() { //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::WatchesCodeEditorLinkRelease()"); //if(watchesInit) { if(codeEditor) { codeEditor.inUseDebug = false; if(!codeEditor.visible) codeEditor.Destroy(0); delete codeEditor; } } } bool ResolveWatch(Watch wh) { bool result = false; _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::ResolveWatch()"); wh.Reset(); /*delete wh.value; if(wh.type) { FreeType(wh.type); wh.type = null; }*/ if(wh.expression) { char watchmsg[MAX_F_STRING]; if(state == stopped && !codeEditor) wh.value = CopyString($"No source file found for selected frame"); //if(codeEditor && state == stopped || state != stopped) else { Module backupPrivateModule; Context backupContext; Class backupThisClass; Expression exp; parseError = false; backupPrivateModule = GetPrivateModule(); backupContext = GetCurrentContext(); backupThisClass = GetThisClass(); if(codeEditor) { SetPrivateModule(codeEditor.privateModule); SetCurrentContext(codeEditor.globalContext); SetTopContext(codeEditor.globalContext); SetGlobalContext(codeEditor.globalContext); SetGlobalData(&codeEditor.globalData); } exp = ParseExpressionString(wh.expression); if(exp && !parseError) { char expString[4096]; expString[0] = 0; PrintExpression(exp, expString); if(GetPrivateModule()) { if(codeEditor) DebugFindCtxTree(codeEditor.ast, activeFrame.line, 0); ProcessExpressionType(exp); } wh.type = exp.expType; if(wh.type) wh.type.refCount++; DebugComputeExpression(exp); if(ExpressionIsError(exp)) { GDBFallBack(exp, expString); } /*if(exp.hasAddress) { char temp[MAX_F_STRING]; sprintf(temp, "0x%x", exp.address); wh.address = CopyString(temp); // wh.address = CopyStringf("0x%x", exp.address); }*/ /* //#ifdef _DEBUG { Type dataType = exp.expType; if(dataType) { char temp[MAX_F_STRING]; switch(dataType.kind) { case charType: sprintf(temp, "%i", exp.val.c); break; case shortType: sprintf(temp, "%i", exp.val.s); break; case intType: case longType: case enumType: sprintf(temp, "%i", exp.val.i); break; case int64Type: sprintf(temp, "%i", exp.val.i64); break; case pointerType: sprintf(temp, "%i", exp.val.p); break; case floatType: { long v = (long)exp.val.f; sprintf(temp, "%i", v); break; } case doubleType: { long v = (long)exp.val.d; sprintf(temp, "%i", v); break; } } if(temp) wh.intVal = CopyString(temp); switch(dataType.kind) { case charType: sprintf(temp, "0x%x", exp.val.c); break; case shortType: sprintf(temp, "0x%x", exp.val.s); break; case enumType: case intType: sprintf(temp, "0x%x", exp.val.i); break; case int64Type: sprintf(temp, "0x%x", exp.val.i64); break; case longType: sprintf(temp, "0x%x", exp.val.i64); break; case pointerType: sprintf(temp, "0x%x", exp.val.p); break; case floatType: { long v = (long)exp.val.f; sprintf(temp, "0x%x", v); break; } case doubleType: { long v = (long)exp.val.d; sprintf(temp, "0x%x", v); break; } } if(temp) wh.hexVal = CopyString(temp); switch(dataType.kind) { case charType: sprintf(temp, "0o%o", exp.val.c); break; case shortType: sprintf(temp, "0o%o", exp.val.s); break; case enumType: case intType: sprintf(temp, "0o%o", exp.val.i); break; case int64Type: sprintf(temp, "0o%o", exp.val.i64); break; case longType: sprintf(temp, "0o%o", exp.val.i64); break; case pointerType: sprintf(temp, "0o%o", exp.val.p); break; case floatType: { long v = (long)exp.val.f; sprintf(temp, "0o%o", v); break; } case doubleType: { long v = (long)exp.val.d; sprintf(temp, "0o%o", v); break; } } if(temp) wh.octVal = CopyString(temp); } } // WHATS THIS HERE ? if(exp.type == constantExp && exp.constant) wh.constant = CopyString(exp.constant); //#endif */ switch(exp.type) { case symbolErrorExp: snprintf(watchmsg, sizeof(watchmsg), $"Symbol \"%s\" not found", exp.identifier.string); break; case structMemberSymbolErrorExp: // todo get info as in next case (ExpClassMemberSymbolError) snprintf(watchmsg, sizeof(watchmsg), $"Error: Struct member not found for \"%s\"", wh.expression); break; case classMemberSymbolErrorExp: { Class _class; Expression memberExp = exp.member.exp; Identifier memberID = exp.member.member; Type type = memberExp.expType; if(type) { _class = (memberID && memberID.classSym) ? memberID.classSym.registered : ((type.kind == classType && type._class) ? type._class.registered : null); if(!_class) { char string[256] = ""; Symbol classSym; PrintTypeNoConst(type, string, false, true); classSym = FindClass(string); _class = classSym ? classSym.registered : null; } if(_class) snprintf(watchmsg, sizeof(watchmsg), $"Member \"%s\" not found in class \"%s\"", memberID ? memberID.string : "", _class.name); else snprintf(watchmsg, sizeof(watchmsg), "Member \"%s\" not found in unregistered class? (Should never get this message)", memberID ? memberID.string : ""); } else snprintf(watchmsg, sizeof(watchmsg), "Member \"%s\" not found in no type? (Should never get this message)", memberID ? memberID.string : ""); } break; case memoryErrorExp: // Need to ensure when set to memoryErrorExp, constant is set snprintf(watchmsg, sizeof(watchmsg), $"Memory can't be read at %s", /*(exp.type == constantExp) ? */exp.constant /*: null*/); break; case dereferenceErrorExp: snprintf(watchmsg, sizeof(watchmsg), $"Dereference failure for \"%s\"", wh.expression); break; case unknownErrorExp: snprintf(watchmsg, sizeof(watchmsg), $"Unknown error for \"%s\"", wh.expression); break; case noDebuggerErrorExp: snprintf(watchmsg, sizeof(watchmsg), $"Debugger required for symbol evaluation in \"%s\"", wh.expression); break; case debugStateErrorExp: snprintf(watchmsg, sizeof(watchmsg), $"Incorrect debugger state for symbol evaluation in \"%s\"", wh.expression); break; case 0: snprintf(watchmsg, sizeof(watchmsg), $"Null type for \"%s\"", wh.expression); break; case constantExp: case stringExp: // Temporary Code for displaying Strings if((exp.expType && ((exp.expType.kind == pointerType || exp.expType.kind == arrayType) && exp.expType.type.kind == charType)) || (wh.type && wh.type.kind == classType && wh.type._class && wh.type._class.registered && wh.type._class.registered.type == normalClass && !strcmp(wh.type._class.registered.name, "String"))) { if(exp.expType.kind != arrayType || exp.hasAddress) { uint64 address; char * string; char value[4196]; int len; //char temp[MAX_F_STRING * 32]; ExpressionType evalError = dummyExp; /*if(exp.expType.kind == arrayType) sprintf(temp, "(char*)0x%x", exp.address); else sprintf(temp, "(char*)%s", exp.constant);*/ //evaluation = Debugger::EvaluateExpression(temp, &evalError); // address = strtoul(exp.constant, null, 0); address = _strtoui64(exp.constant, null, 0); //_dpl(0, "0x", address); // snprintf(value, sizeof(value), "0x%08x ", address); if(address > 0xFFFFFFFFLL) snprintf(value, sizeof(value), (GetRuntimePlatform() == win32) ? "0x%016I64x " : "0x%016llx ", address); else snprintf(value, sizeof(value), (GetRuntimePlatform() == win32) ? "0x%08I64x " : "0x%08llx ", address); value[sizeof(value)-1] = 0; if(!address) strcat(value, $"Null string"); else { int size = 4096; len = strlen(value); string = null; while(!string && size > 2) { string = GdbReadMemory(address, size); size /= 2; } if(string && string[0]) { value[len++] = '('; if(UTF8Validate(string)) { int c; char ch; for(c = 0; (ch = string[c]) && c<4096; c++) value[len++] = ch; value[len++] = ')'; value[len++] = '\0'; } else { ISO8859_1toUTF8(string, value + len, 4096 - len - 30); strcat(value, ") (ISO8859-1)"); } delete string; } else if(string) { strcat(value, $"Empty string"); delete string; } else strcat(value, $"Couldn't read memory"); } wh.value = CopyString(value); } } else if(wh.type && wh.type.kind == classType && wh.type._class && wh.type._class.registered && wh.type._class.registered.type == enumClass) { uint64 value = strtoul(exp.constant, null, 0); Class enumClass = eSystem_FindClass(GetPrivateModule(), wh.type._class.registered.name); EnumClassData enumeration = (EnumClassData)enumClass.data; NamedLink item; for(item = enumeration.values.first; item; item = item.next) if((int)item.data == value) break; if(item) wh.value = CopyString(item.name); else wh.value = CopyString($"Invalid Enum Value"); result = true; } else if(wh.type && (wh.type.kind == charType || (wh.type.kind == classType && wh.type._class && wh.type._class.registered && !strcmp(wh.type._class.registered.fullName, "ecere::com::unichar"))) ) { unichar value; int signedValue; char charString[5]; char string[256]; if(exp.constant[0] == '\'') { if((int)((byte *)exp.constant)[1] > 127) { int nb; value = UTF8GetChar(exp.constant + 1, &nb); if(nb < 2) value = exp.constant[1]; signedValue = value; } else { signedValue = exp.constant[1]; { // Precomp Syntax error with boot strap here: byte b = (byte)(char)signedValue; value = (unichar) b; } } } else { if(wh.type.kind == charType && wh.type.isSigned) { signedValue = (int)(char)strtol(exp.constant, null, 0); { // Precomp Syntax error with boot strap here: byte b = (byte)(char)signedValue; value = (unichar) b; } } else { value = (uint)strtoul(exp.constant, null, 0); signedValue = (int)value; } } charString[0] = 0; UTF32toUTF8Len(&value, 1, charString, 5); if(value == '\0') snprintf(string, sizeof(string), "\'\\0' (0)"); else if(value == '\t') snprintf(string, sizeof(string), "\'\\t' (%d)", value); else if(value == '\n') snprintf(string, sizeof(string), "\'\\n' (%d)", value); else if(value == '\r') snprintf(string, sizeof(string), "\'\\r' (%d)", value); else if(wh.type.kind == charType && wh.type.isSigned) snprintf(string, sizeof(string), "\'%s\' (%d)", charString, signedValue); else if(value > 256 || wh.type.kind != charType) { if(value > 0x10FFFF || !GetCharCategory(value)) snprintf(string, sizeof(string), $"Invalid Unicode Keypoint (0x%08X)", value); else snprintf(string, sizeof(string), "\'%s\' (U+%04X)", charString, value); } else snprintf(string, sizeof(string), "\'%s\' (%d)", charString, value); string[sizeof(string)-1] = 0; wh.value = CopyString(string); result = true; } else { wh.value = CopyString(exp.constant); result = true; } break; default: if(exp.hasAddress) { wh.value = PrintHexUInt64(exp.address); result = true; } else { char tempString[256]; if(exp.member.memberType == propertyMember) snprintf(watchmsg, sizeof(watchmsg), $"Missing property evaluation support for \"%s\"", wh.expression); else snprintf(watchmsg, sizeof(watchmsg), $"Evaluation failed for \"%s\" of type \"%s\"", wh.expression, exp.type.OnGetString(tempString, null, null)); } break; } } else snprintf(watchmsg, sizeof(watchmsg), $"Invalid expression: \"%s\"", wh.expression); if(exp) FreeExpression(exp); SetPrivateModule(backupPrivateModule); SetCurrentContext(backupContext); SetTopContext(backupContext); SetGlobalContext(backupContext); SetThisClass(backupThisClass); } //else // wh.value = CopyString("No source file found for selected frame"); watchmsg[sizeof(watchmsg)-1] = 0; if(!wh.value) wh.value = CopyString(watchmsg); } ide.watchesView.UpdateWatch(wh); return result; } void EvaluateWatches() { _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::EvaluateWatches()"); for(wh : ide.workspace.watches) ResolveWatch(wh); } char * ::GdbEvaluateExpression(char * expression) { _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::GdbEvaluateExpression(", expression, ")"); eval.active = true; eval.error = none; GdbCommand(false, "-data-evaluate-expression \"%s\"", expression); if(eval.active) ide.outputView.debugBox.Logf("Debugger Error: GdbEvaluateExpression\n"); return eval.result; } // to be removed... use GdbReadMemory that returns a byte array instead char * ::GdbReadMemoryString(uint64 address, int size, char format, int rows, int cols) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbReadMemoryString(", address, ")"); eval.active = true; eval.error = none; #ifdef _DEBUG if(!size) _dpl(0, "GdbReadMemoryString called with size = 0!"); #endif // GdbCommand(false, "-data-read-memory 0x%08x %c, %d, %d, %d", address, format, size, rows, cols); if(GetRuntimePlatform() == win32) GdbCommand(false, "-data-read-memory 0x%016I64x %c, %d, %d, %d", address, format, size, rows, cols); else GdbCommand(false, "-data-read-memory 0x%016llx %c, %d, %d, %d", address, format, size, rows, cols); if(eval.active) ide.outputView.debugBox.Logf("Debugger Error: GdbReadMemoryString\n"); return eval.result; } byte * ::GdbReadMemory(uint64 address, int bytes) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbReadMemory(", address, ")"); eval.active = true; eval.error = none; //GdbCommand(false, "-data-read-memory 0x%08x %c, 1, 1, %d", address, 'u', bytes); if(GetRuntimePlatform() == win32) GdbCommand(false, "-data-read-memory 0x%016I64x %c, 1, 1, %d", address, 'u', bytes); else GdbCommand(false, "-data-read-memory 0x%016llx %c, 1, 1, %d", address, 'u', bytes); #ifdef _DEBUG if(!bytes) _dpl(0, "GdbReadMemory called with bytes = 0!"); #endif if(eval.active) ide.outputView.debugBox.Logf("Debugger Error: GdbReadMemory\n"); else if(eval.result && strcmp(eval.result, "N/A")) { byte * result = new byte[bytes]; byte * string = eval.result; int c = 0; while(true) { result[c++] = (byte)strtol(string, &string, 10); if(string) { if(*string == ',') string++; else break; } else break; } return result; } return null; } bool BreakpointHit(GdbDataStop stopItem, Breakpoint bpInternal, Breakpoint bpUser) { bool result = true; char * s1 = null; char * s2 = null; _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::BreakpointHit(", "bpInternal(", bpInternal ? s1=bpInternal.CopyLocationString(false) : null, "), ", "bpUser(", bpUser ? s2=bpUser.CopyLocationString(false) : null, ")) -- ", "ignoreBreakpoints(", ignoreBreakpoints, "), ", "hitCursorBreakpoint(", bpUser && bpUser.type == runToCursor, ")"); delete s1; delete s2; if(bpUser && stopItem.frame.line && bpUser.line != stopItem.frame.line) { // updating user breakpoint on hit location difference // todo, print something? bpUser.line = stopItem.frame.line; ide.breakpointsView.UpdateBreakpoint(bpUser.row); ide.workspace.Save(); } if(bpInternal) { bpInternal.hits++; if(bpInternal.type == internalModulesLoaded) modules = true; if(!bpUser && !userBreakOnInternalBreakpoint) { if(userAction == stepOut)//if(prevStopItem.reason == functionFinished) StepOut(ignoreBreakpoints); else result = false; } } if(bpUser) { bool conditionMet = true; bool levelMatch = (bpUser.level == -1 || bpUser.level == frameCount-1); if(bpUser.condition) conditionMet = ResolveWatch(bpUser.condition); bpUser.hits++; if(levelMatch && conditionMet) { if(!bpUser.ignore) bpUser.breaks++; else { bpUser.ignore--; result = false; } } else result = false; ide.breakpointsView.UpdateBreakpoint(bpUser.row); } if(!bpUser && !bpInternal) result = false; return result; } void ValgrindTargetThreadExit() { ide.outputView.debugBox.Logf($"ValgrindTargetThreadExit\n"); if(vgTargetHandle) { vgTargetHandle.Wait(); delete vgTargetHandle; } HandleExit(null, null); } void GdbThreadExit() { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::GdbThreadExit()"); if(state != terminated) { _ChangeState(terminated); targetProcessId = 0; ClearBreakDisplay(); if(vgLogFile) delete vgLogFile; if(gdbHandle) { serialSemaphore.Release(); gdbTimer.Stop(); gdbHandle.Wait(); delete gdbHandle; ide.outputView.debugBox.Logf($"Debugger Fatal Error: GDB lost\n"); ide.outputView.debugBox.Logf($"Debugging stopped\n"); ide.Update(null); HideDebuggerViews(); } //_ChangeState(terminated); } } void GdbThreadMain(char * output) { int i; Array outTokens { minAllocSize = 50 }; Array subTokens { minAllocSize = 50 }; DebugListItem item { }; DebugListItem item2 { }; bool setWaitingForPID = false; #if defined(GDB_DEBUG_CONSOLE) || defined(GDB_DEBUG_GUI) #ifdef GDB_DEBUG_CONSOLE _dpl2(_dpct, dplchan::gdbOutput, 0, output); #endif #ifdef GDB_DEBUG_OUTPUT { int len = strlen(output); if(len > 1024) { int c; char * start; char tmp[1025]; tmp[1024] = '\0'; start = output; for(c = 0; c < len / 1024; c++) { strncpy(tmp, start, 1024); ide.outputView.gdbBox.Logf("out: %s\n", tmp); start += 1024; } ide.outputView.gdbBox.Logf("out: %s\n", start); } else { ide.outputView.gdbBox.Logf("out: %s\n", output); } } #endif #ifdef GDB_DEBUG_CONSOLE strcpy(lastGdbOutput, output); #endif #ifdef GDB_DEBUG_GUI if(ide.gdbDialog) ide.gdbDialog.AddOutput(output); #endif #endif switch(output[0]) { case '~': if(strstr(output, "No debugging symbols found") || strstr(output, "(no debugging symbols found)")) { symbols = false; ide.outputView.debugBox.Logf($"Target doesn't contain debug information!\n"); ide.Update(null); } break; case '^': gdbReady = false; if(TokenizeList(output, ',', outTokens) && !strcmp(outTokens[0], "^done")) { //if(outTokens.count == 1) { if(sentKill) { sentKill = false; _ChangeState(loaded); targetProcessId = 0; if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item)) { if(!strcmp(item.name, "reason")) { char * reason = item.value; StripQuotes(reason, reason); if(!strcmp(reason, "exited-normally") || !strcmp(reason, "exited") || !strcmp(reason, "exited-signalled")) { char * exitCode; if(outTokens.count > 2 && TokenizeListItem(outTokens[2], item2)) { StripQuotes(item2.value, item2.value); if(!strcmp(item2.name, "exit-code")) exitCode = item2.value; else exitCode = null; } else exitCode = null; HandleExit(reason, exitCode); } } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "kill reply (", item.name, "=", item.value, ") is unheard of"); } else HandleExit(null, null); } } if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item)) { if(!strcmp(item.name, "bkpt")) { sentBreakInsert = false; #ifdef _DEBUG if(bpItem) _dpl(0, "problem"); #endif bpItem = ParseBreakpoint(item.value, outTokens); //breakType = bpValidation; } else if(!strcmp(item.name, "depth")) { StripQuotes(item.value, item.value); frameCount = atoi(item.value); activeFrame = null; stackFrames.Free(Frame::Free); } else if(!strcmp(item.name, "stack")) { Frame frame; if(stackFrames.count) ide.callStackView.Logf("...\n"); else activeFrame = null; item.value = StripBrackets(item.value); TokenizeList(item.value, ',', subTokens); for(i = 0; i < subTokens.count; i++) { if(TokenizeListItem(subTokens[i], item)) { if(!strcmp(item.name, "frame")) { frame = Frame { }; stackFrames.Add(frame); item.value = StripCurlies(item.value); ParseFrame(frame, item.value); if(frame.file && frame.from) _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "unexpected frame file and from members present"); if(frame.file) { char * s; if(activeFrameLevel == -1) { if(ide.projectView.IsModuleInProject(frame.file)); { if(frame.level != 0) { //stopItem.frame = frame; breakType = selectFrame; } else activeFrame = frame; activeFrameLevel = frame.level; } } ide.callStackView.Logf("%3d ", frame.level); if(!strncmp(frame.func, "__ecereMethod_", strlen("__ecereMethod_"))) ide.callStackView.Logf($"%s Method, %s:%d\n", &frame.func[strlen("__ecereMethod_")], (s = CopySystemPath(frame.file)), frame.line); else if(!strncmp(frame.func, "__ecereProp_", strlen("__ecereProp_"))) ide.callStackView.Logf($"%s Property, %s:%d\n", &frame.func[strlen("__ecereProp_")], (s = CopySystemPath(frame.file)), frame.line); else if(!strncmp(frame.func, "__ecereConstructor_", strlen("__ecereConstructor_"))) ide.callStackView.Logf($"%s Constructor, %s:%d\n", &frame.func[strlen("__ecereConstructor_")], (s = CopySystemPath(frame.file)), frame.line); else if(!strncmp(frame.func, "__ecereDestructor_", strlen("__ecereDestructor_"))) ide.callStackView.Logf($"%s Destructor, %s:%d\n", &frame.func[strlen("__ecereDestructor_")], (s = CopySystemPath(frame.file)), frame.line); else ide.callStackView.Logf($"%s Function, %s:%d\n", frame.func, (s = CopySystemPath(frame.file)), frame.line); delete s; } else { ide.callStackView.Logf("%3d ", frame.level); if(frame.from) { char * s; ide.callStackView.Logf($"inside %s, %s\n", frame.func, (s = CopySystemPath(frame.from))); delete s; } else if(frame.func) ide.callStackView.Logf("%s\n", frame.func); else ide.callStackView.Logf($"unknown source\n"); } } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "stack content (", item.name, "=", item.value, ") is unheard of"); } } if(activeFrameLevel == -1) { activeFrameLevel = 0; activeFrame = stackFrames.first; } ide.callStackView.Home(); ide.Update(null); subTokens.RemoveAll(); } /*else if(!strcmp(item.name, "frame")) { Frame frame { }; item.value = StripCurlies(item.value); ParseFrame(&frame, item.value); }*/ else if(!strcmp(item.name, "thread-ids")) { ide.threadsView.Clear(); item.value = StripCurlies(item.value); TokenizeList(item.value, ',', subTokens); for(i = subTokens.count - 1; ; i--) { if(TokenizeListItem(subTokens[i], item)) { if(!strcmp(item.name, "thread-id")) { int value; StripQuotes(item.value, item.value); value = atoi(item.value); ide.threadsView.Logf("%3d \n", value); } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "threads content (", item.name, "=", item.value, ") is unheard of"); } if(!i) break; } ide.threadsView.Home(); ide.Update(null); subTokens.RemoveAll(); //if(!strcmp(outTokens[2], "number-of-threads")) } else if(!strcmp(item.name, "new-thread-id")) { StripQuotes(item.value, item.value); activeThread = atoi(item.value); } else if(!strcmp(item.name, "value")) { StripQuotes(item.value, item.value); eval.result = CopyString(item.value); eval.active = false; } else if(!strcmp(item.name, "addr")) { for(i = 2; i < outTokens.count; i++) { if(TokenizeListItem(outTokens[i], item)) { if(!strcmp(item.name, "total-bytes")) { StripQuotes(item.value, item.value); eval.bytes = atoi(item.value); } else if(!strcmp(item.name, "next-row")) { StripQuotes(item.value, item.value); eval.nextBlockAddress = _strtoui64(item.value, null, 0); } else if(!strcmp(item.name, "memory")) { int j; //int value; //StripQuotes(item.value, item.value); item.value = StripBrackets(item.value); // this should be treated as a list... item.value = StripCurlies(item.value); TokenizeList(item.value, ',', subTokens); for(j = 0; j < subTokens.count; j++) { if(TokenizeListItem(subTokens[j], item)) { if(!strcmp(item.name, "data")) { item.value = StripBrackets(item.value); StripQuotes2(item.value, item.value); eval.result = CopyString(item.value); eval.active = false; } } } subTokens.RemoveAll(); } } } } else if(!strcmp(item.name, "source-path") || !strcmp(item.name, "BreakpointTable")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "command reply (", item.name, "=", item.value, ") is ignored"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "command reply (", item.name, "=", item.value, ") is unheard of"); } } else if(!strcmp(outTokens[0], "^running")) { waitingForPID = true; setWaitingForPID = true; ClearBreakDisplay(); } else if(!strcmp(outTokens[0], "^exit")) { _ChangeState(terminated); // ide.outputView.debugBox.Logf("Exit\n"); // ide.Update(null); gdbReady = true; serialSemaphore.Release(); } else if(!strcmp(outTokens[0], "^error")) { if(sentBreakInsert) { sentBreakInsert = false; breakpointError = true; } if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item)) { if(!strcmp(item.name, "msg")) { StripQuotes(item.value, item.value); if(eval.active) { eval.active = false; eval.result = null; if(strstr(item.value, "No symbol") && strstr(item.value, "in current context")) eval.error = symbolNotFound; else if(strstr(item.value, "Cannot access memory at address")) eval.error = memoryCantBeRead; else eval.error = unknown; } else if(!strcmp(item.value, "Previous frame inner to this frame (corrupt stack?)")) { } else if(!strncmp(item.value, "Cannot access memory at address", 31)) { } else if(!strcmp(item.value, "Cannot find bounds of current function")) { _ChangeState(stopped); gdbHandle.Printf("-exec-continue\n"); } else if(!strcmp(item.value, "ptrace: No such process.")) { _ChangeState(loaded); targetProcessId = 0; } else if(!strcmp(item.value, "Function \\\"WinMain\\\" not defined.")) { } else if(!strcmp(item.value, "You can't do that without a process to debug.")) { _ChangeState(loaded); targetProcessId = 0; } else if(strstr(item.value, "No such file or directory.")) { _ChangeState(loaded); targetProcessId = 0; } else if(strstr(item.value, "During startup program exited with code ")) { _ChangeState(loaded); targetProcessId = 0; } else { #ifdef _DEBUG if(strlen(item.value) < MAX_F_STRING) { char * s; ide.outputView.debugBox.Logf("GDB: %s\n", (s = CopyUnescapedString(item.value))); delete s; } else ide.outputView.debugBox.Logf("GDB: %s\n", item.value); #endif } } } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "error content (", item.name, "=", item.value, ") is unheard of"); } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "result-record: ", outTokens[0]); outTokens.RemoveAll(); break; case '+': _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "status-async-output: ", outTokens[0]); break; case '=': if(TokenizeList(output, ',', outTokens)) { if(!strcmp(outTokens[0], "=library-loaded")) FGODetectLoadedLibraryForAddedProjectIssues(outTokens); else if(!strcmp(outTokens[0], "=thread-group-created") || !strcmp(outTokens[0], "=thread-group-added") || !strcmp(outTokens[0], "=thread-group-started") || !strcmp(outTokens[0], "=thread-group-exited") || !strcmp(outTokens[0], "=thread-created") || !strcmp(outTokens[0], "=thread-exited") || !strcmp(outTokens[0], "=cmd-param-changed") || !strcmp(outTokens[0], "=library-unloaded") || !strcmp(outTokens[0], "=breakpoint-modified")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, outTokens[0], outTokens.count>1 ? outTokens[1] : "", outTokens.count>2 ? outTokens[2] : "", outTokens.count>3 ? outTokens[3] : "", outTokens.count>4 ? outTokens[4] : "", outTokens.count>5 ? outTokens[5] : "", outTokens.count>6 ? outTokens[6] : "", outTokens.count>7 ? outTokens[7] : "", outTokens.count>8 ? outTokens[8] : "", outTokens.count>9 ? outTokens[9] : ""); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "notify-async-output: ", outTokens[0]); } outTokens.RemoveAll(); break; case '*': gdbReady = false; if(TokenizeList(output, ',', outTokens)) { if(!strcmp(outTokens[0],"*running")) { waitingForPID = true; setWaitingForPID = true; } else if(!strcmp(outTokens[0], "*stopped")) { int tk; _ChangeState(stopped); for(tk = 1; tk < outTokens.count; tk++) { if(TokenizeListItem(outTokens[tk], item)) { if(!strcmp(item.name, "reason")) { char * reason = item.value; StripQuotes(reason, reason); if(!strcmp(reason, "exited-normally") || !strcmp(reason, "exited") || !strcmp(reason, "exited-signalled")) { char * exitCode; if(outTokens.count > tk+1 && TokenizeListItem(outTokens[tk+1], item2)) { tk++; StripQuotes(item2.value, item2.value); if(!strcmp(item2.name, "exit-code")) exitCode = item2.value; else exitCode = null; } else exitCode = null; HandleExit(reason, exitCode); needReset = true; } else if(!strcmp(reason, "breakpoint-hit")) { #ifdef _DEBUG if(stopItem) _dpl(0, "problem"); #endif stopItem = GdbDataStop { }; stopItem.reason = breakpointHit; for(i = tk+1; i < outTokens.count; i++) { TokenizeListItem(outTokens[i], item); StripQuotes(item.value, item.value); if(!strcmp(item.name, "bkptno")) stopItem.bkptno = atoi(item.value); else if(!strcmp(item.name, "thread-id")) stopItem.threadid = atoi(item.value); else if(!strcmp(item.name, "frame")) { item.value = StripCurlies(item.value); ParseFrame(stopItem.frame, item.value); } else if(!strcmp(item.name, "disp") || !strcmp(item.name, "stopped-threads") || !strcmp(item.name, "core")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "(", item.name, "=", item.value, ")"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown breakpoint hit item name (", item.name, "=", item.value, ")"); } event = hit; } else if(!strcmp(reason, "end-stepping-range")) { #ifdef _DEBUG if(stopItem) _dpl(0, "problem"); #endif stopItem = GdbDataStop { }; stopItem.reason = endSteppingRange; for(i = tk+1; i < outTokens.count; i++) { TokenizeListItem(outTokens[i], item); StripQuotes(item.value, item.value); if(!strcmp(item.name, "thread-id")) stopItem.threadid = atoi(item.value); else if(!strcmp(item.name, "frame")) { item.value = StripCurlies(item.value); ParseFrame(stopItem.frame, item.value); } else if(!strcmp(item.name, "reason") || !strcmp(item.name, "bkptno")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "(", item.name, "=", item.value, ")"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown end of stepping range item name (", item.name, "=", item.value, ")"); } event = stepEnd; ide.Update(null); } else if(!strcmp(reason, "function-finished")) { #ifdef _DEBUG if(stopItem) _dpl(0, "problem"); #endif stopItem = GdbDataStop { }; stopItem.reason = functionFinished; for(i = tk+1; i < outTokens.count; i++) { TokenizeListItem(outTokens[i], item); StripQuotes(item.value, item.value); if(!strcmp(item.name, "thread-id")) stopItem.threadid = atoi(item.value); else if(!strcmp(item.name, "frame")) { item.value = StripCurlies(item.value); ParseFrame(stopItem.frame, item.value); } else if(!strcmp(item.name, "gdb-result-var")) stopItem.gdbResultVar = CopyString(item.value); else if(!strcmp(item.name, "return-value")) stopItem.returnValue = CopyString(item.value); else if(!strcmp(item.name, "stopped-threads")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Advanced thread debugging not handled"); else if(!strcmp(item.name, "core")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Information (core) not used"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown function finished item name (", item.name, "=", item.value, ")"); } event = functionEnd; ide.Update(null); } else if(!strcmp(reason, "signal-received")) { #ifdef _DEBUG if(stopItem) _dpl(0, "problem"); #endif stopItem = GdbDataStop { }; stopItem.reason = signalReceived; for(i = tk+1; i < outTokens.count; i++) { TokenizeListItem(outTokens[i], item); StripQuotes(item.value, item.value); if(!strcmp(item.name, "signal-name")) stopItem.name = CopyString(item.value); else if(!strcmp(item.name, "signal-meaning")) stopItem.meaning = CopyString(item.value); else if(!strcmp(item.name, "thread-id")) stopItem.threadid = atoi(item.value); else if(!strcmp(item.name, "frame")) { item.value = StripCurlies(item.value); ParseFrame(stopItem.frame, item.value); } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown signal reveived item name (", item.name, "=", item.value, ")"); } if(!strcmp(stopItem.name, "SIGTRAP")) { switch(breakType) { case internal: breakType = none; break; case restart: case stop: break; default: event = breakEvent; } } else { event = signal; } } else if(!strcmp(reason, "watchpoint-trigger")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason watchpoint trigger not handled"); else if(!strcmp(reason, "read-watchpoint-trigger")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason read watchpoint trigger not handled"); else if(!strcmp(reason, "access-watchpoint-trigger")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason access watchpoint trigger not handled"); else if(!strcmp(reason, "watchpoint-scope")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason watchpoint scope not handled"); else if(!strcmp(reason, "location-reached")) _dpl2(_dpct, dplchan::gdbProtoIgnored, 0, "Reason location reached not handled"); else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown reason: ", reason); } else { PrintLn(output); } } } if(usingValgrind && event == none && !stopItem) event = valgrindStartPause; app.SignalEvent(); } } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, "Unknown exec-async-output: ", outTokens[0]); outTokens.RemoveAll(); break; case '(': if(!strcmpi(output, "(gdb) ")) { if(waitingForPID) { char exeFile[MAX_LOCATION]; int oldProcessID = targetProcessId; GetLastDirectory(targetFile, exeFile); while(!targetProcessId/*true*/) { targetProcessId = Process_GetChildExeProcessId(gdbProcessId, exeFile); if(targetProcessId || gdbHandle.Peek()) break; Sleep(0.01); } if(targetProcessId) _ChangeState(running); else if(!oldProcessID) { ide.outputView.debugBox.Logf($"Debugger Error: No target process ID\n"); // TO VERIFY: The rest of this block has not been thoroughly tested in this particular location gdbHandle.Printf("-gdb-exit\n"); gdbTimer.Stop(); _ChangeState(terminated); //loaded; prjConfig = null; if(ide.workspace) { for(bp : ide.workspace.breakpoints) bp.inserted = false; } for(bp : sysBPs) bp.inserted = false; if(bpRunToCursor) bpRunToCursor.inserted = false; ide.outputView.debugBox.Logf($"Debugging stopped\n"); ClearBreakDisplay(); #if defined(__unix__) if(!usingValgrind && FileExists(progFifoPath)) //fileCreated) { progThread.terminate = true; if(fifoFile) { fifoFile.CloseInput(); app.Unlock(); progThread.Wait(); app.Lock(); delete fifoFile; } DeleteFile(progFifoPath); progFifoPath[0] = '\0'; rmdir(progFifoDir); } #endif } } gdbReady = true; serialSemaphore.Release(); } else _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, $"Unknown prompt", output); break; case '&': if(!strncmp(output, "&\"warning:", 10)) { char * content; content = strstr(output, "\""); StripQuotes(content, content); content = strstr(content, ":"); if(content) content++; if(content) { char * s; ide.outputView.debugBox.LogRaw((s = CopyUnescapedString(content))); delete s; ide.Update(null); } } break; default: _dpl2(_dpct, dplchan::gdbProtoUnknown, 0, $"Unknown output: ", output); } if(!setWaitingForPID) waitingForPID = false; setWaitingForPID = false; delete outTokens; delete subTokens; delete item; delete item2; } // From GDB Output functions void FGODetectLoadedLibraryForAddedProjectIssues(Array outTokens) { char path[MAX_LOCATION] = ""; char file[MAX_FILENAME] = ""; bool symbolsLoaded; DebugListItem item { }; //_dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::FGODetectLoadedLibraryForAddedProjectIssues()"); for(token : outTokens) { if(TokenizeListItem(token, item)) { if(!strcmp(item.name, "target-name")) { StripQuotes(item.value, path); MakeSystemPath(path); GetLastDirectory(path, file); } else if(!strcmp(item.name, "symbols-loaded")) { symbolsLoaded = (atoi(item.value) == 1); } } } delete item; if(path[0] && file[0]) { for(prj : ide.workspace.projects; prj != ide.workspace.projects.firstIterator.data) { bool match; char * dot; char prjTargetPath[MAX_LOCATION]; char prjTargetFile[MAX_FILENAME]; DirExpression targetDirExp = prj.GetTargetDir(currentCompiler, prj.config, bitDepth); strcpy(prjTargetPath, prj.topNode.path); PathCat(prjTargetPath, targetDirExp.dir); prjTargetFile[0] = '\0'; prj.CatTargetFileName(prjTargetFile, currentCompiler, prj.config); PathCat(prjTargetPath, prjTargetFile); MakeSystemPath(prjTargetPath); match = !fstrcmp(prjTargetFile, file); if(!match && (dot = strstr(prjTargetFile, ".so."))) { char * dot3 = strstr(dot+4, "."); if(dot3) { dot3[0] = '\0'; match = !fstrcmp(prjTargetFile, file); } if(!match) { dot[3] = '\0'; match = !fstrcmp(prjTargetFile, file); } } if(match) { // TODO: nice visual feedback to better warn user. use some ide notification system or other means. /* -- 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) if(!symbolsLoaded) ide.outputView.debugBox.Logf($"Attention! No symbols for loaded library %s matched to the %s added project.\n", path, prj.topNode.name); */ match = !fstrcmp(prjTargetPath, path); if(!match && (dot = strstr(prjTargetPath, ".so."))) { char * dot3 = strstr(dot+4, "."); if(dot3) { dot3[0] = '\0'; match = !fstrcmp(prjTargetPath, path); } if(!match) { dot[3] = '\0'; match = !fstrcmp(prjTargetPath, path); } } if(!match) ide.outputView.debugBox.Logf($"Loaded library %s doesn't match the %s target of the %s added project.\n", path, prjTargetPath, prj.topNode.name); break; } } } } void FGOBreakpointModified(Array outTokens) { _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::FGOBreakpointModified() -- TODO only if needed: support breakpoint modified"); #if 0 DebugListItem item { }; if(outTokens.count > 1 && TokenizeListItem(outTokens[1], item)) { if(!strcmp(item.name, "bkpt")) { GdbDataBreakpoint modBp = ParseBreakpoint(item.value, outTokens); delete modBp; } } #endif } ExpressionType ::DebugEvalExpTypeError(char * result) { _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::DebugEvalExpTypeError()"); if(result) return dummyExp; switch(eval.error) { case symbolNotFound: return symbolErrorExp; case memoryCantBeRead: return memoryErrorExp; } return unknownErrorExp; } char * ::EvaluateExpression(char * expression, ExpressionType * error) { char * result; _dpl2(_dpct, dplchan::debuggerWatches, 0, "Debugger::EvaluateExpression(", expression, ")"); if(ide.projectView && ide.debugger.state == stopped) { result = GdbEvaluateExpression(expression); *error = DebugEvalExpTypeError(result); } else { result = null; *error = noDebuggerErrorExp; } return result; } char * ::ReadMemory(uint64 address, int size, char format, ExpressionType * error) { // check for state char * result = GdbReadMemoryString(address, size, format, 1, 1); _dpl2(_dpct, dplchan::debuggerCall, 0, "Debugger::ReadMemory(", address, ")"); if(!result || !strcmp(result, "N/A")) *error = memoryErrorExp; else *error = DebugEvalExpTypeError(result); return result; } } class ValgrindLogThread : Thread { Debugger debugger; unsigned int Main() { static char output[4096]; Array dynamicBuffer { minAllocSize = 4096 }; File oldValgrindHandle = vgLogFile; incref oldValgrindHandle; app.Lock(); while(debugger.state != terminated && vgLogFile) { int result; app.Unlock(); result = vgLogFile.Read(output, 1, sizeof(output)); app.Lock(); if(debugger.state == terminated || !vgLogFile/* || vgLogFile.Eof()*/) break; if(result) { int c; int start = 0; for(c = 0; c dynamicBuffer { minAllocSize = 4096 }; DualPipe oldValgrindHandle = vgTargetHandle; incref oldValgrindHandle; app.Lock(); while(debugger.state != terminated && vgTargetHandle && !vgTargetHandle.Eof()) { int result; app.Unlock(); result = vgTargetHandle.Read(output, 1, sizeof(output)); app.Lock(); if(debugger.state == terminated || !vgTargetHandle || vgTargetHandle.Eof()) break; if(result) { int c; int start = 0; for(c = 0; c dynamicBuffer { minAllocSize = 4096 }; DualPipe oldGdbHandle = gdbHandle; incref oldGdbHandle; app.Lock(); while(debugger.state != terminated && gdbHandle && !gdbHandle.Eof()) { int result; app.Unlock(); result = gdbHandle.Read(output, 1, sizeof(output)); app.Lock(); if(debugger.state == terminated || !gdbHandle || gdbHandle.Eof()) break; if(result) { int c; int start = 0; for(c = 0; c #include #include #include #undef uint File fifoFile; class ProgramThread : Thread { bool terminate; unsigned int Main() { bool result = true; bool fileCreated = false; mode_t mask = 0600; static char output[1000]; int fd; /*if(!mkfifo(progFifoPath, mask)) { fileCreated = true; } else { app.Lock(); ide.outputView.debugBox.Logf($"err: Unable to create FIFO %s\n", progFifoPath); app.Unlock(); }*/ if(FileExists(progFifoPath)) //fileCreated) { fifoFile = FileOpen(progFifoPath, read); if(!fifoFile) { app.Lock(); ide.outputView.debugBox.Logf(openFIFOMsg, progFifoPath); app.Unlock(); } else { fd = fileno((FILE *)fifoFile.input); //fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); } } while(!terminate && fifoFile && !fifoFile.Eof()) { fd_set rs, es; struct timeval time; int selectResult; time.tv_sec = 1; time.tv_usec = 0; FD_ZERO(&rs); FD_ZERO(&es); FD_SET(fd, &rs); FD_SET(fd, &es); selectResult = select(fd + 1, &rs, null, null, &time); if(FD_ISSET(fd, &rs)) { int result = (int)read(fd, output, sizeof(output)-1); if(!result || (result < 0 && errno != EAGAIN)) break; if(result > 0) { output[result] = '\0'; if(strcmp(output,"&\"warning: GDB: Failed to set controlling terminal: Invalid argument\\n\"\n")) { app.Lock(); ide.outputView.debugBox.Log(output); app.Unlock(); } } } } //if(fifoFile) { //fifoFile.CloseInput(); //delete fifoFile; app.Lock(); ide.outputView.debugBox.Log("\n"); app.Unlock(); } /* if(FileExists(progFifoPath)) //fileCreated) { DeleteFile(progFifoPath); progFifoPath[0] = '\0'; } */ return 0; } } #endif class Argument : struct { Argument prev, next; char * name; property char * name { set { delete name; if(value) name = CopyString(value); } } char * val; property char * val { set { delete val; if(value) val = CopyString(value); } } void Free() { delete name; delete val; } ~Argument() { Free(); } } class Frame : struct { Frame prev, next; int level; char * addr; property char * addr { set { delete addr; if(value) addr = CopyString(value); } } char * func; property char * func { set { delete func; if(value) func = CopyString(value); } } int argsCount; OldList args; char * from; property char * from { set { delete from; if(value) from = CopyUnescapedUnixPath(value); } } char * file; property char * file { set { delete file; if(value) file = CopyUnescapedUnixPath(value); } } char * absoluteFile; property char * absoluteFile { set { delete absoluteFile; if(value) absoluteFile = CopyUnescapedUnixPath(value); } } int line; void Free() { delete addr; delete func; delete from; delete file; delete absoluteFile; args.Free(Argument::Free); } ~Frame() { Free(); } } class GdbDataStop : struct { DebuggerReason reason; int threadid; union { struct { int bkptno; }; struct { char * name; char * meaning; }; struct { char * gdbResultVar; char * returnValue; }; }; Frame frame { }; void Free() { if(reason) { if(reason == signalReceived) { delete name; delete meaning; } else if(reason == functionFinished) { delete gdbResultVar; delete returnValue; } } if(frame) frame.Free(); } ~GdbDataStop() { Free(); } } class GdbDataBreakpoint : struct { int id; char * number; property char * number { set { delete number; if(value) number = CopyString(value); } } char * type; property char * type { set { delete type; if(value) type = CopyString(value); } } char * disp; property char * disp { set { delete disp; if(value) disp = CopyString(value); } } bool enabled; char * addr; property char * addr { set { delete addr; if(value) addr = CopyString(value); } } char * func; property char * func { set { delete func; if(value) func = CopyString(value); } } char * file; property char * file { set { delete file; if(value) file = CopyUnescapedUnixPath(value); } } char * fullname; property char * fullname { set { delete fullname; if(value) fullname = CopyUnescapedUnixPath(value); } } int line; char * at; property char * at { set { delete at; if(value) at = CopyString(value); } } int times; Array multipleBPs; void Print() { _dpl(0, ""); PrintLn("{", "#", number, " T", type, " D", disp, " E", enabled, " H", times, " (", func, ") (", file, ":", line, ") (", fullname, ") (", addr, ") (", at, ")", "}"); } void Free() { delete type; delete disp; delete addr; delete func; delete file; delete at; if(multipleBPs) multipleBPs.Free(); delete multipleBPs; } ~GdbDataBreakpoint() { Free(); } } class Breakpoint : struct { class_no_expansion; char * function; property char * function { set { delete function; if(value) function = CopyString(value); } } char * relativeFilePath; property char * relativeFilePath { set { delete relativeFilePath; if(value) relativeFilePath = CopyString(value); } } char * absoluteFilePath; property char * absoluteFilePath { set { delete absoluteFilePath; if(value) absoluteFilePath = CopyString(value); } } int line; bool enabled; int hits; int breaks; int ignore; int level; Watch condition; bool inserted; BreakpointType type; DataRow row; GdbDataBreakpoint bp; char * CopyLocationString(bool removePath) { char * location; char * file = relativeFilePath ? relativeFilePath : absoluteFilePath; bool removingPath = removePath && file; if(removingPath) { char * fileName = new char[MAX_FILENAME]; GetLastDirectory(file, fileName); file = fileName; } if(function) { if(file) location = PrintString(file, ":", function); else location = CopyString(function); } else location = PrintString(file, ":", line); if(removingPath) delete file; return location; } char * CopyUserLocationString() { char * location; char * loc = CopyLocationString(false); Project prj = null; for(p : ide.workspace.projects) { if(p.topNode.FindByFullPath(absoluteFilePath, false)) { prj = p; break; } } if(prj) { location = PrintString("(", prj.name, ")", loc); delete loc; } else location = loc; return location; } void Save(File f) { if(relativeFilePath && relativeFilePath[0]) { f.Printf(" * %d,%d,%d,%d,%s\n", enabled ? 1 : 0, ignore, level, line, relativeFilePath); if(condition) f.Printf(" ~ %s\n", condition.expression); } } void Free() { if(bp) bp.Free(); delete bp; delete function; delete relativeFilePath; delete absoluteFilePath; } ~Breakpoint() { Free(); } } class Watch : struct { class_no_expansion; Type type; char * expression; char * value; DataRow row; void Save(File f) { f.Printf(" ~ %s\n", expression); } void Free() { delete expression; delete value; FreeType(type); type = null; } void Reset() { delete value; FreeType(type); type = null; } ~Watch() { Free(); } } class DebugListItem : struct { char * name; char * value; } struct DebugEvaluationData { bool active; char * result; int bytes; uint64 nextBlockAddress; DebuggerEvaluationError error; }; class CodeLocation : struct { char * file; char * absoluteFile; int line; CodeLocation ::ParseCodeLocation(char * location) { if(location) { char * colon = null; char * temp; char loc[MAX_LOCATION]; strcpy(loc, location); for(temp = loc; temp = strstr(temp, ":"); temp++) colon = temp; if(colon) { colon[0] = '\0'; colon++; if(colon) { int line = atoi(colon); if(line) { CodeLocation codloc { line = line }; codloc.file = CopyString(loc); codloc.absoluteFile = ide.workspace.GetAbsolutePathFromRelative(loc); return codloc; } } } } return null; } void Free() { delete file; delete absoluteFile; } ~CodeLocation() { Free(); } } void GDBFallBack(Expression exp, String expString) { char * result; ExpressionType evalError = dummyExp; result = Debugger::EvaluateExpression(expString, &evalError); if(result) { exp.constant = result; exp.type = constantExp; } }