diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d129de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +out/ +.vscode-test/ diff --git a/LICENSE b/LICENSE index 261eeb9..8dada3e 100644 --- a/LICENSE +++ b/LICENSE @@ -178,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" + boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/dist/adapter.js b/dist/adapter.js new file mode 100644 index 0000000..0476752 --- /dev/null +++ b/dist/adapter.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vscode-debugadapter")):"function"==typeof define&&define.amd?define("platformio-vscode-debug",["vscode-debugadapter"],t):"object"==typeof exports?exports["platformio-vscode-debug"]=t(require("vscode-debugadapter")):e["platformio-vscode-debug"]=t(e["vscode-debugadapter"])}(global,(function(e){return(()=>{"use strict";var t={28:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.MIError=t.VariableObject=void 0;const r=s(398);t.VariableObject=class{constructor(e){this.name=r.MINode.valueOf(e,"name"),this.exp=r.MINode.valueOf(e,"exp"),this.numchild=parseInt(r.MINode.valueOf(e,"numchild")),this.type=r.MINode.valueOf(e,"type"),this.value=r.MINode.valueOf(e,"value"),this.threadId=r.MINode.valueOf(e,"thread-id"),this.frozen=!!r.MINode.valueOf(e,"frozen"),this.dynamic=!!r.MINode.valueOf(e,"dynamic"),this.displayhint=r.MINode.valueOf(e,"displayhint"),this.hasMore=!!r.MINode.valueOf(e,"has_more")}applyChanges(e){this.value=r.MINode.valueOf(e,"value"),r.MINode.valueOf(e,"type_changed")&&(this.type=r.MINode.valueOf(e,"new_type")),this.dynamic=!!r.MINode.valueOf(e,"dynamic"),this.displayhint=r.MINode.valueOf(e,"displayhint"),this.hasMore=!!r.MINode.valueOf(e,"has_more")}isCompound(){return this.numchild>0||"{...}"===this.value||this.dynamic&&("array"===this.displayhint||"map"===this.displayhint)}toProtocolVariable(){return{name:this.exp,evaluateName:this.fullExp||this.exp,value:void 0===this.value?"":this.value,type:this.type,presentationHint:{kind:this.displayhint},variablesReference:this.id}}},t.MIError=class{constructor(e,t){Object.defineProperty(this,"name",{get:()=>this.constructor.name}),Object.defineProperty(this,"message",{get:()=>e}),Object.defineProperty(this,"source",{get:()=>t}),Error.captureStackTrace(this,this.constructor)}toString(){return`${this.message} (from ${this.source})`}},Object.setPrototypeOf(t.MIError,Object.create(Error.prototype)),t.MIError.prototype.constructor=t.MIError},656:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.expandValue=t.isExpandable=void 0;const r=s(398),i=/^([a-zA-Z_\-][a-zA-Z0-9_\-]*|\[\d+\])\s*=\s*/,n=/^[a-zA-Z_\-][a-zA-Z0-9_\-]*/,o=/^\<.+?\>/,a=/^(0x[0-9a-fA-F]+\s*)"/,d=/^0x[0-9a-fA-F]+/,l=/^0x0+\b/,u=/^(\d+) ['"]/,h=/^\d+(\.\d+)?/;t.isExpandable=function(e){return 0===(e=e.trim()).length?0:e.startsWith("{...}")?2:"{"===e[0]?1:e.startsWith("true")||e.startsWith("false")||l.exec(e)||a.exec(e)?0:d.exec(e)?2:(u.exec(e)||h.exec(e)||n.exec(e)||o.exec(e),0)},t.expandValue=function(e,t,s="",c){const p=()=>{if('"'!==(t=t.trim())[0]&&"'"!==t[0])return"";let e=1,s=!0;const r=t[0];let i=t.substr(1),n=!1;for(;s;)n?n=!1:"\\"===i[0]?n=!0:i[0]===r&&(s=!1),i=i.substr(1),e++;const o=t.substr(0,e).trim();return t=t.substr(e).trim(),o},m=[s];let b,f,g,v,y,R="";const x=e=>{let t="",s="";return m.push(e),m.forEach((e=>{if(s="",""!==e)if(e.startsWith("["))t+=e;else if(t){for(;e.startsWith("*");)s+="*",e=e.substr(1);t=t+"."+e}else t=e})),m.pop(),s+t};return b=()=>'"'===(t=t.trim())[0]?p():"{"===t[0]?(()=>{if("{"!==(t=t.trim())[0])return;if("}"===(t=t.substr(1).trim())[0])return t=t.substr(1).trim(),[];if(t.startsWith("...")&&"}"===(t=t.substr(3).trim())[0])return t=t.substr(1).trim(),"<...>";const e=t.indexOf("="),s=t.indexOf("{"),r=t.indexOf(",");let i=s;if(-1!==r&&ri||-1===e){const e=[];m.push("[0]");let s=b();m.pop(),e.push(y("[0]",s));let r=0;for(;;){if(m.push("["+ ++r+"]"),!(s=g())){m.pop();break}m.pop(),e.push(y("["+r+"]",s))}return t=t.substr(1).trim(),e}let n=v(!0);if(n){const e=[];for(e.push(n);n=f(!0);)e.push(n);return t=t.substr(1).trim(),e}})():(()=>{let e,s;return 0===(t=t.trim()).length?e=void 0:t.startsWith("true")?(e="true",t=t.substr(4).trim()):t.startsWith("false")?(e="false",t=t.substr(5).trim()):(s=l.exec(t))?(e="",t=t.substr(s[0].length).trim()):(s=a.exec(t))?(t=t.substr(s[1].length).trim(),e=p()):(s=d.exec(t))?(e="*"+s[0],t=t.substr(s[0].length).trim()):(s=u.exec(t))?(e=s[1],t=t.substr(s[0].length-1),e+=" "+p()):(s=h.exec(t))||(s=n.exec(t))||(s=o.exec(t))?(e=s[0],t=t.substr(s[0].length).trim()):e=t,e})(),v=(e=!1)=>{t=t.trim();const s=i.exec(t);if(!s)return;t=t.substr(s[0].length).trim();const r=R=s[1];e&&m.push(R);const n=b();return e&&m.pop(),y(r,n)},y=(t,s)=>{let i=0;return"object"==typeof s&&(i=e(s),s="Object"),"string"==typeof s&&s.startsWith("*0x")&&(c&&"1"===r.MINode.valueOf(c,"arg")?(i=e(x("*("+t),{arg:!0}),s=""):(i=e(x("*"+t)),s="Object@"+s)),"string"==typeof s&&s.startsWith("<...>")&&(i=e(x(t)),s="..."),{name:t,value:s,variablesReference:i}},g=()=>{if(","===(t=t.trim())[0])return t=t.substr(1).trim(),b()},f=(e=!1)=>{if(","===(t=t.trim())[0])return t=t.substr(1).trim(),v(e)},t=t.trim(),b()}},789:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){return new(s||(s=Promise))((function(i,n){function o(e){try{d(r.next(e))}catch(e){n(e)}}function a(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}d((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.GDBDebugSession=void 0;const i=s(113),n=s(147),o=s(512),a=s(797),d=s(200),l=s(593),u=s(28),h=s(656),c=s(504),p=s(398),m=s(527);class b{constructor(e,t){this.name=e,this.options=t}}const f=65536,g=131071;class v extends a.Event{constructor(e,t){super("custom-stop",{reason:e,threadID:t})}}class y extends a.Event{constructor(e,t=!0){super("custom-continued",{threadID:e,allThreads:t})}}class R extends a.DebugSession{constructor(e,t=!1,s=1){super(e,t),this.variableHandles=new a.Handles(131072),this.variableHandlesReverse={},this.forceDisassembly=!1,this.activeEditorPath=null,this.currentThreadId=0,this.stopped=!1,this.stoppedReason="",this.breakpointMap=new Map,this.fileExistsCache=new Map}initDebugger(){this.miDebugger.on("launcherror",this.launchError.bind(this)),this.miDebugger.on("quit",this.quitEvent.bind(this)),this.miDebugger.on("exited-normally",this.quitEvent.bind(this)),this.miDebugger.on("stopped",this.stopEvent.bind(this)),this.miDebugger.on("msg",this.handleMsg.bind(this)),this.miDebugger.on("breakpoint",this.handleBreakpoint.bind(this)),this.miDebugger.on("step-end",this.handleBreak.bind(this)),this.miDebugger.on("step-out-end",this.handleBreak.bind(this)),this.miDebugger.on("signal-stop",this.handlePause.bind(this)),this.miDebugger.on("running",this.handleRunning.bind(this)),this.miDebugger.on("thread-created",this.handleThreadCreated.bind(this)),this.miDebugger.on("thread-exited",this.handleThreadExited.bind(this)),this.miDebugger.on("thread-selected",this.handleThreadSelected.bind(this)),this.sendEvent(new a.InitializedEvent)}initializeRequest(e,t){e.body.supportsConditionalBreakpoints=!0,e.body.supportsConfigurationDoneRequest=!0,e.body.supportsEvaluateForHovers=!0,e.body.supportsFunctionBreakpoints=!0,e.body.supportsHitConditionalBreakpoints=!0,e.body.supportsRestartRequest=!0,e.body.supportsSetVariable=!0,e.body.supportsTerminateRequest=!0,e.body.supportsValueFormattingOptions=!0,this.sendResponse(e)}launchRequest(e,t){this.args=t,this.processLaunchAttachRequest(e,!1)}attachRequest(e,t){this.args=t,this.processLaunchAttachRequest(e,!0)}processLaunchAttachRequest(e,t){this.quit=!1,this.attached=!1,this.started=!1,this.crashed=!1,this.debugReady=!1,this.stopped=!1,this.breakpointMap=new Map,this.fileExistsCache=new Map;const s=["debug"];this.args.projectEnvName&&s.push("-e",this.args.projectEnvName),this.args.loadMode&&s.push("--load-mode",this.args.loadMode),s.push("--interface","gdb","--interpreter=mi2","-q"),this.miDebugger=new c.MI2("platformio",s),this.initDebugger(),this.miDebugger.printCalls=!!this.args.showDevDebugOutput,this.miDebugger.debugOutput=!!this.args.showDevDebugOutput,this.miDebugger.once("debug-ready",(()=>{this.debugReady=!0,this.stopped=!0,this.stoppedReason="start",this.sendEvent(new d.StoppedEvent("start",this.currentThreadId,!0)),this.sendEvent(new v("start",this.currentThreadId))})),this.miDebugger.connect(this.args.cwd,['interpreter-exec console "source .pioinit"',"enable-pretty-printing"]).then((()=>{this.symbolTable=new m.SymbolTable(this.args.toolchainBinDir,this.args.executable);try{this.symbolTable.loadSymbols(),this.started=!0,this.sendResponse(e)}catch(t){this.args.toolchainBinDir&&this.sendErrorResponse(e,102,`Failed to load symbols from executable file: ${t.toString()}`)}}),(t=>{this.sendErrorResponse(e,103,`Failed to launch GDB: ${t.toString()}`)}))}customRequest(e,t,s){switch(e){case"set-force-disassembly":t.body={success:!0},this.forceDisassembly=s.force,this.stopped&&(this.activeEditorPath=null,this.sendEvent(new a.ContinuedEvent(this.currentThreadId,!0)),this.sendEvent(new d.StoppedEvent(this.stoppedReason,this.currentThreadId,!0))),this.sendResponse(t);break;case"load-function-symbols":t.body={functionSymbols:this.symbolTable.getFunctionSymbols()},this.sendResponse(t);break;case"set-active-editor":this.activeEditorPath=s.path,t.body={},this.sendResponse(t);break;case"get-arguments":t.body=this.args,this.sendResponse(t);break;case"read-memory":this.customReadMemoryRequest(t,s.address,s.length);break;case"write-memory":this.customWriteMemoryRequest(t,s.address,s.data);break;case"read-registers":this.customReadRegistersRequest(t);break;case"read-register-list":this.customReadRegisterListRequest(t);break;case"disassemble":this.disassembleRequest(t,s);break;case"execute-command":let e=s.command;e=e.startsWith("-")?e.substring(1):`interpreter-exec console "${e}"`,this.miDebugger.sendCommand(e).then((e=>{t.body=e.resultRecords,this.sendResponse(t)}),(e=>{t.body=e,this.sendErrorResponse(t,110,"Unable to execute command")}));break;default:t.body={error:"Invalid command."},this.sendResponse(t)}}disassembleRequest(e,t){return r(this,void 0,void 0,(function*(){if(t.function)try{const s=yield this.getDisassemblyForFunction(t.function,t.file);e.body={instructions:s.instructions,name:s.name,file:s.file,address:s.address,length:s.length},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,1,`Unable to disassemble: ${t.toString()}`)}else if(t.startAddress)try{let s=this.symbolTable.getFunctionAtAddress(t.startAddress);if(s)s=yield this.getDisassemblyForFunction(s.name,s.file),e.body={instructions:s.instructions,name:s.name,file:s.file,address:s.address,length:s.length},this.sendResponse(e);else{const s=yield this.getDisassemblyForAddresses(t.startAddress,t.length||256);e.body={instructions:s},this.sendResponse(e)}}catch(t){this.sendErrorResponse(e,1,`Unable to disassemble: ${t.toString()}`)}else this.sendErrorResponse(e,1,"Unable to disassemble; invalid parameters.")}))}getDisassemblyForFunction(e,t){return r(this,void 0,void 0,(function*(){const s=this.symbolTable.getFunctionByName(e,t);if(!s)throw new Error(`Unable to find function with name ${e}.`);if(s.instructions)return s;const r=s.address,i=s.address+s.length,n=(yield this.miDebugger.sendCommand(`data-disassemble -s ${(0,l.hexFormat)(r,8)} -e ${(0,l.hexFormat)(i,8)} -- 2`)).result("asm_insns").map((e=>({address:p.MINode.valueOf(e,"address"),functionName:p.MINode.valueOf(e,"func-name"),offset:parseInt(p.MINode.valueOf(e,"offset")),instruction:p.MINode.valueOf(e,"inst"),opcodes:p.MINode.valueOf(e,"opcodes")})));return s.instructions=n,s}))}getDisassemblyForAddresses(e,t){return r(this,void 0,void 0,(function*(){const s=e+t;return(yield this.miDebugger.sendCommand(`data-disassemble -s ${(0,l.hexFormat)(e,8)} -e ${(0,l.hexFormat)(s,8)} -- 2`)).result("asm_insns").map((e=>({address:p.MINode.valueOf(e,"address"),functionName:p.MINode.valueOf(e,"func-name"),offset:parseInt(p.MINode.valueOf(e,"offset")),instruction:p.MINode.valueOf(e,"inst"),opcodes:p.MINode.valueOf(e,"opcodes")})))}))}customReadMemoryRequest(e,t,s){this.miDebugger.examineMemory(t,s).then((s=>{const r=s.match(/[0-9a-f]{2}/g).map((e=>parseInt(e,16)));e.body={startAddress:t,endAddress:t+r.length,bytes:r},this.sendResponse(e)}),(t=>{e.body={error:t},this.sendErrorResponse(e,114,`Unable to read memory: ${t.toString()}`)}))}customWriteMemoryRequest(e,t,s){const r=(0,l.hexFormat)(t,8);this.miDebugger.sendCommand(`data-write-memory-bytes ${r} ${s}`).then((t=>{this.sendResponse(e)}),(t=>{e.body={error:t},this.sendErrorResponse(e,114,`Unable to write memory: ${t.toString()}`)}))}customReadRegistersRequest(e){this.miDebugger.sendCommand("data-list-register-values x").then((t=>{if("done"===t.resultRecords.resultClass){const s=t.resultRecords.results[0][1];e.body=s.map((e=>{const t={};return e.forEach((e=>{t[e[0]]=e[1]})),t}))}else e.body={error:"Unable to parse response"};this.sendResponse(e)}),(t=>{e.body={error:t},this.sendErrorResponse(e,115,`Unable to read registers: ${t.toString()}`)}))}customReadRegisterListRequest(e){this.miDebugger.sendCommand("data-list-register-names").then((t=>{if("done"===t.resultRecords.resultClass){let s;t.resultRecords.results.forEach((e=>{"register-names"===e[0]&&(s=e[1])})),e.body=s}else e.body={error:t.resultRecords.results};this.sendResponse(e)}),(t=>{e.body={error:t},this.sendErrorResponse(e,116,`Unable to read register list: ${t.toString()}`)}))}disconnectRequest(e,t){this.miDebugger&&(this.attached?this.miDebugger.detach():this.miDebugger.stop(!0)),this.sendResponse(e)}terminateRequest(e,t){this.miDebugger&&this.miDebugger.stop(),this.sendResponse(e)}restartRequest(e,t){const s=()=>{this.miDebugger.restart(['interpreter-exec console "pio_restart_target"']).then((t=>{this.sendResponse(e),(0,o.setTimeout)((()=>{this.stopped=!0,this.stoppedReason="restart",this.sendEvent(new a.ContinuedEvent(this.currentThreadId,!0)),this.sendEvent(new d.StoppedEvent("restart",this.currentThreadId,!0))}),50)}),(t=>{this.sendErrorResponse(e,6,`Could not restart: ${t}`)}))};this.stopped?s():(this.miDebugger.once("generic-stopped",s),this.miDebugger.sendCommand("exec-interrupt"))}handleAdapterOutput(e){this.sendEvent(new d.AdapterOutputEvent(e,"out"))}handleMsg(e,t){"target"===e&&(e="stdout"),"log"===e&&(e="stderr"),this.sendEvent(new a.OutputEvent(t,e))}handleRunning(e){this.stopped=!1,this.sendEvent(new a.ContinuedEvent(this.currentThreadId,!0)),this.sendEvent(new y(this.currentThreadId,!0))}handleBreakpoint(e){const t=parseInt(e.record("thread-id")||this.currentThreadId);this.stopped=!0,this.stoppedReason="breakpoint",this.sendEvent(new d.StoppedEvent("breakpoint",t,!0)),this.sendEvent(new v("breakpoint",t))}handleBreak(e){this.stopped=!0,this.stoppedReason="step",this.sendEvent(new d.StoppedEvent("step",this.currentThreadId,!0)),this.sendEvent(new v("step",this.currentThreadId))}handlePause(e){this.stopped=!0,this.stoppedReason="user request",this.sendEvent(new d.StoppedEvent("user request",this.currentThreadId,!0)),this.sendEvent(new v("user request",this.currentThreadId))}handleThreadCreated(e){this.sendEvent(new a.ThreadEvent("started",e.threadId))}handleThreadExited(e){this.sendEvent(new a.ThreadEvent("exited",e.threadId))}handleThreadSelected(e){this.currentThreadId=e.threadId,this.sendEvent(new a.ThreadEvent("selected",e.threadId))}stopEvent(e){this.started||(this.crashed=!0),this.quit||(this.stopped=!0,this.stoppedReason="exception",this.sendEvent(new d.StoppedEvent("exception",this.currentThreadId,!0)),this.sendEvent(new v("exception",this.currentThreadId)))}quitEvent(){this.quit=!0,this.sendEvent(new a.TerminatedEvent)}launchError(e){this.handleMsg("stderr",`Could not start debugger process > ${e.toString()}\n`),this.quitEvent()}setFunctionBreakPointsRequest(e,t){if(!t.breakpoints||!t.breakpoints.length)return;const s=s=>r(this,void 0,void 0,(function*(){const r=[];t.breakpoints.forEach((e=>{r.push(this.miDebugger.addBreakPoint({raw:e.name,condition:e.condition,countCondition:e.hitCondition}))}));try{const t=yield Promise.all(r),s=[];t.forEach((e=>{e[0]&&s.push({line:e[1].line})})),e.body={breakpoints:s},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,10,t.toString())}s&&(yield this.miDebugger.sendCommand("exec-continue"))})),i=()=>r(this,void 0,void 0,(function*(){this.stopped?yield s(!1):(this.miDebugger.sendCommand("exec-interrupt"),this.miDebugger.once("generic-stopped",(()=>{s(!0)})))}));this.debugReady?i():this.miDebugger.once("debug-ready",i)}setBreakPointsRequest(e,t){const s=s=>r(this,void 0,void 0,(function*(){this.debugReady=!0;const r=(this.breakpointMap.get(t.source.path)||[]).map((e=>e.number));try{yield this.miDebugger.removeBreakpoints(r),this.breakpointMap.set(t.source.path,[]);const s=[],i=decodeURIComponent(t.source.path);if(i.startsWith("disassembly:/")){const e=(0,l.parseQuery)(i.substr(i.indexOf("?"))),r=yield this.getDisassemblyForFunction(e.func,e.file);t.breakpoints.forEach((e=>{if(e.line<=r.instructions.length){const i=r.instructions[e.line-1];s.push(this.miDebugger.addBreakPoint({file:t.source.path,line:e.line,condition:e.condition,countCondition:e.hitCondition,raw:i.address}))}}))}else t.breakpoints.forEach((e=>{s.push(this.miDebugger.addBreakPoint({file:t.source.path,line:e.line,condition:e.condition,countCondition:e.hitCondition}))}));const n=(yield Promise.all(s)).filter((e=>null!==e));e.body={breakpoints:n.map((e=>({line:e.line,id:e.number,verified:!0})))},this.breakpointMap.set(t.source.path,n),this.sendResponse(e)}catch(t){this.sendErrorResponse(e,9,t.toString())}s&&(yield this.miDebugger.sendCommand("exec-continue"))})),i=()=>r(this,void 0,void 0,(function*(){this.stopped?yield s(!1):(yield this.miDebugger.sendCommand("exec-interrupt"),this.miDebugger.once("generic-stopped",(()=>{s(!0)})))}));this.debugReady?i():this.miDebugger.once("debug-ready",i)}threadsRequest(e){return r(this,void 0,void 0,(function*(){if(!this.stopped)return e.body={threads:[]},void this.sendResponse(e);try{const t=yield this.miDebugger.sendCommand("thread-list-ids"),s=t.result("thread-ids").map((e=>parseInt(e[1]))),r=t.result("current-thread-id");r?this.currentThreadId=parseInt(r):(yield this.miDebugger.sendCommand(`thread-select ${s[0]}`),this.currentThreadId=s[0]);const i=(yield Promise.all(s.map((e=>this.miDebugger.sendCommand(`thread-info ${e}`))))).map((e=>{let t=e.result("threads");if(1===t.length){t=t[0];const e=parseInt(p.MINode.valueOf(t,"id")),s=p.MINode.valueOf(t,"target-id"),r=p.MINode.valueOf(t,"details");return new a.Thread(e,r||s)}return null})).filter((e=>null!==e));e.body={threads:i},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,1,`Unable to get thread information: ${t}`)}}))}stackTraceRequest(e,t){return r(this,void 0,void 0,(function*(){try{const s=yield this.miDebugger.getStack(t.threadId,t.startFrame,t.levels),r=[];for(const e of s){const s=65535&(t.threadId<<8|255&e.level),i=e.file;let n=this.forceDisassembly||!i;if(n||(n=!(yield this.checkFileExists(i))),!n&&this.activeEditorPath&&this.activeEditorPath.startsWith("disassembly://")){const t=this.symbolTable.getFunctionByName(e.function,e.fileName);t&&(0,l.encodeDisassembly)(t.name,t.file)===this.activeEditorPath&&(n=!0)}try{if(n){const t=yield this.getDisassemblyForFunction(e.function,e.fileName);let i=-1;if(t.instructions.forEach(((t,s)=>{t.address===e.address&&(i=s+1)})),-1!==i){const n=(0,l.encodeDisassembly)(t.name,t.file);r.push(new a.StackFrame(s,`${e.function}@${e.address}`,new a.Source(t.name,n),i,0))}else r.push(new a.StackFrame(s,e.function+"@"+e.address,null,e.line,0))}else r.push(new a.StackFrame(s,e.function+"@"+e.address,new a.Source(e.fileName,i),e.line,0))}catch(t){r.push(new a.StackFrame(s,e.function+"@"+e.address,null,e.line,0))}}e.body={stackFrames:r},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,12,`Failed to get Stack Trace: ${t.toString()}`)}}))}configurationDoneRequest(e,t){this.sendResponse(e)}scopesRequest(e,t){const s=new Array;s.push(new a.Scope("Local",parseInt(t.frameId),!1)),s.push(new a.Scope("Global",254,!1)),s.push(new a.Scope("Static",f+parseInt(t.frameId),!1)),e.body={scopes:s},this.sendResponse(e)}createVariable(e,t){return t?this.variableHandles.create(new b(e,t)):this.variableHandles.create(e)}findOrCreateVariable(e){let t;return this.variableHandlesReverse.hasOwnProperty(e.name)?t=this.variableHandlesReverse[e.name]:(t=this.createVariable(e),this.variableHandlesReverse[e.name]=t),e.isCompound()?t:0}getVarObjByName(e,t){return r(this,void 0,void 0,(function*(){let s;try{(yield this.miDebugger.varUpdate(t)).result("changelist").forEach((e=>{const t=p.MINode.valueOf(e,"name"),s=this.variableHandlesReverse[t];this.variableHandles.get(s).applyChanges(e)}));const e=this.variableHandlesReverse[t];s=this.variableHandles.get(e)}catch(r){if(!(r instanceof u.MIError&&"Variable object not found"===r.message))throw r;{s=yield this.miDebugger.varCreate(e,t);const r=this.findOrCreateVariable(s);s.exp=e,s.id=r}}return s}))}stackVariablesRequest(e,t,s,i){return r(this,void 0,void 0,(function*(){const r=[];let i;try{i=yield this.miDebugger.getStackVariables(e,t);for(const e of i)try{const t=`var_local_${e.name}`,s=yield this.getVarObjByName(e.name,t);r.push(s.toProtocolVariable())}catch(t){r.push({name:e.name,value:`<${t}>`,variablesReference:0})}s.body={variables:r},this.sendResponse(s)}catch(e){this.sendErrorResponse(s,1,`Could not expand variable: ${e}`)}}))}globalVariablesRequest(e,t){return r(this,void 0,void 0,(function*(){const t=this.symbolTable.getGlobalVariables(),s=[];try{for(const e of t){const t=`var_global_${e.name}`,r=yield this.getVarObjByName(e.name,t);s.push(r.toProtocolVariable())}e.body={variables:s},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,1,`Could not get global variable information: ${t}`)}}))}staticVariablesRequest(e,t,s,i){return r(this,void 0,void 0,(function*(){const r=[];try{const i=(yield this.miDebugger.getFrame(e,t)).fileName,n=this.symbolTable.getStaticVariables(i);for(const e of n){const t=`var_static_${i}_${e.name}`,s=yield this.getVarObjByName(e.name,t);r.push(s.toProtocolVariable())}s.body={variables:r},this.sendResponse(s)}catch(e){this.sendErrorResponse(s,1,`Could not get global variable information: ${e}`)}}))}variableMembersRequest(e,t,s){return r(this,void 0,void 0,(function*(){let s;try{s=yield this.miDebugger.evalExpression(JSON.stringify(e));try{let i=(0,h.expandValue)(this.createVariable.bind(this),s.result("value"),e,s);i?("string"==typeof i[0]&&(i=[{name:"",value:(r=i,"object"==typeof r?void 0!==r.length?r.join(", "):JSON.stringify(r):r),variablesReference:0}]),t.body={variables:i},this.sendResponse(t)):this.sendErrorResponse(t,2,"Could not expand variable")}catch(e){this.sendErrorResponse(t,2,`Could not expand variable: ${e}`)}}catch(e){this.sendErrorResponse(t,1,`Could not expand variable: ${e}`)}var r}))}variablesRequest(e,t){return r(this,void 0,void 0,(function*(){let s;if(254===t.variablesReference)return this.globalVariablesRequest(e,t);if(t.variablesReference>=256&&t.variablesReference>>8;return this.stackVariablesRequest(r,s,e,t)}if(t.variablesReference>=f&&t.variablesReference<=g){const s=255&t.variablesReference,r=(65280&t.variablesReference)>>>8;return this.staticVariablesRequest(r,s,e,t)}if(s=this.variableHandles.get(t.variablesReference),"string"==typeof s)return this.variableMembersRequest(s,e,t);if("object"==typeof s)if(s instanceof u.VariableObject){const t=s;let r;try{r=yield this.miDebugger.varListChildren(s.name);const i=r.map((e=>{const s=this.findOrCreateVariable(e);return e.id=s,/^\d+$/.test(e.exp)?e.fullExp=`${t.fullExp||t.exp}[${e.exp}]`:e.fullExp=`${t.fullExp||t.exp}.${e.exp}`,e.toProtocolVariable()}));e.body={variables:i},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,1,`Could not expand variable: ${t}`)}}else if(s instanceof b){const t=s;if(t.options.arg){const s=[];let i=!0,n=0;const o=()=>{e.body={variables:s},this.sendResponse(e)},a=()=>r(this,void 0,void 0,(function*(){const r=yield this.miDebugger.evalExpression(JSON.stringify(`${t.name}+${n})`));try{const d=(0,h.expandValue)(this.createVariable.bind(this),r.result("value"),t.name,r);if(d)if("string"==typeof d){if(""===d){if(!i)return o();i=!1}else if('"'!==d[0])return s.push({name:"[err]",value:d,variablesReference:0}),o();s.push({name:`[${n++}]`,value:d,variablesReference:0}),a()}else s.push({name:"[err]",value:d,variablesReference:0}),o();else this.sendErrorResponse(e,15,"Could not expand variable")}catch(t){this.sendErrorResponse(e,14,`Could not expand variable: ${t}`)}}));a()}else this.sendErrorResponse(e,13,`Unimplemented variable request options: ${JSON.stringify(t.options)}`)}else e.body={variables:s},this.sendResponse(e);else e.body={variables:[]},this.sendResponse(e)}))}evaluateRequest(e,t){return r(this,void 0,void 0,(function*(){if("watch"===t.context)try{const s=`watch_${i.createHash("md5").update(t.expression).digest("hex")}}`,r=yield this.getVarObjByName(t.expression,s);e.body={result:r.value,variablesReference:r.id},this.sendResponse(e)}catch(t){e.body={result:`<${t.toString()}>`,variablesReference:0},this.sendErrorResponse(e,7,t.toString())}else if("hover"===t.context)try{const s=yield this.miDebugger.evalExpression(t.expression);e.body={variablesReference:0,result:s.result("value")},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,7,t.toString())}else this.miDebugger.sendUserInput(t.expression).then((t=>{e.body=void 0===t?{result:"",variablesReference:0}:{result:JSON.stringify(t),variablesReference:0},this.sendResponse(e)}),(t=>{this.sendErrorResponse(e,8,t.toString())}))}))}setVariableRequest(e,t){return r(this,void 0,void 0,(function*(){try{let s=t.name;const r=this.variableHandles.get(t.variablesReference);r?s=`${r.name}.${t.name}`:254===t.variablesReference?s=`var_global_${t.name}`:t.variablesReference>=256&&t.variablesReference=f&&t.variablesReference<=g&&(s=`var_static_${t.name}`);const i=yield this.miDebugger.varAssign(s,t.value);e.body={value:i.result("value")},this.sendResponse(e)}catch(t){this.sendErrorResponse(e,11,`Could not update variable: ${t}`)}}))}pauseRequest(e,t){this.miDebugger.interrupt(t.threadId).then((t=>{this.sendResponse(e)}),(t=>{this.sendErrorResponse(e,3,`Could not pause: ${t}`)}))}continueRequest(e,t){this.miDebugger.continue(t.threadId).then((t=>{e.body={allThreadsContinued:!0},this.sendResponse(e)}),(t=>{this.sendErrorResponse(e,2,`Could not continue: ${t}`)}))}stepInRequest(e,t){return r(this,void 0,void 0,(function*(){try{let s=this.forceDisassembly;if(!s){const e=yield this.miDebugger.getFrame(t.threadId,0);if(s=!(yield this.checkFileExists(e.file)),this.activeEditorPath&&this.activeEditorPath.startsWith("disassembly://")){const t=this.symbolTable.getFunctionByName(e.function,e.fileName);(0,l.encodeDisassembly)(t.name,t.file)===this.activeEditorPath&&(s=!0)}}yield this.miDebugger.step(t.threadId,s),this.sendResponse(e)}catch(t){this.sendErrorResponse(e,6,`Could not step over: ${t}`)}}))}stepOutRequest(e,t){this.miDebugger.stepOut(t.threadId).then((t=>{this.sendResponse(e)}),(t=>{this.sendErrorResponse(e,5,`Could not step out: ${t}`)}))}nextRequest(e,t){return r(this,void 0,void 0,(function*(){try{let s=this.forceDisassembly;if(!s){const e=yield this.miDebugger.getFrame(t.threadId,0);if(s=!(yield this.checkFileExists(e.file)),this.activeEditorPath&&this.activeEditorPath.startsWith("disassembly://")){const t=this.symbolTable.getFunctionByName(e.function,e.fileName);(0,l.encodeDisassembly)(t.name,t.file)===this.activeEditorPath&&(s=!0)}}yield this.miDebugger.next(t.threadId,s),this.sendResponse(e)}catch(t){this.sendErrorResponse(e,6,`Could not step over: ${t}`)}}))}checkFileExists(e){return e?this.fileExistsCache.has(e)?Promise.resolve(this.fileExistsCache.get(e)):new Promise(((t,s)=>{n.exists(e,(s=>{this.fileExistsCache.set(e,s),t(s)}))})):Promise.resolve(!1)}}t.GDBDebugSession=R,a.DebugSession.run(R)},504:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){return new(s||(s=Promise))((function(i,n){function o(e){try{d(r.next(e))}catch(e){n(e)}}function a(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}d((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.MI2=t.escape=void 0;const i=s(81),n=s(361),o=s(28),a=s(398);function d(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}t.escape=d;const l=/^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/,u=/(?:\d*|undefined)\(gdb\)/,h=/\d+/;function c(e){return!l.exec(e)}class p extends n.EventEmitter{constructor(e,t){super(),this.application=e,this.args=t,this.currentToken=1,this.handlers={},this.debugReadyFired=!1}connect(e,t){return new Promise(((s,r)=>{const n=[...this.args],o=Object.create(process.env);process.env.PLATFORMIO_PATH&&(o.PATH=process.env.PLATFORMIO_PATH,o.Path=process.env.PLATFORMIO_PATH),this.process=i.spawn(this.application,n,{cwd:e,env:o,shell:"win32"===process.platform}),this.process.stdout.on("data",this.stdout.bind(this)),this.process.stderr.on("data",this.stderr.bind(this)),this.process.on("exit",(()=>{this.emit("quit")}).bind(this)),this.process.on("error",(e=>{this.emit("launcherror",e)}).bind(this));const a=[this.sendCommand("gdb-set target-async on",!0),...t.map((e=>this.sendCommand(e)))];Promise.all(a).then((()=>{s(!0)}),r)}))}stdout(e){this.buffer+="string"==typeof e?e:e.toString("utf8");const t=this.buffer.lastIndexOf("\n");-1!==t&&(this.onOutput(this.buffer.substr(0,t)),this.buffer=this.buffer.substr(t+1)),this.buffer.length&&this.onOutputPartial(this.buffer)&&(this.buffer="")}stderr(e){this.errbuf+="string"==typeof e?e:e.toString("utf8");const t=this.errbuf.lastIndexOf("\n");-1!==t&&(this.onOutputStderr(this.errbuf.substr(0,t)),this.errbuf=this.errbuf.substr(t+1)),this.errbuf.length&&(this.logNoNewLine("stderr",this.errbuf),this.errbuf="")}onOutputStderr(e){(e=e.split("\n")).forEach((e=>{this.log("stderr",e)}))}onOutputPartial(e){return!!c(e)&&(this.logNoNewLine("stdout",e),!0)}onOutput(e){(e=e.split("\n")).forEach((e=>{if(c(e))u.exec(e)||this.log("stdout",e);else{const t=(0,a.parseMI)(e);this.debugOutput&&this.log("log","GDB -> App: "+JSON.stringify(t));let s=!1;void 0!==t.token&&this.handlers[t.token]&&(this.handlers[t.token](t),delete this.handlers[t.token],s=!0),!s&&t.resultRecords&&"error"===t.resultRecords.resultClass&&this.log("stderr",t.result("msg")||e),t.outOfBandRecord&&(t.outOfBandRecord.forEach((e=>{if(e.isStream)e.content.includes("PlatformIO: Initialization completed")&&(this.debugReadyTimeout=setTimeout((()=>{this.debugReadyFired=!0,this.emit("debug-ready")}),200),this.once("generic-stopped",(()=>{this.debugReadyFired||(clearTimeout(this.debugReadyTimeout),this.emit("debug-ready"))}))),this.log(e.type,e.content);else if("exec"===e.type)if(this.emit("exec-async-output",t),"running"===e.asyncClass)this.emit("running",t);else if("stopped"===e.asyncClass){const e=t.record("reason");"breakpoint-hit"===e?this.emit("breakpoint",t):"end-stepping-range"===e?this.emit("step-end",t):"function-finished"===e?this.emit("step-out-end",t):"signal-received"===e?this.emit("signal-stop",t):"exited-normally"===e?this.emit("exited-normally",t):"exited"===e?(this.log("stderr","Program exited with code "+t.record("exit-code")),this.emit("exited-normally",t)):(this.debugReadyFired&&this.log("console","Not implemented stop reason (assuming exception): "+e),this.emit("stopped",t)),this.emit("generic-stopped",t)}else this.log("log",JSON.stringify(t));else if("notify"===e.type)if("thread-created"===e.asyncClass){const e=t.result("id"),s=t.result("group-id");this.emit("thread-created",{threadId:e,threadGroupId:s})}else if("thread-exited"===e.asyncClass){const e=t.result("id"),s=t.result("group-id");this.emit("thread-exited",{threadId:e,threadGroupId:s})}else if("thread-selected"===e.asyncClass){const e=t.result("id");this.emit("thread-selected",{threadId:e})}})),s=!0),void 0===t.token&&void 0===t.resultRecords&&0===t.outOfBandRecord.length&&(s=!0),s||this.log("log","Unhandled: "+JSON.stringify(t))}}))}stop(e=!1){if(e){const e=this.process,t=setTimeout((()=>{process.kill(-e.pid)}),1e3);this.process.on("exit",(e=>{clearTimeout(t)}))}this.sendRaw("-gdb-exit")}detach(){const e=this.process,t=setTimeout((()=>{process.kill(-e.pid)}),1e3);this.process.on("exit",(e=>{clearTimeout(t)})),this.sendRaw("-target-detach")}interrupt(e){return new Promise(((t,s)=>{this.sendCommand(`exec-interrupt --thread ${e}`).then((e=>{t("done"===e.resultRecords.resultClass)}),s)}))}continue(e){return new Promise(((t,s)=>{this.sendCommand(`exec-continue --thread ${e}`).then((e=>{t("running"===e.resultRecords.resultClass)}),s)}))}next(e,t){return new Promise(((s,r)=>{const i=t?"exec-next-instruction":"exec-next";this.sendCommand(`${i} --thread ${e}`).then((e=>{s("running"===e.resultRecords.resultClass)}),r)}))}step(e,t){return new Promise(((s,r)=>{const i=t?"exec-step-instruction":"exec-step";this.sendCommand(`${i} --thread ${e}`).then((e=>{s("running"===e.resultRecords.resultClass)}),r)}))}stepOut(e){return new Promise(((t,s)=>{this.sendCommand(`exec-finish --thread ${e}`).then((e=>{t("running"===e.resultRecords.resultClass)}),s)}))}restart(e){return this._sendCommandSequence(e)}_sendCommandSequence(e){return new Promise(((t,s)=>{const r=(e=>{0===e.length&&t(!0);const i=e[0];this.sendCommand(i).then((t=>{r(e.slice(1))}),s)}).bind(this);r(e)}))}changeVariable(e,t){return this.sendCommand("gdb-set var "+e+"="+t)}setBreakPointCondition(e,t){return this.sendCommand("break-condition "+e+" "+t)}addBreakPoint(e){return new Promise(((t,s)=>{let r="";if(e.countCondition)if(">"===e.countCondition[0])r+="-i "+h.exec(e.countCondition.substr(1))[0]+" ";else{const t=h.exec(e.countCondition)[0];t.length!==e.countCondition.length?(this.log("stderr","Unsupported break count expression: '"+e.countCondition+"'. Only supports 'X' for breaking once after X times or '>X' for ignoring the first X breaks"),r+="-t "):0!==parseInt(t)&&(r+="-t -i "+parseInt(t)+" ")}e.raw?r+="*"+d(e.raw):r+='"'+d(e.file)+":"+e.line+'"',this.sendCommand(`break-insert ${r}`).then((r=>{if("done"===r.resultRecords.resultClass){const i=parseInt(r.result("bkpt.number"));e.number=i,e.condition?this.setBreakPointCondition(i,e.condition).then((s=>{"done"===s.resultRecords.resultClass?t(e):t(null)}),s):t(e)}else t(null)}),s)}))}removeBreakpoints(e){return new Promise(((t,s)=>{if(0===e.length)t(!0);else{const r="break-delete "+e.join(" ");this.sendCommand(r).then((e=>{t("done"===e.resultRecords.resultClass)}),s)}}))}getFrame(e,t){return new Promise(((s,r)=>{const i=`stack-info-frame --thread ${e} --frame ${t}`;this.sendCommand(i).then((e=>{const t=e.result("frame"),r=a.MINode.valueOf(t,"level"),i=a.MINode.valueOf(t,"addr"),n=a.MINode.valueOf(t,"func"),o=a.MINode.valueOf(t,"file"),d=a.MINode.valueOf(t,"fullname");let l=0;const u=a.MINode.valueOf(t,"line");u&&(l=parseInt(u)),s({address:i,fileName:o,file:d,function:n,level:r,line:l})}),r)}))}getStack(e,t,s){return new Promise(((r,i)=>{this.sendCommand(`stack-list-frames --thread ${e} ${t} ${s}`).then((e=>{const t=e.result("stack"),s=[];t.forEach((e=>{const t=a.MINode.valueOf(e,"@frame.level"),r=a.MINode.valueOf(e,"@frame.addr"),i=a.MINode.valueOf(e,"@frame.func"),n=a.MINode.valueOf(e,"@frame.file"),o=a.MINode.valueOf(e,"@frame.fullname");let d=0;const l=a.MINode.valueOf(e,"@frame.line");l&&(d=parseInt(l));const u=parseInt(a.MINode.valueOf(e,"@frame.from"));s.push({address:r,fileName:n,file:o,function:i||u,level:t,line:d})})),r(s)}),i)}))}getStackVariables(e,t){return r(this,void 0,void 0,(function*(){const s=(yield this.sendCommand(`stack-list-variables --thread ${e} --frame ${t} --simple-values`)).result("variables"),r=[];for(const e of s){const t=a.MINode.valueOf(e,"name"),s=a.MINode.valueOf(e,"value"),i=a.MINode.valueOf(e,"type");r.push({name:t,valueStr:s,type:i,raw:e})}return r}))}examineMemory(e,t){return r(this,void 0,void 0,(function*(){let s="",r=e;for(;t>0;){const e=t>1024?1024:t;s+=(yield this.sendCommand(`data-read-memory-bytes 0x${r.toString(16)} ${e}`)).result("memory[0].contents"),t-=e,r+=e}return s}))}evalExpression(e){return new Promise(((t,s)=>{this.sendCommand("data-evaluate-expression "+e).then((e=>{t(e)}),s)}))}varCreate(e,t="-"){return r(this,void 0,void 0,(function*(){const s=yield this.sendCommand(`var-create ${t} @ "${e}"`);return new o.VariableObject(s.result(""))}))}varEvalExpression(e){return r(this,void 0,void 0,(function*(){return this.sendCommand(`var-evaluate-expression ${e}`)}))}varListChildren(e){return r(this,void 0,void 0,(function*(){return((yield this.sendCommand(`var-list-children --all-values ${e}`)).result("children")||[]).map((e=>new o.VariableObject(e[1])))}))}varUpdate(e="*"){return r(this,void 0,void 0,(function*(){return this.sendCommand(`var-update --all-values ${e}`)}))}varAssign(e,t){return r(this,void 0,void 0,(function*(){return this.sendCommand(`var-assign ${e} ${t}`)}))}logNoNewLine(e,t){this.emit("msg",e,t)}log(e,t){this.emit("msg",e,"\n"===t[t.length-1]?t:t+"\n")}sendUserInput(e){return e.startsWith("-")?this.sendCommand(e.substr(1)):this.sendCommand(`interpreter-exec console "${e}"`)}sendRaw(e){this.printCalls&&this.log("log",e),this.process.stdin.write(e+"\n")}sendCommand(e,t=!1){const s=this.currentToken++;return new Promise(((r,i)=>{this.handlers[s]=s=>{s&&s.resultRecords&&"error"===s.resultRecords.resultClass?t?(this.log("stderr",`WARNING: Error executing command '${e}'`),r(s)):i(new o.MIError(s.result("msg")||"Internal error",e)):r(s)},this.sendRaw(s+"-"+e)}))}isReady(){return!!this.process}}t.MI2=p},398:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.parseMI=t.MINode=void 0;const s=/^[0-7]{3}/;class r{constructor(e,t,s){this.token=e,this.outOfBandRecord=t,this.resultRecords=s}static valueOf(e,t){if(!e)return;const s=/^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/,r=/^\[(\d+)\](?:$|\.)/;if(!(t=t.trim()))return e;let i=e;do{let e=s.exec(t);if(e){if(t=t.substr(e[0].length),!i.length||"string"==typeof i)return;{const t=[];for(const s of i)s[0]===e[1]&&t.push(s[1]);if(t.length>1)i=t;else{if(1!==t.length)return;i=t[0]}}}else if("@"===t[0])i=[i],t=t.substr(1);else{if(e=r.exec(t),!e)return;{t=t.substr(e[0].length);const s=parseInt(e[1]);if(i.length&&"string"!=typeof i&&s>=0&&s{if('"'!==e[0])return"";let t,r=1,i=!0,n=e.substr(1),o=!1;for(;i;)o?o=!1:"\\"===n[0]?o=!0:'"'===n[0]&&(i=!1),n=n.substr(1),r++;try{t=function(e){const t=Buffer.alloc(4*e.length);let r=0;if('"'!==e[0]||'"'!==e[e.length-1])throw new Error("Not a valid string");e=e.slice(1,-1);let i=!1;for(let n=0;n'"'===e[0]?p():"{"===e[0]||"["===e[0]?(()=>{if("{"!==e[0]&&"["!==e[0])return;const t="["===e[0];if("}"===(e=e.substr(1))[0]||"]"===e[0])return e=e.substr(1),[];if(t){let t=m();if(t){const s=[];for(s.push(t);void 0!==(t=f());)s.push(t);return e=e.substr(1),s}}let s=g();if(s){const t=[];for(t.push(s);s=b();)t.push(s);return e=e.substr(1),t}e=(t?"[":"{")+e})():void 0,g=()=>{const t=a.exec(e);if(t)return e=e.substr(t[0].length+1),[t[1],m()]},f=()=>{if(","===e[0])return e=e.substr(1),m()},b=()=>{if(","===e[0])return e=e.substr(1),g()};v=i.exec(e);){if(e=e.substr(v[0].length),v[1]&&void 0===t&&"undefined"!==v[1]&&(t=parseInt(v[1])),v[2]){const t=d.exec(e);e=e.substr(t[1].length);const s={isStream:!1,type:h[v[2]],asyncClass:t[1],output:[]};let r;for(;r=b();)s.output.push(r);l.push(s)}else if(v[3]){const e={isStream:!0,type:c[v[3]],content:p()};l.push(e)}e=e.replace(o,"")}if(v=n.exec(e)){let s;for(e=e.substr(v[0].length),v[1]&&void 0===t&&(t=parseInt(v[1])),u={resultClass:v[2],results:[]};s=b();)u.results.push(s);e=e.replace(o,"")}return new r(t,l||[],u)}},527:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SymbolTable=void 0;const r=s(81),i=s(147),n=s(17),o=s(200),a=/^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/,d=/^_Z[^\d]*(\d+)(.+)$/,l={F:o.SymbolType.Function,f:o.SymbolType.File,O:o.SymbolType.Object," ":o.SymbolType.Normal},u={l:o.SymbolScope.Local,g:o.SymbolScope.Global," ":o.SymbolScope.Neither,"!":o.SymbolScope.Both};t.SymbolTable=class{constructor(e,t){this.toolchainBinDir=e,this.executable=t,this.symbols=[]}loadSymbols(){let e="";if(i.readdirSync(this.toolchainBinDir).forEach((t=>{t.includes("objdump")&&i.existsSync(n.join(this.toolchainBinDir,t))&&(e=n.join(this.toolchainBinDir,t))})),!e)throw new Error('Could not find "objdump" program');const t=r.spawnSync(e,["--syms",this.executable]).stdout.toString().split("\n");let s=null;for(const e of t){const t=e.match(a);if(!t)continue;const r=l[t[8]],i=u[t[2]];let n=t[11].trim(),h=!1;if("d"===t[7]&&"f"===t[8])s=n;else{n.startsWith(".hidden")&&(n=n.substring(7).trim(),h=!0);const e=n.match(d);if(e){if(r!==o.SymbolType.Function)continue;const t=parseInt(e[1]);if(n=e[2].substr(0,t),e[2].length>t){const s=e[2].substr(t).match(/^(\d+)(.+)$/);s&&(n+="::"+s[2].substr(0,parseInt(s[1])))}}}this.symbols.push({address:parseInt(t[1],16),type:r,scope:i,section:t[9].trim(),length:parseInt(t[10],16),name:n,file:i===o.SymbolScope.Local?s:null,instructions:null,hidden:h})}}getFunctionAtAddress(e){const t=this.symbols.filter((t=>t.type===o.SymbolType.Function&&t.address<=e&&t.address+t.length>e));if(t&&0!==t.length)return t[0]}getFunctionSymbols(){return this.symbols.filter((e=>e.type===o.SymbolType.Function))}getGlobalVariables(){return this.symbols.filter((e=>e.type===o.SymbolType.Object&&e.scope===o.SymbolScope.Global))}getStaticVariables(e){return this.symbols.filter((t=>t.type===o.SymbolType.Object&&t.scope===o.SymbolScope.Local&&t.file===e))}getFunctionByName(e,t){let s=this.symbols.filter((s=>s.type===o.SymbolType.Function&&s.scope===o.SymbolScope.Local&&s.name===e&&s.file===t));return 0!==s.length?s[0]:(s=this.symbols.filter((t=>t.type===o.SymbolType.Function&&t.scope!==o.SymbolScope.Local&&t.name===e)),0!==s.length?s[0]:null)}}},200:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SymbolScope=t.SymbolType=t.TelemetryEvent=t.StoppedEvent=t.AdapterOutputEvent=t.NumberFormat=void 0;const r=s(797);var i,n,o;(i=t.NumberFormat||(t.NumberFormat={}))[i.Auto=0]="Auto",i[i.Hexidecimal=1]="Hexidecimal",i[i.Decimal=2]="Decimal",i[i.Binary=3]="Binary";class a extends r.Event{constructor(e,t){super("adapter-output",{content:e,type:t})}}t.AdapterOutputEvent=a;class d extends r.Event{constructor(e,t,s){super("stopped",{reason:e,threadId:t,allThreadsStopped:s})}}t.StoppedEvent=d;class l extends r.Event{constructor(e,t,s,r={}){super("record-event",{category:e,action:t,label:s,parameters:r})}}t.TelemetryEvent=l,(o=t.SymbolType||(t.SymbolType={}))[o.Function=0]="Function",o[o.File=1]="File",o[o.Object=2]="Object",o[o.Normal=3]="Normal",(n=t.SymbolScope||(t.SymbolScope={}))[n.Local=0]="Local",n[n.Global=1]="Global",n[n.Neither=2]="Neither",n[n.Both=3]="Both"},593:(e,t)=>{function s(e,t){let s=0;const r=e+t-1;for(let t=e;t<=r;t++)s=(s|1<>>0;return s}Object.defineProperty(t,"__esModule",{value:!0}),t.encodeDisassembly=t.parseQuery=t.extractBits=t.createMask=t.binaryFormat=t.hexFormat=void 0,t.hexFormat=function(e,t=8,s=!0){let r=e.toString(16);for(;r.length>>0).toString(2);for(;i.length>>t>>>0},t.parseQuery=function(e){const t={},s=("?"===e[0]?e.substr(1):e).split("&");for(const e of s){const s=e.split("=");t[decodeURIComponent(s[0])]=decodeURIComponent(s[1]||"")}return t},t.encodeDisassembly=function(e,t){let s="disassembly:///";return t&&(s+=`${t}:`),s+=`${e}.dbgasm?func=${e}&file=${t||""}`,s}},81:e=>{e.exports=require("child_process")},113:e=>{e.exports=require("crypto")},361:e=>{e.exports=require("events")},147:e=>{e.exports=require("fs")},17:e=>{e.exports=require("path")},512:e=>{e.exports=require("timers")},797:t=>{t.exports=e}},s={};return function e(r){var i=s[r];if(void 0!==i)return i.exports;var n=s[r]={exports:{}};return t[r].call(n.exports,n,n.exports,e),n.exports}(789)})()})); \ No newline at end of file diff --git a/dist/extension.js b/dist/extension.js new file mode 100644 index 0000000..aa33eaf --- /dev/null +++ b/dist/extension.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("copy-paste"),require("vscode"),require("vscode-debugadapter"),require("xml2js")):"function"==typeof define&&define.amd?define("platformio-vscode-debug",["copy-paste","vscode","vscode-debugadapter","xml2js"],t):"object"==typeof exports?exports["platformio-vscode-debug"]=t(require("copy-paste"),require("vscode"),require("vscode-debugadapter"),require("xml2js")):e["platformio-vscode-debug"]=t(e["copy-paste"],e.vscode,e["vscode-debugadapter"],e.xml2js)}(global,(function(e,t,s,r){return(()=>{"use strict";var i={200:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SymbolScope=t.SymbolType=t.TelemetryEvent=t.StoppedEvent=t.AdapterOutputEvent=t.NumberFormat=void 0;const r=s(797);var i,n,o;(i=t.NumberFormat||(t.NumberFormat={}))[i.Auto=0]="Auto",i[i.Hexidecimal=1]="Hexidecimal",i[i.Decimal=2]="Decimal",i[i.Binary=3]="Binary";class a extends r.Event{constructor(e,t){super("adapter-output",{content:e,type:t})}}t.AdapterOutputEvent=a;class d extends r.Event{constructor(e,t,s){super("stopped",{reason:e,threadId:t,allThreadsStopped:s})}}t.StoppedEvent=d;class h extends r.Event{constructor(e,t,s,r={}){super("record-event",{category:e,action:t,label:s,parameters:r})}}t.TelemetryEvent=h,(o=t.SymbolType||(t.SymbolType={}))[o.Function=0]="Function",o[o.File=1]="File",o[o.Object=2]="Object",o[o.Normal=3]="Normal",(n=t.SymbolScope||(t.SymbolScope={}))[n.Local=0]="Local",n[n.Global=1]="Global",n[n.Neither=2]="Neither",n[n.Both=3]="Both"},719:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){return new(s||(s=Promise))((function(i,n){function o(e){try{d(r.next(e))}catch(e){n(e)}}function a(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}d((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.PlatformIODebugConfigurationProvider=void 0;const i=s(100);t.PlatformIODebugConfigurationProvider=class{constructor(){}resolveDebugConfiguration(e,t,s){return r(this,void 0,void 0,(function*(){return t.cwd=e?e.uri.fsPath:i.workspace.rootPath,t}))}}},186:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DisassemblyContentProvider=void 0;const r=s(100),i=s(593);t.DisassemblyContentProvider=class{provideTextDocumentContent(e,t){return new Promise(((t,s)=>{const n=(0,i.parseQuery)(e.query);r.debug.activeDebugSession.customRequest("disassemble",{function:n.func,file:n.file}).then((e=>{const s=e.instructions;let r="";s.forEach((e=>{r+=`${e.address}: ${this.padEnd(15,e.opcodes)} \t${e.instruction}\n`})),t(r)}),(e=>{r.window.showErrorMessage(e.message),s(e.message)}))}))}padEnd(e,t){for(let s=t.length;s{Object.defineProperty(t,"__esModule",{value:!0}),t.DisassemblyTreeProvider=void 0;const r=s(100);t.DisassemblyTreeProvider=class{constructor(){this._onDidChangeTreeData=new r.EventEmitter,this.onDidChangeTreeData=this._onDidChangeTreeData.event,this.forced=!1}refresh(){this._onDidChangeTreeData.fire()}getChildren(e){if(!r.debug.activeDebugSession)return[];const t=new r.TreeItem("Disassemble function");t.command={title:"Disassemble function",command:"platformio-debug.viewDisassembly"};const s="Switch to "+(this.forced?"code":"assembly"),i=new r.TreeItem(s);return i.command={title:s,command:"platformio-debug.setForceDisassembly",arguments:[this.forced?"Auto":"Forced"]},[t,i]}getTreeItem(e){return e}updateForcedState(e){this.forced=e,this.refresh()}debugSessionStarted(){this.refresh()}debugSessionTerminated(){this.updateForcedState(!1)}}},103:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){return new(s||(s=Promise))((function(i,n){function o(e){try{d(r.next(e))}catch(e){n(e)}}function a(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}d((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.deactivate=t.activate=void 0;const i=s(316),n=s(100),o=s(200),a=s(593),d=s(719),h=s(186),l=s(842),u=s(791),c=s(558),m=s(632),p=s(755);class f{constructor(e){this.adapterOutputChannel=null,this.functionSymbols=null,this.context=e,this.registerProvider=new p.RegisterTreeProvider,this.peripheralProvider=new m.PeripheralTreeProvider,this.memoryTreeProvider=new c.MemoryTreeProvider,this.disassemblyTreeProvider=new l.DisassemblyTreeProvider,this.memoryContentProvider=new u.MemoryContentProvider;const t=n.window.createTreeView("platformio-debug.peripherals",{treeDataProvider:this.peripheralProvider});e.subscriptions.push(n.debug.registerDebugConfigurationProvider("platformio-debug",new d.PlatformIODebugConfigurationProvider),t,t.onDidExpandElement(this.peripheralProvider.onDidExpandElement.bind(this.peripheralProvider)),t.onDidCollapseElement(this.peripheralProvider.onDidCollapseElement.bind(this.peripheralProvider)),n.window.registerTreeDataProvider("platformio-debug.registers",this.registerProvider),n.window.registerTreeDataProvider("platformio-debug.memory",this.memoryTreeProvider),n.window.registerTreeDataProvider("platformio-debug.disassembly",this.disassemblyTreeProvider),n.workspace.registerTextDocumentContentProvider("examinememory",this.memoryContentProvider),n.workspace.registerTextDocumentContentProvider("disassembly",new h.DisassemblyContentProvider),n.commands.registerCommand("platformio-debug.peripherals.updateNode",this.peripheralsUpdateNode.bind(this)),n.commands.registerCommand("platformio-debug.peripherals.selectedNode",this.peripheralsSelectedNode.bind(this)),n.commands.registerCommand("platformio-debug.peripherals.copyValue",this.peripheralsCopyValue.bind(this)),n.commands.registerCommand("platformio-debug.peripherals.setFormat",this.peripheralsSetFormat.bind(this)),n.commands.registerCommand("platformio-debug.registers.selectedNode",this.registersSelectedNode.bind(this)),n.commands.registerCommand("platformio-debug.registers.copyValue",this.registersCopyValue.bind(this)),n.commands.registerCommand("platformio-debug.registers.setFormat",this.registersSetFormat.bind(this)),n.commands.registerCommand("platformio-debug.memory.deleteHistoryItem",this.memoryDeleteHistoryItem.bind(this)),n.commands.registerCommand("platformio-debug.memory.clearHistory",this.memoryClearHistory.bind(this)),n.commands.registerCommand("platformio-debug.examineMemory",this.examineMemory.bind(this)),n.commands.registerCommand("platformio-debug.viewDisassembly",this.showDisassembly.bind(this)),n.commands.registerCommand("platformio-debug.setForceDisassembly",this.setForceDisassembly.bind(this)),n.debug.onDidReceiveDebugSessionCustomEvent(this.receivedCustomEvent.bind(this)),n.debug.onDidStartDebugSession(this.debugSessionStarted.bind(this)),n.debug.onDidTerminateDebugSession(this.debugSessionTerminated.bind(this)),n.window.onDidChangeActiveTextEditor(this.activeEditorChanged.bind(this)),n.window.onDidChangeTextEditorSelection((e=>{e&&e.textEditor.document.fileName.endsWith(".dbgmem")&&this.memoryContentProvider.handleSelection(e)})))}isPIODebugSession(){return n.debug.activeDebugSession&&"platformio-debug"===n.debug.activeDebugSession.type}activeEditorChanged(e){if(!e||!this.isPIODebugSession())return;const t=e.document.uri;"file"===t.scheme?n.debug.activeDebugSession.customRequest("set-active-editor",{path:t.path}):"disassembly"===t.scheme&&n.debug.activeDebugSession.customRequest("set-active-editor",{path:`${t.scheme}://${t.authority}${t.path}`})}showDisassembly(){return r(this,void 0,void 0,(function*(){if(this.isPIODebugSession()){if(!this.functionSymbols)try{const e=yield n.debug.activeDebugSession.customRequest("load-function-symbols");this.functionSymbols=e.functionSymbols}catch(e){n.window.showErrorMessage("Unable to load symbol table. Disassembly view unavailable.")}try{const e=yield n.window.showInputBox({placeHolder:"main",ignoreFocusOut:!0,prompt:"Function Name to Disassemble"}),t=this.functionSymbols.filter((t=>t.name===e));let s;if(1===t.length)s=(0,a.encodeDisassembly)(t[0].name,t[0].file);else{if(!(t.length>1))return n.window.showErrorMessage(`No function with name ${e} found.`);{const e=yield n.window.showQuickPick(t.map((e=>({label:e.name,name:e.name,file:e.file,scope:e.scope,description:e.scope===o.SymbolScope.Global?"Global Scope":`Static in ${e.file}`}))),{ignoreFocusOut:!0});s=(0,a.encodeDisassembly)(e.name,e.file)}}s&&n.window.showTextDocument(n.Uri.parse(s))}catch(e){n.window.showErrorMessage("Unable to show disassembly.")}}else n.window.showErrorMessage("No debugging session available")}))}setForceDisassembly(e){const t=e=>{const t="Forced"===e;return this.disassemblyTreeProvider.updateForcedState(t),n.debug.activeDebugSession.customRequest("set-force-disassembly",{force:t})};if(e)return t(e);n.window.showQuickPick([{label:"Auto",description:"Show disassembly for functions when source cannot be located."},{label:"Forced",description:"Always show disassembly for functions."}],{matchOnDescription:!0,ignoreFocusOut:!0}).then((e=>{t(e.label)}),(e=>{}))}memoryDeleteHistoryItem(e){const[t,s]=e.label.split("+");this.memoryTreeProvider.deleteHistory(t,s)}memoryClearHistory(){this.memoryTreeProvider.clearHistory()}examineMemory(e,t){function s(e){return/^0x[0-9a-f]{1,8}$/i.test(e)||/^[0-9]+$/i.test(e)?e:null}if(this.isPIODebugSession())return e&&t?this.showMemoryContent(e,t):void n.window.showInputBox({placeHolder:"Prefix with 0x for hexidecimal format",ignoreFocusOut:!0,prompt:"A start memory address"}).then((e=>{s(e)?n.window.showInputBox({placeHolder:"Prefix with 0x for hexidecimal format",ignoreFocusOut:!0,prompt:"How many bytes to read?"}).then((t=>{s(t)?(this.memoryTreeProvider.pushHistory(e,t),this.showMemoryContent(e,t)):n.window.showErrorMessage("Invalid length entered")}),(e=>{})):n.window.showErrorMessage("Invalid memory address entered")}),(e=>{}));n.window.showErrorMessage("No debugging session available")}showMemoryContent(e,t){n.workspace.openTextDocument(n.Uri.parse(`examinememory:///Memory%20[${e}+${t}].dbgmem?address=${e}&length=${t}×tamp=${(new Date).getTime()}`)).then((e=>{n.window.showTextDocument(e,{viewColumn:2,preview:!1})}),(e=>{n.window.showErrorMessage(`Failed to examine memory: ${e}`)}))}peripheralsUpdateNode(e){e.node.performUpdate().then((e=>{e&&this.peripheralProvider.refresh()}),(e=>{n.window.showErrorMessage(`Unable to update value: ${e.toString()}`)}))}peripheralsSelectedNode(e){e.recordType!==m.RecordType.Field&&(e.expanded=!e.expanded),e.selected().then((e=>{e&&this.peripheralProvider.refresh()}),(e=>{}))}peripheralsCopyValue(e){const t=e.node.getCopyValue();t&&i.copy(t)}peripheralsSetFormat(e){return r(this,void 0,void 0,(function*(){const t=yield n.window.showQuickPick([{label:"Auto",description:"Automatically choose format (Inherits from parent)",value:o.NumberFormat.Auto},{label:"Hex",description:"Format value in hexidecimal",value:o.NumberFormat.Hexidecimal},{label:"Decimal",description:"Format value in decimal",value:o.NumberFormat.Decimal},{label:"Binary",description:"Format value in binary",value:o.NumberFormat.Binary}]);e.node.setFormat(t.value),this.peripheralProvider.refresh()}))}registersSelectedNode(e){e.recordType!==p.RecordType.Field&&(e.expanded=!e.expanded)}registersCopyValue(e){const t=e.node.getCopyValue();t&&i.copy(t)}registersSetFormat(e){return r(this,void 0,void 0,(function*(){const t=yield n.window.showQuickPick([{label:"Auto",description:"Automatically choose format (Inherits from parent)",value:o.NumberFormat.Auto},{label:"Hex",description:"Format value in hexidecimal",value:o.NumberFormat.Hexidecimal},{label:"Decimal",description:"Format value in decimal",value:o.NumberFormat.Decimal},{label:"Binary",description:"Format value in binary",value:o.NumberFormat.Binary}]);e.node.setFormat(t.value),this.registerProvider.refresh()}))}debugSessionStarted(e){"platformio-debug"===e.type&&(this.functionSymbols=null,e.customRequest("get-arguments").then((e=>{this.registerProvider.debugSessionStarted(this.context.workspaceState.get("debugRegistersTreeState")),this.peripheralProvider.debugSessionStarted(e.svdPath,this.context.workspaceState.get("debugPeripheralsTreeState")),this.memoryTreeProvider.debugSessionStarted(this.context.workspaceState.get("debugMemoryTreeState")),this.disassemblyTreeProvider.debugSessionStarted()}),(e=>{console.error(e)})))}debugSessionTerminated(e){"platformio-debug"===e.type&&(this.context.workspaceState.update("debugRegistersTreeState",this.registerProvider.dumpSettings()),this.context.workspaceState.update("debugPeripheralsTreeState",this.peripheralProvider.dumpSettings()),this.context.workspaceState.update("debugMemoryTreeState",this.memoryTreeProvider.dumpSettings()),this.registerProvider.debugSessionTerminated(),this.peripheralProvider.debugSessionTerminated(),this.memoryTreeProvider.debugSessionTerminated(),this.disassemblyTreeProvider.debugSessionTerminated())}receivedCustomEvent(e){if(this.isPIODebugSession())switch(e.event){case"custom-stop":this.receivedStopEvent(e);break;case"custom-continued":this.receivedContinuedEvent(e);break;case"adapter-output":this.receivedAdapterOutput(e);break;case"record-event":this.receivedEvent(e)}}receivedStopEvent(e){this.peripheralProvider.debugStopped(),this.registerProvider.debugStopped(),n.workspace.textDocuments.filter((e=>e.fileName.endsWith(".dbgmem"))).forEach((e=>{this.memoryContentProvider.update(e)}))}receivedContinuedEvent(e){this.peripheralProvider.debugContinued(),this.registerProvider.debugContinued()}receivedEvent(e){}receivedAdapterOutput(e){this.adapterOutputChannel||(this.adapterOutputChannel=n.window.createOutputChannel("Adapter Output"));let t=e.body.content;t.endsWith("\n")||(t+="\n"),this.adapterOutputChannel.append(t)}}t.activate=function(e){return new f(e)},t.deactivate=function(){}},791:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.MemoryContentProvider=void 0;const r=s(100),i=s(593);t.MemoryContentProvider=class{constructor(){this._onDidChange=new r.EventEmitter,this.onDidChange=this._onDidChange.event,this.firstBytePos=10,this.lastBytePos=this.firstBytePos+48-1,this.firstAsciiPos=this.lastBytePos+3,this.lastAsciiPos=this.firstAsciiPos+16,this.smallDecorationType=r.window.createTextEditorDecorationType({borderWidth:"1px",borderStyle:"solid",overviewRulerColor:"blue",overviewRulerLane:r.OverviewRulerLane.Right,light:{borderColor:"darkblue"},dark:{borderColor:"lightblue"}})}provideTextDocumentContent(e){return new Promise(((t,s)=>{const n=(0,i.parseQuery)(e.query),o=n.address.startsWith("0x")?parseInt(n.address.substring(2),16):parseInt(n.address,10),a=n.length.startsWith("0x")?parseInt(n.length.substring(2),16):parseInt(n.length,10);r.debug.activeDebugSession.customRequest("read-memory",{address:o,length:a||32}).then((e=>{const s=e.bytes;let r=o-o%16;const n=o-r;let d="";d+=" Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F \t\n",d+=(0,i.hexFormat)(r,8,!1)+": ";let h="";for(let e=0;e=127&&t<=159?".":String.fromCharCode(s[e]),(o+e)%16==15&&e{r.window.showErrorMessage(`Unable to read memory from ${(0,i.hexFormat)(o,8)} to ${(0,i.hexFormat)(o+a,8)}`),s(e.toString())}))}))}update(e){this._onDidChange.fire(e.uri)}getOffset(e){if(e.line<1||e.character=this.firstBytePos&&e.character<=this.lastBytePos?t+=Math.floor(s/3):e.character>=this.firstAsciiPos&&(t+=e.character-this.firstAsciiPos),t}getPosition(e,t=!1){const s=1+Math.floor(e/16);let i=e%16;return t?i+=this.firstAsciiPos:i=this.firstBytePos+3*i,new r.Position(s,i)}getRanges(e,t,s){const i=this.getPosition(e,s);let n=this.getPosition(t,s);n=new r.Position(n.line,n.character+(s?1:2));const o=[],a=s?this.firstAsciiPos:this.firstBytePos,d=s?this.lastAsciiPos:this.lastBytePos;for(let e=i.line;e<=n.line;++e){const t=new r.Position(e,e===i.line?i.character:a),s=new r.Position(e,e===n.line?n.character:d);o.push(new r.Range(t,s))}return o}handleSelection(e){const t=e.textEditor.document.lineCount;if(e.selections[0].start.line+1===t||e.selections[0].end.line+1===t)return void e.textEditor.setDecorations(this.smallDecorationType,[]);const s=this.getOffset(e.selections[0].start),r=this.getOffset(e.selections[0].end);if(void 0===s||void 0===r)return void e.textEditor.setDecorations(this.smallDecorationType,[]);let i=this.getRanges(s,r,!1);i=i.concat(this.getRanges(s,r,!0)),e.textEditor.setDecorations(this.smallDecorationType,i)}}},558:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.MemoryTreeProvider=void 0;const r=s(100);t.MemoryTreeProvider=class{constructor(){this._onDidChangeTreeData=new r.EventEmitter,this.onDidChangeTreeData=this._onDidChangeTreeData.event,this.history=[]}refresh(){this._onDidChangeTreeData.fire()}dumpSettings(){return this.history}getChildren(e){if(!r.debug.activeDebugSession)return[];if(this.history.length)return this.getHistoryNodes();const t=new r.TreeItem("Enter address...");return t.command={title:"Enter memory address...",command:"platformio-debug.examineMemory"},[t]}getHistoryNodes(){return this.history.map((e=>{const t=new r.TreeItem(e);return t.command={title:`Examine memory at ${e}`,command:"platformio-debug.examineMemory",arguments:e.split("+")},t}))}getTreeItem(e){return e}pushHistory(e,t){const s=`${e}+${t}`;this.history.includes(s)||(this.history.push(s),this.refresh())}deleteHistory(e,t){const s=`${e}+${t}`;this.history.includes(s)&&(this.history=this.history.filter((e=>e!==s)),this.refresh())}clearHistory(){this.history=[],this.refresh()}debugSessionStarted(e){this.history=e||[],this.refresh()}debugSessionTerminated(){this.history=[],this.refresh()}}},632:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){return new(s||(s=Promise))((function(i,n){function o(e){try{d(r.next(e))}catch(e){n(e)}}function a(e){try{d(r.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}d((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.PeripheralTreeProvider=t.FieldNode=t.RegisterNode=t.ClusterNode=t.PeripheralNode=t.BaseNode=t.TreeNode=t.AccessType=t.RecordType=void 0;const i=s(147),n=s(100),o=s(167),a=s(200),d=s(593);var h,l;!function(e){e[e.Peripheral=1]="Peripheral",e[e.Register=2]="Register",e[e.Field=3]="Field",e[e.Cluster=4]="Cluster"}(h=t.RecordType||(t.RecordType={})),function(e){e[e.ReadOnly=1]="ReadOnly",e[e.ReadWrite=2]="ReadWrite",e[e.WriteOnly=3]="WriteOnly"}(l=t.AccessType||(t.AccessType={}));const u={"read-only":l.ReadOnly,"write-only":l.WriteOnly,"read-write":l.ReadWrite,writeOnce:l.WriteOnly,"read-writeOnce":l.ReadWrite};class c extends n.TreeItem{constructor(e,t,s,r){super(e,t),this.label=e,this.collapsibleState=t,this.contextValue=s,this.node=r,this.command={command:"platformio-debug.peripherals.selectedNode",arguments:[r],title:"Selected Node"}}get tooltip(){return(this.node?this.node.description:void 0)||this.label}}t.TreeNode=c;class m{constructor(e){this.recordType=e,this.expanded=!1,this.format=a.NumberFormat.Auto}selected(){return Promise.resolve(!1)}update(){return Promise.resolve(!1)}performUpdate(){return Promise.resolve(!1)}getChildren(){return[]}getTreeNode(){return null}getCopyValue(){return null}setFormat(e){this.format=e}}function p(e){return/^0b([01]+)$/i.test(e)?parseInt(e.substring(2),2):/^0x([0-9a-f]+)$/i.test(e)?parseInt(e.substring(2),16):/^[0-9]+/i.test(e)?parseInt(e,10):/^#[0-1]+/i.test(e)?parseInt(e.substring(1),2):void 0}function f(e,t){if(-1!==e.indexOf(",")){const s=e.split(",").map((e=>e.trim()));if(s.length!==t)throw new Error("dimIndex Element has invalid specification.");return s}if(/^([0-9]+)\-([0-9]+)$/i.test(e)){const s=e.split("-").map((e=>p(e))),r=s[0];if(s[1]-r+1e.offset>t.offset?1:-1))}addChild(e){this.children.push(e),this.children.sort(((e,t)=>e.offset>t.offset?1:-1))}getBytes(e,t){try{return new Uint8Array(this.currentValue.slice(e,e+t))}catch(e){return new Uint8Array(0)}}getAddress(e){return this.baseAddress+e}getFormat(){return this.format}update(){return new Promise(((e,t)=>{this.expanded?n.debug.activeDebugSession.customRequest("read-memory",{address:this.baseAddress,length:this.totalLength>32768?32768:this.totalLength}).then((t=>{this.currentValue=t.bytes,this.children.forEach((e=>e.update())),e(!0)}),(e=>{t(e)})):e(!1)}))}selected(){return this.update()}dumpSettings(){const e=[];return(this.format!==a.NumberFormat.Auto||this.expanded)&&e.push({node:`${this.name}`,expanded:this.expanded,format:this.format}),this.children.forEach((t=>{e.push(...t.dumpSettings(`${this.name}`))})),e}_findByPath(e){if(0===e.length)return this;{const t=this.children.find((t=>t.name===e[0]));return t?t._findByPath(e.slice(1)):null}}}t.PeripheralNode=b;class y extends m{constructor(e,t){super(h.Cluster),this.parent=e,this.name=t.name,this.description=t.description,this.offset=t.addressOffset,this.accessType=t.accessType||l.ReadWrite,this.size=t.size||e.size,this.resetValue=t.resetValue||e.resetValue,this.children=[],this.parent.addChild(this)}getTreeNode(){const e=`${this.name} [${(0,d.hexFormat)(this.offset,0)}]`;return new c(e,this.expanded?n.TreeItemCollapsibleState.Expanded:n.TreeItemCollapsibleState.Collapsed,"cluster",this)}getChildren(){return this.children}setChildren(e){this.children=e.slice(0,e.length),this.children.sort(((e,t)=>e.offset>t.offset?1:-1))}addChild(e){this.children.push(e),this.children.sort(((e,t)=>e.offset>t.offset?1:-1))}getBytes(e,t){return this.parent.getBytes(this.offset+e,t)}getAddress(e){return this.parent.getAddress(this.offset+e)}getFormat(){return this.format!==a.NumberFormat.Auto?this.format:this.parent.getFormat()}update(){return Promise.resolve(!0)}dumpSettings(e){const t=[];return(this.format!==a.NumberFormat.Auto||this.expanded)&&t.push({node:`${e}.${this.name}`,expanded:this.expanded,format:this.format}),this.children.forEach((s=>{t.push(...s.dumpSettings(`${e}.${this.name}`))})),t}_findByPath(e){if(0===e.length)return this;{const t=this.children.find((t=>t.name===e[0]));return t?t._findByPath(e.slice(1)):null}}}t.ClusterNode=y;class v extends m{constructor(e,t){super(h.Register),this.parent=e,this.name=t.name,this.description=t.description,this.offset=t.addressOffset,this.accessType=t.accessType||e.accessType,this.size=t.size||e.size,this.resetValue=void 0!==t.resetValue?t.resetValue:e.resetValue,this.currentValue=this.resetValue,this.hexLength=Math.ceil(this.size/4),this.maxValue=Math.pow(2,this.size),this.binaryRegex=new RegExp(`^0b[01]{1,${this.size}}$`,"i"),this.hexRegex=new RegExp(`^0x[0-9a-f]{1,${this.hexLength}}$`,"i"),this.children=[],this.parent,this.parent.addChild(this)}reset(){this.currentValue=this.resetValue}extractBits(e,t){return(0,d.extractBits)(this.currentValue,e,t)}updateBits(e,t,s){return new Promise(((r,i)=>{const n=Math.pow(2,t);if(s>n)return i(`Value entered is invalid. Maximum value for this field is ${n-1} (${(0,d.hexFormat)(n-1,0)})`);{const n=(0,d.createMask)(e,t),o=s<";else switch(this.getFormat()){case a.NumberFormat.Decimal:t+=` = ${this.currentValue.toString()}`;break;case a.NumberFormat.Binary:t+=` = ${(0,d.binaryFormat)(this.currentValue,4*this.hexLength,!1,!0)}`;break;default:t+=` = ${(0,d.hexFormat)(this.currentValue,this.hexLength)}`}const s=this.children&&this.children.length>0?this.expanded?n.TreeItemCollapsibleState.Expanded:n.TreeItemCollapsibleState.Collapsed:n.TreeItemCollapsibleState.None;return new c(t,s,e,this)}getChildren(){return this.children||[]}setChildren(e){this.children=e.slice(0,e.length),this.children.sort(((e,t)=>e.offset>t.offset?1:-1))}addChild(e){this.children.push(e),this.children.sort(((e,t)=>e.offset>t.offset?1:-1))}getFormat(){return this.format!==a.NumberFormat.Auto?this.format:this.parent.getFormat()}getCopyValue(){switch(this.getFormat()){case a.NumberFormat.Decimal:return this.currentValue.toString();case a.NumberFormat.Binary:return(0,d.binaryFormat)(this.currentValue,4*this.hexLength);default:return(0,d.hexFormat)(this.currentValue,this.hexLength)}}performUpdate(){return new Promise(((e,t)=>{n.window.showInputBox({prompt:"Enter new value: (prefix hex with 0x, binary with 0b)"}).then((s=>{let r;if(s.match(this.hexRegex))r=parseInt(s.substr(2),16);else if(s.match(this.binaryRegex))r=parseInt(s.substr(2),2);else{if(!s.match(/^[0-9]+/))return t("Value entered is not a valid format.");if(r=parseInt(s,10),r>=this.maxValue)return t(`Value entered (${r}) is greater than the maximum value of ${this.maxValue}`)}this.updateValueInternal(r).then(e,t)}))}))}updateValueInternal(e){const t=this.parent.getAddress(this.offset),s=[],r=this.size/8;for(let t=0;t>>=8;let i=r.toString(16);1===i.length&&(i="0"+i),s[t]=i}return new Promise(((e,r)=>{n.debug.activeDebugSession.customRequest("write-memory",{address:t,data:s.join("")}).then((t=>{this.parent.update().then((()=>{}),(()=>{})),e(!0)}),r)}))}update(){const e=this.size/8,t=this.parent.getBytes(this.offset,e),s=Buffer.from(t);switch(e){case 1:this.currentValue=s.readUInt8(0);break;case 2:this.currentValue=s.readUInt16LE(0);break;case 4:this.currentValue=s.readUInt32LE(0);break;default:n.window.showErrorMessage(`Register ${this.name} has invalid size: ${this.size}. Should be 8, 16 or 32.`)}return this.children.forEach((e=>e.update())),Promise.resolve(!0)}dumpSettings(e){const t=[];return(this.format!==a.NumberFormat.Auto||this.expanded)&&t.push({node:`${e}.${this.name}`,expanded:this.expanded,format:this.format}),this.children.forEach((s=>{t.push(...s.dumpSettings(`${e}.${this.name}`))})),t}_findByPath(e){return 0===e.length?this:1===e.length?this.children.find((t=>t.name===e[0])):null}}t.RegisterNode=v;class w extends m{constructor(e,t){if(super(h.Field),this.parent=e,this.name=t.name,this.description=t.description,this.offset=t.offset,this.width=t.width,t.accessType?e.accessType===l.ReadOnly&&t.accessType!==l.ReadOnly?this.accessType=l.ReadOnly:e.accessType===l.WriteOnly&&t.accessType!==l.WriteOnly?this.accessType=l.WriteOnly:this.accessType=t.accessType:this.accessType=e.accessType,t.enumeration){this.enumeration=t.enumeration,this.enumerationMap={},this.enumerationValues=[];for(const e in t.enumeration){const s=t.enumeration[e].name;this.enumerationValues.push(s),this.enumerationMap[s]=e}}this.parent.addChild(this)}getTreeNode(){const e=this.parent.extractBits(this.offset,this.width);let t=null,s=this.name;const r=this.offset;let i="field";if(s+=`[${this.offset+this.width-1}:${r}]`,"reserved"===this.name.toLowerCase())i="field-res";else if(this.accessType===l.WriteOnly)s+=" - ";else{let r="";switch(this.getFormat()){case a.NumberFormat.Decimal:r=e.toString();break;case a.NumberFormat.Binary:r=(0,d.binaryFormat)(e,this.width);break;case a.NumberFormat.Hexidecimal:r=(0,d.hexFormat)(e,Math.ceil(this.width/4),!0);break;default:r=this.width>=4?(0,d.hexFormat)(e,Math.ceil(this.width/4),!0):(0,d.binaryFormat)(e,this.width)}this.enumeration&&this.enumeration[e]?(t=this.enumeration[e],s+=` = ${t.name} (${r})`):s+=` = ${r}`}return this.parent.accessType===l.ReadOnly&&(i="field-ro"),new c(s,n.TreeItemCollapsibleState.None,i,this)}performUpdate(){return new Promise(((e,t)=>{this.enumeration?n.window.showQuickPick(this.enumerationValues).then((s=>{if(void 0===s)return t("Input not selected");const r=this.enumerationMap[s];this.parent.updateBits(this.offset,this.width,r).then(e,t)})):n.window.showInputBox({prompt:"Enter new value: (prefix hex with 0x, binary with 0b)"}).then((s=>{const r=p(s);if(void 0===r)return t("Unable to parse input value.");this.parent.updateBits(this.offset,this.width,r).then(e,t)}))}))}getCopyValue(){const e=this.parent.extractBits(this.offset,this.width);switch(this.getFormat()){case a.NumberFormat.Decimal:return e.toString();case a.NumberFormat.Binary:return(0,d.binaryFormat)(e,this.width);case a.NumberFormat.Hexidecimal:return(0,d.hexFormat)(e,Math.ceil(this.width/4),!0);default:return this.width>=4?(0,d.hexFormat)(e,Math.ceil(this.width/4),!0):(0,d.binaryFormat)(e,this.width)}}getFormat(){return this.format!==a.NumberFormat.Auto?this.format:this.parent.getFormat()}dumpSettings(e){return this.format!==a.NumberFormat.Auto?[{node:`${e}.${this.name}`,format:this.format}]:[]}_findByPath(e){return 0===e.length?this:null}}t.FieldNode=w,t.PeripheralTreeProvider=class{constructor(){this._onDidChangeTreeData=new n.EventEmitter,this.onDidChangeTreeData=this._onDidChangeTreeData.event,this.peripherials=[],this.loaded=!1,this.viewExpanded=!1}refresh(){this._onDidChangeTreeData.fire()}dumpSettings(){const e=[];return this.peripherials.forEach((t=>{e.push(...t.dumpSettings())})),e}_parseFields(e,t){const s=[];return e.map((e=>{let r,i;const n=e.description?e.description[0]:"";if(e.bitOffset&&e.bitWidth)r=p(e.bitOffset[0]),i=p(e.bitWidth[0]);else if(e.bitRange){let t=e.bitRange[0];t=t.substring(1,t.length-1),t=t.split(":");const s=p(t[0]),n=p(t[1]);i=s-n+1,r=n}else{if(!e.msb||!e.lsb)throw new Error(`Unable to parse SVD file: field ${e.name[0]} must have either bitOffset and bitWidth elements, bitRange Element, or msb and lsb elements.`);{const t=p(e.msb[0]),s=p(e.lsb[0]);i=t-s+1,r=s}}let o=null;e.enumeratedValues&&(o={},e.enumeratedValues[0].enumeratedValue.map((e=>{if(e.value&&e.value.length>0){const t=e.name[0],s=e.description?e.description[0]:t,r=p(e.value[0].toLowerCase());o[r]=new g(t,s,r)}})));const a={name:e.name[0],description:n,offset:r,width:i,enumeration:o};if(e.dim){if(!e.dimIncrement)throw new Error(`Unable to parse SVD file: field ${e.name[0]} has dim element, with no dimIncrement element.`);const i=p(e.dim[0]),n=p(e.dimIncrement[0]);let o=[];if(e.dimIndex)o=f(e.dimIndex[0],i);else for(let e=0;e{const r={};if(e.description&&(r.description=e.description[0]),e.access&&(r.accessType=u[e.access[0]]),e.size&&(r.size=p(e.size[0])),e.resetValue&&(r.resetValue=p(e.resetValue[0])),e.dim){if(!e.dimIncrement)throw new Error(`Unable to parse SVD file: register ${e.name[0]} has dim element, with no dimIncrement element.`);const i=p(e.dim[0]),n=p(e.dimIncrement[0]);let o=[];if(e.dimIndex)o=f(e.dimIndex[0],i);else for(let e=0;ee.offsett.offset?1:0)),s}_parseClusters(e,t){const s=[];return e?(e.forEach((e=>{const r={};if(e.description&&(r.description=e.description[0]),e.access&&(r.accessType=u[e.access[0]]),e.size&&(r.size=p(e.size[0])),e.resetValue&&(r.resetValue=p(e.resetValue)),e.dim){if(!e.dimIncrement)throw new Error(`Unable to parse SVD file: cluster ${e.name[0]} has dim element, with no dimIncrement element.`);const i=p(e.dim[0]),n=p(e.dimIncrement[0]);let o=[];if(e.dimIndex)o=f(e.dimIndex[0],i);else for(let e=0;e{i.readFile(e,"utf8",((e,r)=>{if(e)return s(e);o.parseString(r,((e,r)=>{if(e)return s(e);try{const e={},s={accessType:l.ReadWrite,size:32,resetValue:0};r.device.resetValue&&(s.resetValue=p(r.device.resetValue[0])),r.device.size&&(s.size=p(r.device.size[0])),r.device.access&&(s.accessType=u[r.device.access[0]]),r.device.peripherals[0].peripheral.forEach((t=>{const s=t.name[0];e[s]=t}));for(const t in e){const s=e[t];if(s.$&&s.$.derivedFrom){const r=e[s.$.derivedFrom];e[t]=Object.assign(Object.assign({},r),s)}}this.peripherials=[];for(const t in e)this.peripherials.push(this._parsePeripheral(e[t],s));return this.peripherials.sort(((e,t)=>e.groupName>t.groupName?1:e.groupNamet.name?1:e.namee.name===t[0]));return s?s._findByPath(t.slice(1)):null}getTreeItem(e){return e}getChildren(e){return this.viewExpanded=!0,n.debug.activeDebugSession?this.peripherials.length>0?e?e.node.getChildren().map((e=>e.getTreeNode())):this.peripherials.map((e=>e.getTreeNode())):(this.loaded||this._update(),[new c(this.svdPath?"Loading...":"No Information",n.TreeItemCollapsibleState.None,"message",null)]):[]}_load(){return r(this,void 0,void 0,(function*(){if(this.svdPath)return this.loaded=!0,this.peripherials=[],new Promise((e=>{setTimeout((()=>r(this,void 0,void 0,(function*(){try{yield this._loadSVD(this.svdPath),this.initialSettings&&this.initialSettings.forEach((e=>{const t=this._findNodeByPath(e.node);t&&(t.expanded=e.expanded||!1,t.format=e.format)}))}catch(e){this.peripherials=[],n.window.showErrorMessage(`Unable to parse SVD file: ${e.toString()}`)}e(!0)}))),1e3)}))}))}_update(){return r(this,void 0,void 0,(function*(){if(this.viewExpanded){this.loaded||(yield this._load());try{yield Promise.all(this.peripherials.map((e=>e.update())))}catch(e){}this.refresh()}}))}onDidExpandElement(e){e.element.node.expanded=!0,e.element.node.update(),this.refresh()}onDidCollapseElement(e){e.element.node.expanded=!1}debugSessionStarted(e,t){this.peripherials=[],this.loaded=!1,this.svdPath=e,this.initialSettings=t}debugSessionTerminated(){this.peripherials=[],this.loaded=!1,this.refresh()}debugStopped(){return this._update()}debugContinued(){}}},755:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RegisterTreeProvider=t.FieldNode=t.RegisterNode=t.BaseNode=t.TreeNode=t.RecordType=void 0;const r=s(100),i=s(200),n=s(593);var o;!function(e){e[e.Register=0]="Register",e[e.Field=1]="Field"}(o=t.RecordType||(t.RecordType={}));class a extends r.TreeItem{constructor(e,t,s,r){super(e,t),this.label=e,this.collapsibleState=t,this.contextValue=s,this.node=r,this.command={command:"platformio-debug.registers.selectedNode",arguments:[r],title:"Selected Node"}}}t.TreeNode=a;class d{constructor(e){this.recordType=e,this.format=i.NumberFormat.Auto,this.expanded=!1}getChildren(){return[]}getTreeNode(){return null}getCopyValue(){return null}setFormat(e){this.format=e}}t.BaseNode=d;class h extends d{constructor(e,t){super(o.Register),this.name=e,this.index=t,this.name=this.name,"XPSR"===e.toUpperCase()||"CPSR"===e.toUpperCase()?this.fields=[new l("Negative Flag (N)",31,1,this),new l("Zero Flag (Z)",30,1,this),new l("Carry or borrow flag (C)",29,1,this),new l("Overflow Flag (V)",28,1,this),new l("Saturation Flag (Q)",27,1,this),new l("GE",16,4,this),new l("Interrupt Number",0,8,this),new l("ICI/IT",25,2,this),new l("ICI/IT",10,6,this),new l("Thumb State (T)",24,1,this)]:"CONTROL"===e.toUpperCase()&&(this.fields=[new l("FPCA",2,1,this),new l("SPSEL",1,1,this),new l("nPRIV",0,1,this)]),this.currentValue=0}extractBits(e,t){return(0,n.extractBits)(this.currentValue,e,t)}getTreeNode(){let e=`${this.name} = `;switch(this.getFormat()){case i.NumberFormat.Decimal:e+=this.currentValue.toString();break;case i.NumberFormat.Binary:e+=(0,n.binaryFormat)(this.currentValue,32,!1,!0);break;default:e+=(0,n.hexFormat)(this.currentValue,8)}return this.fields&&this.fields.length>0?new a(e,this.expanded?r.TreeItemCollapsibleState.Expanded:r.TreeItemCollapsibleState.Collapsed,"register",this):new a(e,r.TreeItemCollapsibleState.None,"register",this)}getChildren(){return this.fields}setValue(e){this.currentValue=e}getCopyValue(){switch(this.getFormat()){case i.NumberFormat.Decimal:return this.currentValue.toString();case i.NumberFormat.Binary:return(0,n.binaryFormat)(this.currentValue,32);default:return(0,n.hexFormat)(this.currentValue,8)}}getFormat(){return this.format}dumpSettings(){const e=[];return(this.expanded||this.format!==i.NumberFormat.Auto)&&e.push({node:this.name,format:this.format,expanded:this.expanded}),this.fields&&e.push(...this.fields.map((e=>e.dumpSettings())).filter((e=>null!==e))),e}}t.RegisterNode=h;class l extends d{constructor(e,t,s,r){super(o.Field),this.name=e,this.offset=t,this.size=s,this.register=r}getTreeNode(){const e=this.register.extractBits(this.offset,this.size);let t=`${this.name} = `;switch(this.getFormat()){case i.NumberFormat.Decimal:t+=e.toString();break;case i.NumberFormat.Binary:t+=(0,n.binaryFormat)(e,this.size,!1,!0);break;case i.NumberFormat.Hexidecimal:t+=(0,n.hexFormat)(e,Math.ceil(this.size/4),!0);break;default:t+=this.size>=4?(0,n.hexFormat)(e,Math.ceil(this.size/4),!0):(0,n.binaryFormat)(e,this.size,!1,!0)}return new a(t,r.TreeItemCollapsibleState.None,"field",this)}getCopyValue(){const e=this.register.extractBits(this.offset,this.size);switch(this.getFormat()){case i.NumberFormat.Decimal:return e.toString();case i.NumberFormat.Binary:return(0,n.binaryFormat)(e,this.size);case i.NumberFormat.Hexidecimal:return(0,n.hexFormat)(e,Math.ceil(this.size/4),!0);default:return this.size>=4?(0,n.hexFormat)(e,Math.ceil(this.size/4),!0):(0,n.binaryFormat)(e,this.size)}}getFormat(){return this.format===i.NumberFormat.Auto?this.register.getFormat():this.format}dumpSettings(){return this.format!==i.NumberFormat.Auto?{node:`${this.register.name}.${this.name}`,format:this.format}:null}}t.FieldNode=l,t.RegisterTreeProvider=class{constructor(){this._onDidChangeTreeData=new r.EventEmitter,this.onDidChangeTreeData=this._onDidChangeTreeData.event,this.loaded=!1,this.viewExpanded=!1,this.registers=[],this.registerMap={}}refresh(){this._onDidChangeTreeData.fire()}dumpSettings(){const e=[];return this.registers.forEach((t=>{e.push(...t.dumpSettings())})),e}fetchRegisterList(){r.debug.activeDebugSession&&(this.loaded?this._fetchRegisterValues():r.debug.activeDebugSession.customRequest("read-register-list").then((e=>{this.loaded=!0,this.createRegisters(e),this._fetchRegisterValues()})))}_fetchRegisterValues(){r.debug.activeDebugSession.customRequest("read-registers").then((e=>{e.forEach((e=>{const t=parseInt(e.number,10),s=parseInt(e.value,16),r=this.registerMap[t];r&&r.setValue(s)})),this.refresh()}))}getTreeItem(e){return e.node?e.node.getTreeNode():e}createRegisters(e){this.registerMap={},this.registers=[],e.forEach(((e,t)=>{if(e){const s=new h(e,t);this.registers.push(s),this.registerMap[t]=s}})),this.initialSettings&&this.initialSettings.forEach((e=>{if(-1===e.node.indexOf(".")){const t=this.registers.find((t=>t.name===e.node));t&&(e.expanded&&(t.expanded=e.expanded),e.format&&t.setFormat(e.format))}else{const[t,s]=e.node.split("."),r=this.registers.find((e=>e.name===t));if(r){const t=r.getChildren().find((e=>e.name===s));t&&e.format&&t.setFormat(e.format)}}})),this.refresh()}updateRegisterValues(e){e.forEach((e=>{this.registerMap[e.number].setValue(e.value)})),this.refresh()}getChildren(e){return this.viewExpanded=!0,r.debug.activeDebugSession?this.registers.length>0?e?e.node.getChildren().map((e=>e.getTreeNode())):this.registers.map((e=>e.getTreeNode())):(this.loaded||setTimeout((()=>this.fetchRegisterList()),1e3),[new a("Loading...",r.TreeItemCollapsibleState.None,"message",null)]):[]}debugSessionTerminated(){this.loaded=!1,this.registers=[],this.registerMap={},this.refresh()}debugSessionStarted(e){this.loaded=!1,this.registers=[],this.registerMap={},this.initialSettings=e}debugStopped(){this.viewExpanded&&this.fetchRegisterList()}debugContinued(){}}},593:(e,t)=>{function s(e,t){let s=0;const r=e+t-1;for(let t=e;t<=r;t++)s=(s|1<>>0;return s}Object.defineProperty(t,"__esModule",{value:!0}),t.encodeDisassembly=t.parseQuery=t.extractBits=t.createMask=t.binaryFormat=t.hexFormat=void 0,t.hexFormat=function(e,t=8,s=!0){let r=e.toString(16);for(;r.length>>0).toString(2);for(;i.length>>t>>>0},t.parseQuery=function(e){const t={},s=("?"===e[0]?e.substr(1):e).split("&");for(const e of s){const s=e.split("=");t[decodeURIComponent(s[0])]=decodeURIComponent(s[1]||"")}return t},t.encodeDisassembly=function(e,t){let s="disassembly:///";return t&&(s+=`${t}:`),s+=`${e}.dbgasm?func=${e}&file=${t||""}`,s}},147:e=>{e.exports=require("fs")},316:t=>{t.exports=e},100:e=>{e.exports=t},797:e=>{e.exports=s},167:e=>{e.exports=r}},n={};return function e(t){var s=n[t];if(void 0!==s)return s.exports;var r=n[t]={exports:{}};return i[t].call(r.exports,r,r.exports,e),r.exports}(103)})()})); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4f46e81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2342 @@ +{ + "name": "platformio-vscode-debug", + "version": "1.4.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "platformio-vscode-debug", + "version": "1.4.1", + "hasInstallScript": true, + "dependencies": { + "copy-paste": "^1.3.0", + "vscode-debugadapter": "1.35.0", + "vscode-debugprotocol": "1.35.0", + "xml2js": "^0.4.23" + }, + "devDependencies": { + "@types/node": "^12.0.0", + "@types/vscode": "~1.44.0", + "ts-loader": "^9.2.6", + "tslint": "^6.1.3", + "typescript": "^4.5.5", + "webpack": "~5.68.0", + "webpack-cli": "~4.9.2" + }, + "engines": { + "vscode": "^1.35.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.44.0.tgz", + "integrity": "sha512-WJZtZlinE3meRdH+I7wTsIhpz/GLhqEQwmPGeh4s1irWLwMzCeTV8WZ+pgPTwrDXoafVUWwo1LiZ9HJVHFlJSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "deprecated": "package has been renamed to acorn-import-attributes", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-paste": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/copy-paste/-/copy-paste-1.5.3.tgz", + "integrity": "sha512-qOnFo+8l8vemGmdcoCiD7gPTefkXEg2rivYE+EBtuKOj754eFivkGhGAM9e/xqShrpuVE11evSxGnHwVAUK1Iw==", + "dependencies": { + "iconv-lite": "^0.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vscode-debugadapter": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.35.0.tgz", + "integrity": "sha512-Au90Iowj6TuD5uDMaTnxOjl/9hQN0Yoky1TV1Cjjr7jPdxTQpALBRW09Y2LzkIXUVICXlAqxWL9zL8BpzI30jg==", + "deprecated": "This package has been renamed to @vscode/debugadapter, please update to the new name", + "license": "MIT", + "dependencies": { + "mkdirp": "^0.5.1", + "vscode-debugprotocol": "1.35.0" + } + }, + "node_modules/vscode-debugprotocol": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz", + "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==", + "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name", + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.68.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.68.0.tgz", + "integrity": "sha512-zUcqaUO0772UuuW2bzaES2Zjlm/y3kRBQDVFVCge+s2Y8mwuUTdperGaAv65/NtRL/1zanpSJOq/MD8u61vo6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.0", + "@types/estree": "^0.0.50", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.4.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.8.3", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3e3e33e --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "platformio-vscode-debug", + "version": "1.4.1", + "description": "PlatformIO Debugger for VSCode", + "main": "dist/extension.js", + "engines": { + "vscode": "^1.35.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/platformio/platformio-vscode-ide.git" + }, + "scripts": { + "build": "webpack --mode production", + "lint": "tslint ./src/**/*.ts", + "postinstall": "node ./node_modules/@types/vscode/bin/install || true" + }, + "dependencies": { + "copy-paste": "^1.3.0", + "vscode-debugadapter": "1.35.0", + "vscode-debugprotocol": "1.35.0", + "xml2js": "^0.4.23" + }, + "devDependencies": { + "@types/node": "^12.0.0", + "@types/vscode": "~1.44.0", + "ts-loader": "^9.2.6", + "tslint": "^6.1.3", + "typescript": "^4.5.5", + "webpack": "~5.68.0", + "webpack-cli": "~4.9.2" + } +} diff --git a/src/backend/adapter.ts b/src/backend/adapter.ts new file mode 100644 index 0000000..b1a4d74 --- /dev/null +++ b/src/backend/adapter.ts @@ -0,0 +1,1267 @@ +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import { setTimeout } from 'timers'; +import { + DebugSession, + Event, + Handles, + InitializedEvent, + OutputEvent, + TerminatedEvent, + ThreadEvent, + Thread, + StackFrame, + Scope, + Source, + ContinuedEvent, +} from 'vscode-debugadapter'; +import { StoppedEvent, AdapterOutputEvent } from '../common'; +import { hexFormat, encodeDisassembly, parseQuery } from '../utils'; +import { VariableObject, MIError } from './mi2/types'; +import { expandValue } from './expand_value'; +import { MI2 } from './mi2/mi2'; +import { MINode } from './mi_parse'; +import { SymbolTable } from './symbols'; + +class ExtendedVariable { + constructor( + public name: string, + public options: any + ) {} +} + +const STATIC_HANDLES_START = 65536; // 0x10000 +const STATIC_HANDLES_END = 131071; // 0x1FFFF + +class CustomStopEvent extends Event { + constructor(reason: string, threadID: number) { + super('custom-stop', { reason, threadID }); + } +} + +class CustomContinuedEvent extends Event { + constructor(threadID: number, allThreads: boolean = true) { + super('custom-continued', { threadID, allThreads }); + } +} + +export class GDBDebugSession extends DebugSession { + private variableHandles = new Handles(131072); + private variableHandlesReverse: { [name: string]: number } = {}; + private forceDisassembly: boolean = false; + private activeEditorPath: string | null = null; + private currentThreadId: number = 0; + private stopped: boolean = false; + private stoppedReason: string = ''; + private breakpointMap: Map = new Map(); + private fileExistsCache: Map = new Map(); + private miDebugger: MI2; + private args: any; + private quit: boolean; + private attached: boolean; + private started: boolean; + private crashed: boolean; + private debugReady: boolean; + private symbolTable: SymbolTable; + + constructor(debuggerLinesStartAt1?: boolean, isServer?: boolean) { + super(debuggerLinesStartAt1, isServer); + } + + initDebugger(): void { + this.miDebugger.on('launcherror', this.launchError.bind(this)); + this.miDebugger.on('quit', this.quitEvent.bind(this)); + this.miDebugger.on('exited-normally', this.quitEvent.bind(this)); + this.miDebugger.on('stopped', this.stopEvent.bind(this)); + this.miDebugger.on('msg', this.handleMsg.bind(this)); + this.miDebugger.on('breakpoint', this.handleBreakpoint.bind(this)); + this.miDebugger.on('step-end', this.handleBreak.bind(this)); + this.miDebugger.on('step-out-end', this.handleBreak.bind(this)); + this.miDebugger.on('signal-stop', this.handlePause.bind(this)); + this.miDebugger.on('running', this.handleRunning.bind(this)); + this.miDebugger.on('thread-created', this.handleThreadCreated.bind(this)); + this.miDebugger.on('thread-exited', this.handleThreadExited.bind(this)); + this.miDebugger.on('thread-selected', this.handleThreadSelected.bind(this)); + this.sendEvent(new InitializedEvent()); + } + + protected initializeRequest(response: any, args: any): void { + response.body.supportsConditionalBreakpoints = true; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsEvaluateForHovers = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsHitConditionalBreakpoints = true; + response.body.supportsRestartRequest = true; + response.body.supportsSetVariable = true; + response.body.supportsTerminateRequest = true; + response.body.supportsValueFormattingOptions = true; + this.sendResponse(response); + } + + protected launchRequest(response: any, args: any): void { + this.args = args; + this.processLaunchAttachRequest(response, false); + } + + protected attachRequest(response: any, args: any): void { + this.args = args; + this.processLaunchAttachRequest(response, true); + } + + private processLaunchAttachRequest(response: any, isAttach: boolean): void { + this.quit = false; + this.attached = false; + this.started = false; + this.crashed = false; + this.debugReady = false; + this.stopped = false; + this.breakpointMap = new Map(); + this.fileExistsCache = new Map(); + + const pioArgs = ['debug']; + if (this.args.projectEnvName) { + pioArgs.push('-e', this.args.projectEnvName); + } + if (this.args.loadMode) { + pioArgs.push('--load-mode', this.args.loadMode); + } + pioArgs.push('--interface', 'gdb', '--interpreter=mi2', '-q'); + + this.miDebugger = new MI2('platformio', pioArgs); + this.initDebugger(); + this.miDebugger.printCalls = !!this.args.showDevDebugOutput; + this.miDebugger.debugOutput = !!this.args.showDevDebugOutput; + + this.miDebugger.once('debug-ready', () => { + this.debugReady = true; + this.stopped = true; + this.stoppedReason = 'start'; + this.sendEvent(new StoppedEvent('start', this.currentThreadId, true)); + this.sendEvent(new CustomStopEvent('start', this.currentThreadId)); + }); + + this.miDebugger + .connect(this.args.cwd, [ + 'interpreter-exec console "source .pioinit"', + 'enable-pretty-printing', + ]) + .then( + () => { + this.symbolTable = new SymbolTable(this.args.toolchainBinDir, this.args.executable); + try { + this.symbolTable.loadSymbols(); + this.started = true; + this.sendResponse(response); + } catch (err) { + if (this.args.toolchainBinDir) { + this.sendErrorResponse( + response, + 102, + `Failed to load symbols from executable file: ${err.toString()}` + ); + } + } + }, + (err) => { + this.sendErrorResponse(response, 103, `Failed to launch GDB: ${err.toString()}`); + } + ); + } + + protected customRequest(command: string, response: any, args: any): void { + switch (command) { + case 'set-force-disassembly': + response.body = { success: true }; + this.forceDisassembly = args.force; + if (this.stopped) { + this.activeEditorPath = null; + this.sendEvent(new ContinuedEvent(this.currentThreadId, true)); + this.sendEvent(new StoppedEvent(this.stoppedReason, this.currentThreadId, true)); + } + this.sendResponse(response); + break; + + case 'load-function-symbols': + response.body = { functionSymbols: this.symbolTable.getFunctionSymbols() }; + this.sendResponse(response); + break; + + case 'set-active-editor': + this.activeEditorPath = args.path; + response.body = {}; + this.sendResponse(response); + break; + + case 'get-arguments': + response.body = this.args; + this.sendResponse(response); + break; + + case 'read-memory': + this.customReadMemoryRequest(response, args.address, args.length); + break; + + case 'write-memory': + this.customWriteMemoryRequest(response, args.address, args.data); + break; + + case 'read-registers': + this.customReadRegistersRequest(response); + break; + + case 'read-register-list': + this.customReadRegisterListRequest(response); + break; + + case 'disassemble': + this.disassembleRequest(response, args); + break; + + case 'execute-command': + let cmd: string = args.command; + cmd = cmd.startsWith('-') ? cmd.substring(1) : `interpreter-exec console "${cmd}"`; + this.miDebugger.sendCommand(cmd).then( + (result) => { + response.body = result.resultRecords; + this.sendResponse(response); + }, + (err) => { + response.body = err; + this.sendErrorResponse(response, 110, 'Unable to execute command'); + } + ); + break; + + default: + response.body = { error: 'Invalid command.' }; + this.sendResponse(response); + } + } + + protected async disassembleRequest(response: any, args: any): Promise { + if (args.function) { + try { + const result = await this.getDisassemblyForFunction(args.function, args.file); + response.body = { + instructions: result.instructions, + name: result.name, + file: result.file, + address: result.address, + length: result.length, + }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Unable to disassemble: ${err.toString()}`); + } + } else if (args.startAddress) { + try { + let func = this.symbolTable.getFunctionAtAddress(args.startAddress); + if (func) { + func = await this.getDisassemblyForFunction(func.name, func.file); + response.body = { + instructions: func.instructions, + name: func.name, + file: func.file, + address: func.address, + length: func.length, + }; + this.sendResponse(response); + } else { + const instructions = await this.getDisassemblyForAddresses( + args.startAddress, + args.length || 256 + ); + response.body = { instructions }; + this.sendResponse(response); + } + } catch (err) { + this.sendErrorResponse(response, 1, `Unable to disassemble: ${err.toString()}`); + } + } else { + this.sendErrorResponse(response, 1, 'Unable to disassemble; invalid parameters.'); + } + } + + private async getDisassemblyForFunction(functionName: string, file: string): Promise { + const func = this.symbolTable.getFunctionByName(functionName, file); + if (!func) { + throw new Error(`Unable to find function with name ${functionName}.`); + } + if (func.instructions) { + return func; + } + + const startAddress = func.address; + const endAddress = func.address + func.length; + const result = await this.miDebugger.sendCommand( + `data-disassemble -s ${hexFormat(startAddress, 8)} -e ${hexFormat(endAddress, 8)} -- 2` + ); + + const instructions = result.result('asm_insns').map((entry: any) => ({ + address: MINode.valueOf(entry, 'address'), + functionName: MINode.valueOf(entry, 'func-name'), + offset: parseInt(MINode.valueOf(entry, 'offset')), + instruction: MINode.valueOf(entry, 'inst'), + opcodes: MINode.valueOf(entry, 'opcodes'), + })); + + func.instructions = instructions; + return func; + } + + private async getDisassemblyForAddresses(startAddress: number, length: number): Promise { + const endAddress = startAddress + length; + const result = await this.miDebugger.sendCommand( + `data-disassemble -s ${hexFormat(startAddress, 8)} -e ${hexFormat(endAddress, 8)} -- 2` + ); + + return result.result('asm_insns').map((entry: any) => ({ + address: MINode.valueOf(entry, 'address'), + functionName: MINode.valueOf(entry, 'func-name'), + offset: parseInt(MINode.valueOf(entry, 'offset')), + instruction: MINode.valueOf(entry, 'inst'), + opcodes: MINode.valueOf(entry, 'opcodes'), + })); + } + + private customReadMemoryRequest(response: any, address: number, length: number): void { + this.miDebugger.examineMemory(address, length).then( + (data) => { + const bytes = data.match(/[0-9a-f]{2}/g).map((hex: string) => parseInt(hex, 16)); + response.body = { + startAddress: address, + endAddress: address + bytes.length, + bytes, + }; + this.sendResponse(response); + }, + (err) => { + response.body = { error: err }; + this.sendErrorResponse(response, 114, `Unable to read memory: ${err.toString()}`); + } + ); + } + + private customWriteMemoryRequest(response: any, address: number, data: string): void { + const hexAddr = hexFormat(address, 8); + this.miDebugger.sendCommand(`data-write-memory-bytes ${hexAddr} ${data}`).then( + (result) => { + this.sendResponse(response); + }, + (err) => { + response.body = { error: err }; + this.sendErrorResponse(response, 114, `Unable to write memory: ${err.toString()}`); + } + ); + } + + private customReadRegistersRequest(response: any): void { + this.miDebugger.sendCommand('data-list-register-values x').then( + (result) => { + if (result.resultRecords.resultClass === 'done') { + const registers = result.resultRecords.results[0][1]; + response.body = registers.map((reg: any) => { + const obj: any = {}; + reg.forEach((pair: any) => { + obj[pair[0]] = pair[1]; + }); + return obj; + }); + } else { + response.body = { error: 'Unable to parse response' }; + } + this.sendResponse(response); + }, + (err) => { + response.body = { error: err }; + this.sendErrorResponse(response, 115, `Unable to read registers: ${err.toString()}`); + } + ); + } + + private customReadRegisterListRequest(response: any): void { + this.miDebugger.sendCommand('data-list-register-names').then( + (result) => { + if (result.resultRecords.resultClass === 'done') { + let names: string[]; + result.resultRecords.results.forEach((entry: any) => { + if (entry[0] === 'register-names') { + names = entry[1]; + } + }); + response.body = names; + } else { + response.body = { error: result.resultRecords.results }; + } + this.sendResponse(response); + }, + (err) => { + response.body = { error: err }; + this.sendErrorResponse(response, 116, `Unable to read register list: ${err.toString()}`); + } + ); + } + + protected disconnectRequest(response: any, args: any): void { + if (this.miDebugger) { + if (this.attached) { + this.miDebugger.detach(); + } else { + this.miDebugger.stop(true); + } + } + this.sendResponse(response); + } + + protected terminateRequest(response: any, args: any): void { + if (this.miDebugger) { + this.miDebugger.stop(); + } + this.sendResponse(response); + } + + protected restartRequest(response: any, args: any): void { + const doRestart = () => { + this.miDebugger + .restart(['interpreter-exec console "pio_restart_target"']) + .then( + (result) => { + this.sendResponse(response); + setTimeout(() => { + this.stopped = true; + this.stoppedReason = 'restart'; + this.sendEvent(new ContinuedEvent(this.currentThreadId, true)); + this.sendEvent(new StoppedEvent('restart', this.currentThreadId, true)); + }, 50); + }, + (err) => { + this.sendErrorResponse(response, 6, `Could not restart: ${err}`); + } + ); + }; + + if (this.stopped) { + doRestart(); + } else { + this.miDebugger.once('generic-stopped', doRestart); + this.miDebugger.sendCommand('exec-interrupt'); + } + } + + private handleAdapterOutput(text: string): void { + this.sendEvent(new AdapterOutputEvent(text, 'out')); + } + + private handleMsg(type: string, message: string): void { + if (type === 'target') { + type = 'stdout'; + } + if (type === 'log') { + type = 'stderr'; + } + this.sendEvent(new OutputEvent(message, type)); + } + + private handleRunning(info: any): void { + this.stopped = false; + this.sendEvent(new ContinuedEvent(this.currentThreadId, true)); + this.sendEvent(new CustomContinuedEvent(this.currentThreadId, true)); + } + + private handleBreakpoint(info: any): void { + const threadId = parseInt(info.record('thread-id') || this.currentThreadId); + this.stopped = true; + this.stoppedReason = 'breakpoint'; + this.sendEvent(new StoppedEvent('breakpoint', threadId, true)); + this.sendEvent(new CustomStopEvent('breakpoint', threadId)); + } + + private handleBreak(info: any): void { + this.stopped = true; + this.stoppedReason = 'step'; + this.sendEvent(new StoppedEvent('step', this.currentThreadId, true)); + this.sendEvent(new CustomStopEvent('step', this.currentThreadId)); + } + + private handlePause(info: any): void { + this.stopped = true; + this.stoppedReason = 'user request'; + this.sendEvent(new StoppedEvent('user request', this.currentThreadId, true)); + this.sendEvent(new CustomStopEvent('user request', this.currentThreadId)); + } + + private handleThreadCreated(info: any): void { + this.sendEvent(new ThreadEvent('started', info.threadId)); + } + + private handleThreadExited(info: any): void { + this.sendEvent(new ThreadEvent('exited', info.threadId)); + } + + private handleThreadSelected(info: any): void { + this.currentThreadId = info.threadId; + this.sendEvent(new ThreadEvent('selected', info.threadId)); + } + + private stopEvent(info: any): void { + if (!this.started) { + this.crashed = true; + } + if (!this.quit) { + this.stopped = true; + this.stoppedReason = 'exception'; + this.sendEvent(new StoppedEvent('exception', this.currentThreadId, true)); + this.sendEvent(new CustomStopEvent('exception', this.currentThreadId)); + } + } + + private quitEvent(): void { + this.quit = true; + this.sendEvent(new TerminatedEvent()); + } + + private launchError(err: any): void { + this.handleMsg('stderr', `Could not start debugger process > ${err.toString()}\n`); + this.quitEvent(); + } + + protected setFunctionBreakPointsRequest(response: any, args: any): void { + if (!args.breakpoints || !args.breakpoints.length) { + return; + } + + const setBreakpoints = async (shouldContinue: boolean) => { + const promises: Promise[] = []; + args.breakpoints.forEach((bp: any) => { + promises.push( + this.miDebugger.addBreakPoint({ + raw: bp.name, + condition: bp.condition, + countCondition: bp.hitCondition, + }) + ); + }); + + try { + const results = await Promise.all(promises); + const breakpoints: any[] = []; + results.forEach((result) => { + if (result[0]) { + breakpoints.push({ line: result[1].line }); + } + }); + response.body = { breakpoints }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 10, err.toString()); + } + + if (shouldContinue) { + await this.miDebugger.sendCommand('exec-continue'); + } + }; + + const doSet = async () => { + if (this.stopped) { + await setBreakpoints(false); + } else { + this.miDebugger.sendCommand('exec-interrupt'); + this.miDebugger.once('generic-stopped', () => { + setBreakpoints(true); + }); + } + }; + + if (this.debugReady) { + doSet(); + } else { + this.miDebugger.once('debug-ready', doSet); + } + } + + protected setBreakPointsRequest(response: any, args: any): void { + const setBreakpoints = async (shouldContinue: boolean) => { + this.debugReady = true; + const existingBreakpoints = (this.breakpointMap.get(args.source.path) || []).map( + (bp: any) => bp.number + ); + + try { + await this.miDebugger.removeBreakpoints(existingBreakpoints); + this.breakpointMap.set(args.source.path, []); + + const promises: Promise[] = []; + const decodedPath = decodeURIComponent(args.source.path); + + if (decodedPath.startsWith('disassembly:/')) { + const params = parseQuery(decodedPath.substr(decodedPath.indexOf('?'))); + const func = await this.getDisassemblyForFunction(params.func, params.file); + args.breakpoints.forEach((bp: any) => { + if (bp.line <= func.instructions.length) { + const instruction = func.instructions[bp.line - 1]; + promises.push( + this.miDebugger.addBreakPoint({ + file: args.source.path, + line: bp.line, + condition: bp.condition, + countCondition: bp.hitCondition, + raw: instruction.address, + }) + ); + } + }); + } else { + args.breakpoints.forEach((bp: any) => { + promises.push( + this.miDebugger.addBreakPoint({ + file: args.source.path, + line: bp.line, + condition: bp.condition, + countCondition: bp.hitCondition, + }) + ); + }); + } + + const results = (await Promise.all(promises)).filter((r) => r !== null); + response.body = { + breakpoints: results.map((bp: any) => ({ + line: bp.line, + id: bp.number, + verified: true, + })), + }; + this.breakpointMap.set(args.source.path, results); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 9, err.toString()); + } + + if (shouldContinue) { + await this.miDebugger.sendCommand('exec-continue'); + } + }; + + const doSet = async () => { + if (this.stopped) { + await setBreakpoints(false); + } else { + await this.miDebugger.sendCommand('exec-interrupt'); + this.miDebugger.once('generic-stopped', () => { + setBreakpoints(true); + }); + } + }; + + if (this.debugReady) { + doSet(); + } else { + this.miDebugger.once('debug-ready', doSet); + } + } + + protected async threadsRequest(response: any): Promise { + if (!this.stopped) { + response.body = { threads: [] }; + this.sendResponse(response); + return; + } + + try { + const result = await this.miDebugger.sendCommand('thread-list-ids'); + const threadIds: number[] = result.result('thread-ids').map((t: any) => parseInt(t[1])); + const currentThreadId = result.result('current-thread-id'); + + if (currentThreadId) { + this.currentThreadId = parseInt(currentThreadId); + } else { + await this.miDebugger.sendCommand(`thread-select ${threadIds[0]}`); + this.currentThreadId = threadIds[0]; + } + + const threadInfoResults = await Promise.all( + threadIds.map((id) => this.miDebugger.sendCommand(`thread-info ${id}`)) + ); + + const threads = threadInfoResults + .map((info) => { + let thread = info.result('threads'); + if (thread.length === 1) { + thread = thread[0]; + const id = parseInt(MINode.valueOf(thread, 'id')); + const targetId = MINode.valueOf(thread, 'target-id'); + const details = MINode.valueOf(thread, 'details'); + return new Thread(id, details || targetId); + } + return null; + }) + .filter((t) => t !== null); + + response.body = { threads }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Unable to get thread information: ${err}`); + } + } + + protected async stackTraceRequest(response: any, args: any): Promise { + try { + const stack = await this.miDebugger.getStack(args.threadId, args.startFrame, args.levels); + const frames: StackFrame[] = []; + + for (const frame of stack) { + const frameIndex = 0xffff & ((args.threadId << 8) | (0xff & frame.level)); + const filePath = frame.file; + let useDisassembly = this.forceDisassembly || !filePath; + + if (!useDisassembly) { + useDisassembly = !(await this.checkFileExists(filePath)); + } + + if ( + !useDisassembly && + this.activeEditorPath && + this.activeEditorPath.startsWith('disassembly://') + ) { + const func = this.symbolTable.getFunctionByName(frame.function, frame.fileName); + if (func && encodeDisassembly(func.name, func.file) === this.activeEditorPath) { + useDisassembly = true; + } + } + + try { + if (useDisassembly) { + const disassembly = await this.getDisassemblyForFunction( + frame.function, + frame.fileName + ); + let lineNumber = -1; + disassembly.instructions.forEach((instruction: any, index: number) => { + if (instruction.address === frame.address) { + lineNumber = index + 1; + } + }); + + if (lineNumber !== -1) { + const uri = encodeDisassembly(disassembly.name, disassembly.file); + frames.push( + new StackFrame( + frameIndex, + `${frame.function}@${frame.address}`, + new Source(disassembly.name, uri), + lineNumber, + 0 + ) + ); + } else { + frames.push( + new StackFrame( + frameIndex, + frame.function + '@' + frame.address, + null, + frame.line, + 0 + ) + ); + } + } else { + frames.push( + new StackFrame( + frameIndex, + frame.function + '@' + frame.address, + new Source(frame.fileName, filePath), + frame.line, + 0 + ) + ); + } + } catch (err) { + frames.push( + new StackFrame( + frameIndex, + frame.function + '@' + frame.address, + null, + frame.line, + 0 + ) + ); + } + } + + response.body = { stackFrames: frames }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`); + } + } + + protected configurationDoneRequest(response: any, args: any): void { + this.sendResponse(response); + } + + protected scopesRequest(response: any, args: any): void { + const scopes = new Array(); + scopes.push(new Scope('Local', parseInt(args.frameId), false)); + scopes.push(new Scope('Global', 254, false)); + scopes.push(new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId), false)); + response.body = { scopes }; + this.sendResponse(response); + } + + private createVariable(name: string | VariableObject, options?: any): number { + if (options) { + return this.variableHandles.create(new ExtendedVariable(name as string, options)); + } + return this.variableHandles.create(name as any); + } + + private findOrCreateVariable(variable: VariableObject): number { + let id: number; + if (this.variableHandlesReverse.hasOwnProperty(variable.name)) { + id = this.variableHandlesReverse[variable.name]; + } else { + id = this.createVariable(variable); + this.variableHandlesReverse[variable.name] = id; + } + return variable.isCompound() ? id : 0; + } + + private async getVarObjByName(expression: string, name: string): Promise { + let varObj: VariableObject; + + try { + const updateResult = await this.miDebugger.varUpdate(name); + updateResult.result('changelist').forEach((change: any) => { + const changeName = MINode.valueOf(change, 'name'); + const handle = this.variableHandlesReverse[changeName]; + (this.variableHandles.get(handle) as VariableObject).applyChanges(change); + }); + const handle = this.variableHandlesReverse[name]; + varObj = this.variableHandles.get(handle) as VariableObject; + } catch (err) { + if (!(err instanceof MIError && err.message === 'Variable object not found')) { + throw err; + } + varObj = await this.miDebugger.varCreate(expression, name); + const id = this.findOrCreateVariable(varObj); + varObj.exp = expression; + varObj.id = id; + } + + return varObj; + } + + private async stackVariablesRequest( + threadId: number, + frameLevel: number, + response: any, + args: any + ): Promise { + const variables: any[] = []; + try { + const stackVars = await this.miDebugger.getStackVariables(threadId, frameLevel); + for (const stackVar of stackVars) { + try { + const varName = `var_local_${stackVar.name}`; + const varObj = await this.getVarObjByName(stackVar.name, varName); + variables.push(varObj.toProtocolVariable()); + } catch (err) { + variables.push({ + name: stackVar.name, + value: `<${err}>`, + variablesReference: 0, + }); + } + } + response.body = { variables }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } + + private async globalVariablesRequest(response: any, args: any): Promise { + const globalVars = this.symbolTable.getGlobalVariables(); + const variables: any[] = []; + + try { + for (const globalVar of globalVars) { + const varName = `var_global_${globalVar.name}`; + const varObj = await this.getVarObjByName(globalVar.name, varName); + variables.push(varObj.toProtocolVariable()); + } + response.body = { variables }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not get global variable information: ${err}`); + } + } + + private async staticVariablesRequest( + threadId: number, + frameLevel: number, + response: any, + args: any + ): Promise { + const variables: any[] = []; + try { + const frameInfo = await this.miDebugger.getFrame(threadId, frameLevel); + const fileName = frameInfo.fileName; + const staticVars = this.symbolTable.getStaticVariables(fileName); + + for (const staticVar of staticVars) { + const varName = `var_static_${fileName}_${staticVar.name}`; + const varObj = await this.getVarObjByName(staticVar.name, varName); + variables.push(varObj.toProtocolVariable()); + } + response.body = { variables }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not get global variable information: ${err}`); + } + } + + private async variableMembersRequest( + expression: string, + response: any, + args: any + ): Promise { + let evalResult: MINode; + try { + evalResult = await this.miDebugger.evalExpression(JSON.stringify(expression)); + try { + let expanded: any = expandValue( + this.createVariable.bind(this), + evalResult.result('value'), + expression, + evalResult + ); + + if (expanded) { + if (typeof expanded[0] === 'string') { + const formatValue = (v: any): string => { + if (typeof v === 'object') { + if (v.length !== undefined) { + return v.join(', '); + } + return JSON.stringify(v); + } + return v; + }; + expanded = [ + { + name: '', + value: formatValue(expanded), + variablesReference: 0, + }, + ]; + } + response.body = { variables: expanded }; + this.sendResponse(response); + } else { + this.sendErrorResponse(response, 2, 'Could not expand variable'); + } + } catch (err) { + this.sendErrorResponse(response, 2, `Could not expand variable: ${err}`); + } + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } + + protected async variablesRequest(response: any, args: any): Promise { + let varRef: any; + + if (args.variablesReference === 254) { + return this.globalVariablesRequest(response, args); + } + + if (args.variablesReference >= 256 && args.variablesReference < STATIC_HANDLES_START) { + const frameLevel = 0xff & args.variablesReference; + const threadId = (0xff00 & args.variablesReference) >>> 8; + return this.stackVariablesRequest(threadId, frameLevel, response, args); + } + + if ( + args.variablesReference >= STATIC_HANDLES_START && + args.variablesReference <= STATIC_HANDLES_END + ) { + const frameLevel = 0xff & args.variablesReference; + const threadId = (0xff00 & args.variablesReference) >>> 8; + return this.staticVariablesRequest(threadId, frameLevel, response, args); + } + + varRef = this.variableHandles.get(args.variablesReference); + + if (typeof varRef === 'string') { + return this.variableMembersRequest(varRef, response, args); + } + + if (typeof varRef === 'object') { + if (varRef instanceof VariableObject) { + const parent = varRef; + try { + const children = await this.miDebugger.varListChildren(varRef.name); + const variables = children.map((child) => { + const id = this.findOrCreateVariable(child); + child.id = id; + if (/^\d+$/.test(child.exp)) { + child.fullExp = `${parent.fullExp || parent.exp}[${child.exp}]`; + } else { + child.fullExp = `${parent.fullExp || parent.exp}.${child.exp}`; + } + return child.toProtocolVariable(); + }); + response.body = { variables }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } else if (varRef instanceof ExtendedVariable) { + const extVar = varRef; + if (extVar.options.arg) { + const variables: any[] = []; + let firstNull = true; + let index = 0; + + const done = () => { + response.body = { variables }; + this.sendResponse(response); + }; + + const fetchNext = async () => { + const evalResult = await this.miDebugger.evalExpression( + JSON.stringify(`${extVar.name}+${index})`) + ); + try { + const expanded = expandValue( + this.createVariable.bind(this), + evalResult.result('value'), + extVar.name, + evalResult + ); + + if (expanded) { + if (typeof expanded === 'string') { + if (expanded === '') { + if (!firstNull) { + return done(); + } + firstNull = false; + } else if (expanded[0] !== '"') { + variables.push({ + name: '[err]', + value: expanded, + variablesReference: 0, + }); + return done(); + } + variables.push({ + name: `[${index++}]`, + value: expanded, + variablesReference: 0, + }); + fetchNext(); + } else { + variables.push({ + name: '[err]', + value: expanded, + variablesReference: 0, + }); + done(); + } + } else { + this.sendErrorResponse(response, 15, 'Could not expand variable'); + } + } catch (err) { + this.sendErrorResponse(response, 14, `Could not expand variable: ${err}`); + } + }; + + fetchNext(); + } else { + this.sendErrorResponse( + response, + 13, + `Unimplemented variable request options: ${JSON.stringify(extVar.options)}` + ); + } + } else { + response.body = { variables: varRef }; + this.sendResponse(response); + } + } else { + response.body = { variables: [] }; + this.sendResponse(response); + } + } + + protected async evaluateRequest(response: any, args: any): Promise { + if (args.context === 'watch') { + try { + const varName = `watch_${crypto.createHash('md5').update(args.expression).digest('hex')}}`; + const varObj = await this.getVarObjByName(args.expression, varName); + response.body = { + result: varObj.value, + variablesReference: varObj.id, + }; + this.sendResponse(response); + } catch (err) { + response.body = { + result: `<${err.toString()}>`, + variablesReference: 0, + }; + this.sendErrorResponse(response, 7, err.toString()); + } + } else if (args.context === 'hover') { + try { + const result = await this.miDebugger.evalExpression(args.expression); + response.body = { + variablesReference: 0, + result: result.result('value'), + }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 7, err.toString()); + } + } else { + this.miDebugger.sendUserInput(args.expression).then( + (result) => { + response.body = + result === undefined + ? { result: '', variablesReference: 0 } + : { result: JSON.stringify(result), variablesReference: 0 }; + this.sendResponse(response); + }, + (err) => { + this.sendErrorResponse(response, 8, err.toString()); + } + ); + } + } + + protected async setVariableRequest(response: any, args: any): Promise { + try { + let varName = args.name; + const varRef = this.variableHandles.get(args.variablesReference); + + if (varRef) { + varName = `${(varRef as VariableObject).name}.${args.name}`; + } else if (args.variablesReference === 254) { + varName = `var_global_${args.name}`; + } else if (args.variablesReference >= 256 && args.variablesReference < STATIC_HANDLES_START) { + varName = `var_local_${args.name}`; + } else if ( + args.variablesReference >= STATIC_HANDLES_START && + args.variablesReference <= STATIC_HANDLES_END + ) { + varName = `var_static_${args.name}`; + } + + const result = await this.miDebugger.varAssign(varName, args.value); + response.body = { value: result.result('value') }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 11, `Could not update variable: ${err}`); + } + } + + protected pauseRequest(response: any, args: any): void { + this.miDebugger.interrupt(args.threadId).then( + (result) => { + this.sendResponse(response); + }, + (err) => { + this.sendErrorResponse(response, 3, `Could not pause: ${err}`); + } + ); + } + + protected continueRequest(response: any, args: any): void { + this.miDebugger.continue(args.threadId).then( + (result) => { + response.body = { allThreadsContinued: true }; + this.sendResponse(response); + }, + (err) => { + this.sendErrorResponse(response, 2, `Could not continue: ${err}`); + } + ); + } + + protected async stepInRequest(response: any, args: any): Promise { + try { + let useDisassembly = this.forceDisassembly; + if (!useDisassembly) { + const frame = await this.miDebugger.getFrame(args.threadId, 0); + useDisassembly = !(await this.checkFileExists(frame.file)); + + if ( + this.activeEditorPath && + this.activeEditorPath.startsWith('disassembly://') + ) { + const func = this.symbolTable.getFunctionByName(frame.function, frame.fileName); + if (encodeDisassembly(func.name, func.file) === this.activeEditorPath) { + useDisassembly = true; + } + } + } + await this.miDebugger.step(args.threadId, useDisassembly); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 6, `Could not step over: ${err}`); + } + } + + protected stepOutRequest(response: any, args: any): void { + this.miDebugger.stepOut(args.threadId).then( + (result) => { + this.sendResponse(response); + }, + (err) => { + this.sendErrorResponse(response, 5, `Could not step out: ${err}`); + } + ); + } + + protected async nextRequest(response: any, args: any): Promise { + try { + let useDisassembly = this.forceDisassembly; + if (!useDisassembly) { + const frame = await this.miDebugger.getFrame(args.threadId, 0); + useDisassembly = !(await this.checkFileExists(frame.file)); + + if ( + this.activeEditorPath && + this.activeEditorPath.startsWith('disassembly://') + ) { + const func = this.symbolTable.getFunctionByName(frame.function, frame.fileName); + if (encodeDisassembly(func.name, func.file) === this.activeEditorPath) { + useDisassembly = true; + } + } + } + await this.miDebugger.next(args.threadId, useDisassembly); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 6, `Could not step over: ${err}`); + } + } + + private checkFileExists(file: string): Promise { + if (!file) { + return Promise.resolve(false); + } + if (this.fileExistsCache.has(file)) { + return Promise.resolve(this.fileExistsCache.get(file)); + } + return new Promise((resolve, reject) => { + fs.exists(file, (exists) => { + this.fileExistsCache.set(file, exists); + resolve(exists); + }); + }); + } +} + +DebugSession.run(GDBDebugSession); diff --git a/src/backend/expand_value.ts b/src/backend/expand_value.ts new file mode 100644 index 0000000..c4fe735 --- /dev/null +++ b/src/backend/expand_value.ts @@ -0,0 +1,284 @@ +import { MINode } from './mi_parse'; + +const VARIABLE_ASSIGNMENT_REGEX = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*|\[\d+\])\s*=\s*/; +const VARIABLE_NAME_REGEX = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/; +const ANGLE_BRACKET_REGEX = /^\<.+?\>/; +const HEX_STRING_REGEX = /^(0x[0-9a-fA-F]+\s*)"/; +const HEX_REGEX = /^0x[0-9a-fA-F]+/; +const NULL_PTR_REGEX = /^0x0+\b/; +const CHAR_CODE_STRING_REGEX = /^(\d+) ['"]/; +const NUMBER_REGEX = /^\d+(\.\d+)?/; + +export function isExpandable(value: string): number { + value = value.trim(); + if (value.length === 0) { + return 0; + } + if (value.startsWith('{...}')) { + return 2; + } + if (value[0] === '{') { + return 1; + } + if (value.startsWith('true') || value.startsWith('false') || NULL_PTR_REGEX.exec(value) || HEX_STRING_REGEX.exec(value)) { + return 0; + } + if (HEX_REGEX.exec(value)) { + return 2; + } + // Check other patterns but always return 0 for them + CHAR_CODE_STRING_REGEX.exec(value) || NUMBER_REGEX.exec(value) || VARIABLE_NAME_REGEX.exec(value) || ANGLE_BRACKET_REGEX.exec(value); + return 0; +} + +export function expandValue( + createVariableReference: (name: string | any, options?: any) => number, + value: string, + root: string = '', + extra?: any +): any { + const parseQuotedString = (): string => { + value = value.trim(); + if (value[0] !== '"' && value[0] !== "'") { + return ''; + } + + let pos = 1; + let scanning = true; + const quote = value[0]; + let remaining = value.substr(1); + let escaped = false; + + while (scanning) { + if (escaped) { + escaped = false; + } else if (remaining[0] === '\\') { + escaped = true; + } else if (remaining[0] === quote) { + scanning = false; + } + remaining = remaining.substr(1); + pos++; + } + + const result = value.substr(0, pos).trim(); + value = value.substr(pos).trim(); + return result; + }; + + const stack = [root]; + let lastAssignedVariable = ''; + + const buildFullExpression = (name: string): string => { + let fullPath = ''; + let derefPrefix = ''; + stack.push(name); + stack.forEach((part) => { + derefPrefix = ''; + if (part === '') { + // skip + } else if (part.startsWith('[')) { + fullPath += part; + } else if (fullPath) { + while (part.startsWith('*')) { + derefPrefix += '*'; + part = part.substr(1); + } + fullPath = fullPath + '.' + part; + } else { + fullPath = part; + } + }); + stack.pop(); + return derefPrefix + fullPath; + }; + + let parseValue: () => any; + let parseCommaValue: () => any; + let parseNamedValue: (pushToStack?: boolean) => any; + let parseCommaNamedValue: (pushToStack?: boolean) => any; + let createResult: (name: string, val: any) => any; + + parseValue = (): any => { + value = value.trim(); + + if (value[0] === '"') { + return parseQuotedString(); + } + + if (value[0] === '{') { + // Parse object/array + return (() => { + value = value.trim(); + if (value[0] !== '{') { + return; + } + value = value.substr(1).trim(); + if (value[0] === '}') { + value = value.substr(1).trim(); + return []; + } + if (value.startsWith('...')) { + value = value.substr(3).trim(); + if (value[0] === '}') { + value = value.substr(1).trim(); + return '<...>'; + } + } + + // Check if this is an array or named values + const equalsIdx = value.indexOf('='); + const braceIdx = value.indexOf('{'); + const commaIdx = value.indexOf(','); + + let checkIdx = braceIdx; + if (commaIdx !== -1 && commaIdx < braceIdx) { + checkIdx = commaIdx; + } + + if ((checkIdx !== -1 && equalsIdx > checkIdx) || equalsIdx === -1) { + // Array of values + const arr: any[] = []; + stack.push('[0]'); + let val = parseValue(); + stack.pop(); + arr.push(createResult('[0]', val)); + + let index = 0; + for (;;) { + stack.push('[' + (++index) + ']'); + val = parseCommaValue(); + if (!val) { + stack.pop(); + break; + } + stack.pop(); + arr.push(createResult('[' + index + ']', val)); + } + value = value.substr(1).trim(); + return arr; + } + + // Named values + let named = parseNamedValue(true); + if (named) { + const arr: any[] = []; + arr.push(named); + while ((named = parseCommaNamedValue(true))) { + arr.push(named); + } + value = value.substr(1).trim(); + return arr; + } + })(); + } + + // Parse primitive value + return (() => { + value = value.trim(); + let match: RegExpExecArray; + let result: any; + + if (value.length === 0) { + result = undefined; + } else if (value.startsWith('true')) { + result = 'true'; + value = value.substr(4).trim(); + } else if (value.startsWith('false')) { + result = 'false'; + value = value.substr(5).trim(); + } else if ((match = NULL_PTR_REGEX.exec(value))) { + result = ''; + value = value.substr(match[0].length).trim(); + } else if ((match = HEX_STRING_REGEX.exec(value))) { + value = value.substr(match[1].length).trim(); + result = parseQuotedString(); + } else if ((match = HEX_REGEX.exec(value))) { + result = '*' + match[0]; + value = value.substr(match[0].length).trim(); + } else if ((match = CHAR_CODE_STRING_REGEX.exec(value))) { + result = match[1]; + value = value.substr(match[0].length - 1); + result += ' ' + parseQuotedString(); + } else if ( + (match = NUMBER_REGEX.exec(value)) || + (match = VARIABLE_NAME_REGEX.exec(value)) || + (match = ANGLE_BRACKET_REGEX.exec(value)) + ) { + result = match[0]; + value = value.substr(match[0].length).trim(); + } else { + result = value; + } + + return result; + })(); + }; + + parseNamedValue = (pushToStack: boolean = false): any => { + value = value.trim(); + const match = VARIABLE_ASSIGNMENT_REGEX.exec(value); + if (!match) { + return; + } + value = value.substr(match[0].length).trim(); + const name = (lastAssignedVariable = match[1]); + if (pushToStack) { + stack.push(lastAssignedVariable); + } + const val = parseValue(); + if (pushToStack) { + stack.pop(); + } + return createResult(name, val); + }; + + createResult = (name: string, val: any): any => { + let variablesReference = 0; + + if (typeof val === 'object') { + variablesReference = createVariableReference(val); + val = 'Object'; + } + + if (typeof val === 'string' && val.startsWith('*0x')) { + if (extra && MINode.valueOf(extra, 'arg') === '1') { + variablesReference = createVariableReference(buildFullExpression('*(' + name), { arg: true }); + val = ''; + } else { + variablesReference = createVariableReference(buildFullExpression('*' + name)); + val = 'Object@' + val; + } + } + + if (typeof val === 'string' && val.startsWith('<...>')) { + variablesReference = createVariableReference(buildFullExpression(name)); + val = '...'; + } + + return { + name, + value: val, + variablesReference, + }; + }; + + parseCommaValue = (): any => { + value = value.trim(); + if (value[0] === ',') { + value = value.substr(1).trim(); + return parseValue(); + } + }; + + parseCommaNamedValue = (pushToStack: boolean = false): any => { + value = value.trim(); + if (value[0] === ',') { + value = value.substr(1).trim(); + return parseNamedValue(pushToStack); + } + }; + + value = value.trim(); + return parseValue(); +} diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts new file mode 100644 index 0000000..64e3d0a --- /dev/null +++ b/src/backend/mi2/mi2.ts @@ -0,0 +1,567 @@ +import * as childProcess from 'child_process'; +import { EventEmitter } from 'events'; +import { VariableObject, MIError } from './types'; +import { parseMI, MINode } from '../mi_parse'; + +export function escape(str: string): string { + return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); +} + +const MI_OUTPUT_REGEX = /^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/; +const GDB_PROMPT_REGEX = /(?:\d*|undefined)\(gdb\)/; +const BREAK_COUNT_REGEX = /\d+/; + +function isPlainOutput(line: string): boolean { + return !MI_OUTPUT_REGEX.exec(line); +} + +export class MI2 extends EventEmitter { + private process: childProcess.ChildProcess; + private currentToken: number = 1; + private handlers: { [token: number]: (result: MINode) => void } = {}; + public printCalls: boolean; + public debugOutput: boolean; + private debugReadyFired: boolean = false; + private debugReadyTimeout: any; + private buffer: string; + private errbuf: string; + + constructor( + public application: string, + public args: string[] + ) { + super(); + } + + connect(cwd: string, commands: string[]): Promise { + return new Promise((resolve, reject) => { + const args = [...this.args]; + const env = Object.create(process.env); + if (process.env.PLATFORMIO_PATH) { + env.PATH = process.env.PLATFORMIO_PATH; + env.Path = process.env.PLATFORMIO_PATH; + } + + this.process = childProcess.spawn(this.application, args, { + cwd, + env, + shell: process.platform === 'win32', + }); + + this.process.stdout.on('data', this.stdout.bind(this)); + this.process.stderr.on('data', this.stderr.bind(this)); + this.process.on('exit', (() => { + this.emit('quit'); + }).bind(this)); + this.process.on('error', ((err: Error) => { + this.emit('launcherror', err); + }).bind(this)); + + const initCommands = [ + this.sendCommand('gdb-set target-async on', true), + ...commands.map((cmd) => this.sendCommand(cmd)), + ]; + Promise.all(initCommands).then(() => { + resolve(true); + }, reject); + }); + } + + stdout(data: any): void { + this.buffer += typeof data === 'string' ? data : data.toString('utf8'); + const newlineIndex = this.buffer.lastIndexOf('\n'); + if (newlineIndex !== -1) { + this.onOutput(this.buffer.substr(0, newlineIndex)); + this.buffer = this.buffer.substr(newlineIndex + 1); + } + if (this.buffer.length) { + if (this.onOutputPartial(this.buffer)) { + this.buffer = ''; + } + } + } + + stderr(data: any): void { + this.errbuf += typeof data === 'string' ? data : data.toString('utf8'); + const newlineIndex = this.errbuf.lastIndexOf('\n'); + if (newlineIndex !== -1) { + this.onOutputStderr(this.errbuf.substr(0, newlineIndex)); + this.errbuf = this.errbuf.substr(newlineIndex + 1); + } + if (this.errbuf.length) { + this.logNoNewLine('stderr', this.errbuf); + this.errbuf = ''; + } + } + + onOutputStderr(output: string): void { + const lines = output.split('\n'); + lines.forEach((line) => { + this.log('stderr', line); + }); + } + + onOutputPartial(line: string): boolean { + if (isPlainOutput(line)) { + this.logNoNewLine('stdout', line); + return true; + } + return false; + } + + onOutput(output: string): void { + const lines = output.split('\n'); + lines.forEach((line) => { + if (isPlainOutput(line)) { + if (!GDB_PROMPT_REGEX.exec(line)) { + this.log('stdout', line); + } + } else { + const parsed = parseMI(line); + if (this.debugOutput) { + this.log('log', 'GDB -> App: ' + JSON.stringify(parsed)); + } + + let handled = false; + + if (parsed.token !== undefined && this.handlers[parsed.token]) { + this.handlers[parsed.token](parsed); + delete this.handlers[parsed.token]; + handled = true; + } + + if (!handled && parsed.resultRecords && parsed.resultRecords.resultClass === 'error') { + this.log('stderr', parsed.result('msg') || line); + } + + if (parsed.outOfBandRecord) { + parsed.outOfBandRecord.forEach((record: any) => { + if (record.isStream) { + if (record.content.includes('PlatformIO: Initialization completed')) { + this.debugReadyTimeout = setTimeout(() => { + this.debugReadyFired = true; + this.emit('debug-ready'); + }, 200); + this.once('generic-stopped', () => { + if (!this.debugReadyFired) { + clearTimeout(this.debugReadyTimeout); + this.emit('debug-ready'); + } + }); + } + this.log(record.type, record.content); + } else if (record.type === 'exec') { + this.emit('exec-async-output', parsed); + if (record.asyncClass === 'running') { + this.emit('running', parsed); + } else if (record.asyncClass === 'stopped') { + const reason = parsed.record('reason'); + if (reason === 'breakpoint-hit') { + this.emit('breakpoint', parsed); + } else if (reason === 'end-stepping-range') { + this.emit('step-end', parsed); + } else if (reason === 'function-finished') { + this.emit('step-out-end', parsed); + } else if (reason === 'signal-received') { + this.emit('signal-stop', parsed); + } else if (reason === 'exited-normally') { + this.emit('exited-normally', parsed); + } else if (reason === 'exited') { + this.log('stderr', 'Program exited with code ' + parsed.record('exit-code')); + this.emit('exited-normally', parsed); + } else { + if (this.debugReadyFired) { + this.log('console', 'Not implemented stop reason (assuming exception): ' + reason); + } + this.emit('stopped', parsed); + } + this.emit('generic-stopped', parsed); + } else { + this.log('log', JSON.stringify(parsed)); + } + } else if (record.type === 'notify') { + if (record.asyncClass === 'thread-created') { + const threadId = parsed.result('id'); + const threadGroupId = parsed.result('group-id'); + this.emit('thread-created', { threadId, threadGroupId }); + } else if (record.asyncClass === 'thread-exited') { + const threadId = parsed.result('id'); + const threadGroupId = parsed.result('group-id'); + this.emit('thread-exited', { threadId, threadGroupId }); + } else if (record.asyncClass === 'thread-selected') { + const threadId = parsed.result('id'); + this.emit('thread-selected', { threadId }); + } + } + }); + handled = true; + } + + if (parsed.token === undefined && parsed.resultRecords === undefined && parsed.outOfBandRecord.length === 0) { + handled = true; + } + + if (!handled) { + this.log('log', 'Unhandled: ' + JSON.stringify(parsed)); + } + } + }); + } + + stop(forceKill: boolean = false): void { + if (forceKill) { + const proc = this.process; + const killTimeout = setTimeout(() => { + process.kill(-proc.pid); + }, 1000); + this.process.on('exit', (code: any) => { + clearTimeout(killTimeout); + }); + } + this.sendRaw('-gdb-exit'); + } + + detach(): void { + const proc = this.process; + const killTimeout = setTimeout(() => { + process.kill(-proc.pid); + }, 1000); + this.process.on('exit', (code: any) => { + clearTimeout(killTimeout); + }); + this.sendRaw('-target-detach'); + } + + interrupt(threadId: number): Promise { + return new Promise((resolve, reject) => { + this.sendCommand(`exec-interrupt --thread ${threadId}`).then( + (result) => { + resolve(result.resultRecords.resultClass === 'done'); + }, + reject + ); + }); + } + + continue(threadId: number): Promise { + return new Promise((resolve, reject) => { + this.sendCommand(`exec-continue --thread ${threadId}`).then( + (result) => { + resolve(result.resultRecords.resultClass === 'running'); + }, + reject + ); + }); + } + + next(threadId: number, instruction: boolean): Promise { + return new Promise((resolve, reject) => { + const command = instruction ? 'exec-next-instruction' : 'exec-next'; + this.sendCommand(`${command} --thread ${threadId}`).then( + (result) => { + resolve(result.resultRecords.resultClass === 'running'); + }, + reject + ); + }); + } + + step(threadId: number, instruction: boolean): Promise { + return new Promise((resolve, reject) => { + const command = instruction ? 'exec-step-instruction' : 'exec-step'; + this.sendCommand(`${command} --thread ${threadId}`).then( + (result) => { + resolve(result.resultRecords.resultClass === 'running'); + }, + reject + ); + }); + } + + stepOut(threadId: number): Promise { + return new Promise((resolve, reject) => { + this.sendCommand(`exec-finish --thread ${threadId}`).then( + (result) => { + resolve(result.resultRecords.resultClass === 'running'); + }, + reject + ); + }); + } + + restart(commands: string[]): Promise { + return this._sendCommandSequence(commands); + } + + _sendCommandSequence(commands: string[]): Promise { + return new Promise((resolve, reject) => { + const executeNext = ((remaining: string[]) => { + if (remaining.length === 0) { + resolve(true); + return; + } + const cmd = remaining[0]; + this.sendCommand(cmd).then( + (result) => { + executeNext(remaining.slice(1)); + }, + reject + ); + }).bind(this); + executeNext(commands); + }); + } + + changeVariable(name: string, rawValue: string): Promise { + return this.sendCommand('gdb-set var ' + name + '=' + rawValue); + } + + setBreakPointCondition(breakpointNumber: number, condition: string): Promise { + return this.sendCommand('break-condition ' + breakpointNumber + ' ' + condition); + } + + addBreakPoint(breakpoint: any): Promise { + return new Promise((resolve, reject) => { + let args = ''; + + if (breakpoint.countCondition) { + if (breakpoint.countCondition[0] === '>') { + args += '-i ' + BREAK_COUNT_REGEX.exec(breakpoint.countCondition.substr(1))[0] + ' '; + } else { + const count = BREAK_COUNT_REGEX.exec(breakpoint.countCondition)[0]; + if (count.length !== breakpoint.countCondition.length) { + this.log( + 'stderr', + "Unsupported break count expression: '" + + breakpoint.countCondition + + "'. Only supports 'X' for breaking once after X times or '>X' for ignoring the first X breaks" + ); + args += '-t '; + } else if (parseInt(count) !== 0) { + args += '-t -i ' + parseInt(count) + ' '; + } + } + } + + if (breakpoint.raw) { + args += '*' + escape(breakpoint.raw); + } else { + args += '"' + escape(breakpoint.file) + ':' + breakpoint.line + '"'; + } + + this.sendCommand(`break-insert ${args}`).then( + (result) => { + if (result.resultRecords.resultClass === 'done') { + const bkptNumber = parseInt(result.result('bkpt.number')); + breakpoint.number = bkptNumber; + if (breakpoint.condition) { + this.setBreakPointCondition(bkptNumber, breakpoint.condition).then( + (condResult) => { + if (condResult.resultRecords.resultClass === 'done') { + resolve(breakpoint); + } else { + resolve(null); + } + }, + reject + ); + } else { + resolve(breakpoint); + } + } else { + resolve(null); + } + }, + reject + ); + }); + } + + removeBreakpoints(breakpointNumbers: number[]): Promise { + return new Promise((resolve, reject) => { + if (breakpointNumbers.length === 0) { + resolve(true); + } else { + const cmd = 'break-delete ' + breakpointNumbers.join(' '); + this.sendCommand(cmd).then( + (result) => { + resolve(result.resultRecords.resultClass === 'done'); + }, + reject + ); + } + }); + } + + getFrame(threadId: number, frameLevel: number): Promise { + return new Promise((resolve, reject) => { + const cmd = `stack-info-frame --thread ${threadId} --frame ${frameLevel}`; + this.sendCommand(cmd).then( + (result) => { + const frame = result.result('frame'); + const level = MINode.valueOf(frame, 'level'); + const address = MINode.valueOf(frame, 'addr'); + const func = MINode.valueOf(frame, 'func'); + const file = MINode.valueOf(frame, 'file'); + const fullname = MINode.valueOf(frame, 'fullname'); + let line = 0; + const lineStr = MINode.valueOf(frame, 'line'); + if (lineStr) { + line = parseInt(lineStr); + } + resolve({ + address, + fileName: file, + file: fullname, + function: func, + level, + line, + }); + }, + reject + ); + }); + } + + getStack(threadId: number, startFrame: number, levels: number): Promise { + return new Promise((resolve, reject) => { + this.sendCommand(`stack-list-frames --thread ${threadId} ${startFrame} ${levels}`).then( + (result) => { + const stack = result.result('stack'); + const frames: any[] = []; + stack.forEach((entry: any) => { + const level = MINode.valueOf(entry, '@frame.level'); + const address = MINode.valueOf(entry, '@frame.addr'); + const func = MINode.valueOf(entry, '@frame.func'); + const file = MINode.valueOf(entry, '@frame.file'); + const fullname = MINode.valueOf(entry, '@frame.fullname'); + let line = 0; + const lineStr = MINode.valueOf(entry, '@frame.line'); + if (lineStr) { + line = parseInt(lineStr); + } + const from = parseInt(MINode.valueOf(entry, '@frame.from')); + frames.push({ + address, + fileName: file, + file: fullname, + function: func || from, + level, + line, + }); + }); + resolve(frames); + }, + reject + ); + }); + } + + async getStackVariables(threadId: number, frameLevel: number): Promise { + const result = await this.sendCommand( + `stack-list-variables --thread ${threadId} --frame ${frameLevel} --simple-values` + ); + const variables = result.result('variables'); + const vars: any[] = []; + for (const variable of variables) { + const name = MINode.valueOf(variable, 'name'); + const valueStr = MINode.valueOf(variable, 'value'); + const type = MINode.valueOf(variable, 'type'); + vars.push({ name, valueStr, type, raw: variable }); + } + return vars; + } + + async examineMemory(address: number, length: number): Promise { + let result = ''; + let currentAddress = address; + while (length > 0) { + const chunkSize = length > 1024 ? 1024 : length; + const response = await this.sendCommand( + `data-read-memory-bytes 0x${currentAddress.toString(16)} ${chunkSize}` + ); + result += response.result('memory[0].contents'); + length -= chunkSize; + currentAddress += chunkSize; + } + return result; + } + + evalExpression(expression: string): Promise { + return new Promise((resolve, reject) => { + this.sendCommand('data-evaluate-expression ' + expression).then( + (result) => { + resolve(result); + }, + reject + ); + }); + } + + async varCreate(expression: string, name: string = '-'): Promise { + const result = await this.sendCommand(`var-create ${name} @ "${expression}"`); + return new VariableObject(result.result('')); + } + + async varEvalExpression(name: string): Promise { + return this.sendCommand(`var-evaluate-expression ${name}`); + } + + async varListChildren(name: string): Promise { + const result = await this.sendCommand(`var-list-children --all-values ${name}`); + return (result.result('children') || []).map((child: any) => new VariableObject(child[1])); + } + + async varUpdate(name: string = '*'): Promise { + return this.sendCommand(`var-update --all-values ${name}`); + } + + async varAssign(name: string, value: string): Promise { + return this.sendCommand(`var-assign ${name} ${value}`); + } + + logNoNewLine(type: string, message: string): void { + this.emit('msg', type, message); + } + + log(type: string, message: string): void { + this.emit('msg', type, message[message.length - 1] === '\n' ? message : message + '\n'); + } + + sendUserInput(command: string): Promise { + if (command.startsWith('-')) { + return this.sendCommand(command.substr(1)); + } + return this.sendCommand(`interpreter-exec console "${command}"`); + } + + sendRaw(raw: string): void { + if (this.printCalls) { + this.log('log', raw); + } + this.process.stdin.write(raw + '\n'); + } + + sendCommand(command: string, suppressErrors: boolean = false): Promise { + const token = this.currentToken++; + return new Promise((resolve, reject) => { + this.handlers[token] = (result: MINode) => { + if (result && result.resultRecords && result.resultRecords.resultClass === 'error') { + if (suppressErrors) { + this.log('stderr', `WARNING: Error executing command '${command}'`); + resolve(result); + } else { + reject(new MIError(result.result('msg') || 'Internal error', command)); + } + } else { + resolve(result); + } + }; + this.sendRaw(token + '-' + command); + }); + } + + isReady(): boolean { + return !!this.process; + } +} diff --git a/src/backend/mi2/types.ts b/src/backend/mi2/types.ts new file mode 100644 index 0000000..29578e2 --- /dev/null +++ b/src/backend/mi2/types.ts @@ -0,0 +1,83 @@ +import { MINode } from '../mi_parse'; + +export class VariableObject { + public name: string; + public exp: string; + public numchild: number; + public type: string; + public value: string; + public threadId: string; + public frozen: boolean; + public dynamic: boolean; + public displayhint: string; + public hasMore: boolean; + public id: number; + public fullExp: string; + + constructor(node: any) { + this.name = MINode.valueOf(node, 'name'); + this.exp = MINode.valueOf(node, 'exp'); + this.numchild = parseInt(MINode.valueOf(node, 'numchild')); + this.type = MINode.valueOf(node, 'type'); + this.value = MINode.valueOf(node, 'value'); + this.threadId = MINode.valueOf(node, 'thread-id'); + this.frozen = !!MINode.valueOf(node, 'frozen'); + this.dynamic = !!MINode.valueOf(node, 'dynamic'); + this.displayhint = MINode.valueOf(node, 'displayhint'); + this.hasMore = !!MINode.valueOf(node, 'has_more'); + } + + applyChanges(node: any): void { + this.value = MINode.valueOf(node, 'value'); + if (MINode.valueOf(node, 'type_changed')) { + this.type = MINode.valueOf(node, 'new_type'); + } + this.dynamic = !!MINode.valueOf(node, 'dynamic'); + this.displayhint = MINode.valueOf(node, 'displayhint'); + this.hasMore = !!MINode.valueOf(node, 'has_more'); + } + + isCompound(): boolean { + return ( + this.numchild > 0 || + this.value === '{...}' || + (this.dynamic && (this.displayhint === 'array' || this.displayhint === 'map')) + ); + } + + toProtocolVariable(): any { + return { + name: this.exp, + evaluateName: this.fullExp || this.exp, + value: this.value === undefined ? '' : this.value, + type: this.type, + presentationHint: { kind: this.displayhint }, + variablesReference: this.id, + }; + } +} + +export class MIError { + public readonly message: string; + public readonly source: string; + + constructor(message: string, source: string) { + Object.defineProperty(this, 'name', { + get: () => this.constructor.name, + }); + Object.defineProperty(this, 'message', { + get: () => message, + }); + Object.defineProperty(this, 'source', { + get: () => source, + }); + Error.captureStackTrace(this, this.constructor); + } + + toString(): string { + return `${(this as any).message} (from ${(this as any).source})`; + } +} + +Object.setPrototypeOf(MIError, Object.create(Error.prototype)); +(MIError as any).prototype.constructor = MIError; diff --git a/src/backend/mi_parse.ts b/src/backend/mi_parse.ts new file mode 100644 index 0000000..0df7846 --- /dev/null +++ b/src/backend/mi_parse.ts @@ -0,0 +1,317 @@ +const OCTAL_ESCAPE_REGEX = /^[0-7]{3}/; + +export class MINode { + public token: number; + public outOfBandRecord: any[]; + public resultRecords: any; + + constructor(token: number, outOfBandRecord: any[], resultRecords: any) { + this.token = token; + this.outOfBandRecord = outOfBandRecord; + this.resultRecords = resultRecords; + } + + static valueOf(startNode: any, path: string): any { + if (!startNode) { + return undefined; + } + + const KEY_REGEX = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/; + const INDEX_REGEX = /^\[(\d+)\](?:$|\.)/; + + path = path.trim(); + if (!path) { + return startNode; + } + + let current = startNode; + + do { + let match = KEY_REGEX.exec(path); + if (match) { + path = path.substr(match[0].length); + if (!current.length || typeof current === 'string') { + return undefined; + } else { + const matches: any[] = []; + for (const item of current) { + if (item[0] === match[1]) { + matches.push(item[1]); + } + } + if (matches.length > 1) { + current = matches; + } else if (matches.length === 1) { + current = matches[0]; + } else { + return undefined; + } + } + } else if (path[0] === '@') { + current = [current]; + path = path.substr(1); + } else { + match = INDEX_REGEX.exec(path); + if (!match) { + return undefined; + } + path = path.substr(match[0].length); + const index = parseInt(match[1]); + if (current.length && typeof current !== 'string' && index >= 0 && index < current.length) { + current = current[index]; + } else if (index !== 0) { + return undefined; + } + } + + path = path.trim(); + } while (path); + + return current; + } + + record(path: string): any { + if (this.outOfBandRecord) { + return MINode.valueOf(this.outOfBandRecord[0].output, path); + } + } + + result(path: string): any { + if (this.resultRecords) { + return MINode.valueOf(this.resultRecords.results, path); + } + } +} + +const OUT_OF_BAND_REGEX = /^(?:(\d*|undefined)([\*\+\=])|([\~\@\&]))/; +const RESULT_REGEX = /^(\d*)\^(done|running|connected|error|exit)/; +const NEWLINE_REGEX = /^\r\n?/; +const VARIABLE_NAME_REGEX = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/; +const ASYNC_CLASS_REGEX = /^(.*?),/; + +function parseString(input: string): [string, string] { + if (input[0] !== '"') { + return ['', input]; + } + + let pos = 1; + let scanning = true; + let str = input.substr(1); + let escaped = false; + + while (scanning) { + if (escaped) { + escaped = false; + } else if (str[0] === '\\') { + escaped = true; + } else if (str[0] === '"') { + scanning = false; + } + str = str.substr(1); + pos++; + } + + let result: string; + try { + result = parseCString(input.substr(0, pos)); + } catch (e) { + result = input.substr(0, pos); + } + + return [result, input.substr(pos)]; +} + +function parseCString(str: string): string { + const buffer = Buffer.alloc(str.length * 4); + let offset = 0; + + if (str[0] !== '"' || str[str.length - 1] !== '"') { + throw new Error('Not a valid string'); + } + str = str.slice(1, -1); + + let escaped = false; + for (let i = 0; i < str.length; i++) { + if (escaped) { + let octalMatch; + if (str[i] === '\\') { + offset += buffer.write('\\', offset); + } else if (str[i] === '"') { + offset += buffer.write('"', offset); + } else if (str[i] === "'") { + offset += buffer.write("'", offset); + } else if (str[i] === 'n') { + offset += buffer.write('\n', offset); + } else if (str[i] === 'r') { + offset += buffer.write('\r', offset); + } else if (str[i] === 't') { + offset += buffer.write('\t', offset); + } else if (str[i] === 'b') { + offset += buffer.write('\b', offset); + } else if (str[i] === 'f') { + offset += buffer.write('\f', offset); + } else if (str[i] === 'v') { + offset += buffer.write('\v', offset); + } else if (str[i] === '0') { + offset += buffer.write('\0', offset); + } else if ((octalMatch = OCTAL_ESCAPE_REGEX.exec(str.substr(i)))) { + buffer.writeUInt8(parseInt(octalMatch[0], 8), offset++); + i += 2; + } else { + offset += buffer.write(str[i], offset); + } + escaped = false; + } else if (str[i] === '\\') { + escaped = true; + } else { + if (str[i] === '"') { + throw new Error('Not a valid string'); + } + offset += buffer.write(str[i], offset); + } + } + + return buffer.slice(0, offset).toString('utf8'); +} + +export function parseMI(output: string): MINode { + let token: number; + const outOfBandRecords: any[] = []; + let resultRecords: any; + + const asyncClassMap: { [key: string]: string } = { + '*': 'exec', + '+': 'status', + '=': 'notify', + }; + + const streamClassMap: { [key: string]: string } = { + '~': 'console', + '@': 'target', + '&': 'log', + }; + + let parseValue: () => any; + let parseResult: () => any; + let parseCommaValue: () => any; + let parseCommaResult: () => any; + + parseValue = (): any => { + if (output[0] === '"') { + const [str, rest] = parseString(output); + output = rest; + return str; + } else if (output[0] === '{' || output[0] === '[') { + const isList = output[0] === '['; + output = output.substr(1); + if (output[0] === '}' || output[0] === ']') { + output = output.substr(1); + return []; + } + + if (isList) { + let val = parseValue(); + if (val) { + const arr: any[] = []; + arr.push(val); + while ((val = parseCommaValue()) !== undefined) { + arr.push(val); + } + output = output.substr(1); + return arr; + } + } + + let result = parseResult(); + if (result) { + const arr: any[] = []; + arr.push(result); + while ((result = parseCommaResult())) { + arr.push(result); + } + output = output.substr(1); + return arr; + } + + output = (isList ? '[' : '{') + output; + } + return undefined; + }; + + parseResult = (): any => { + const match = VARIABLE_NAME_REGEX.exec(output); + if (match) { + output = output.substr(match[0].length + 1); // +1 for '=' + return [match[1], parseValue()]; + } + }; + + parseCommaValue = (): any => { + if (output[0] === ',') { + output = output.substr(1); + return parseValue(); + } + }; + + parseCommaResult = (): any => { + if (output[0] === ',') { + output = output.substr(1); + return parseResult(); + } + }; + + let match: RegExpExecArray; + + while ((match = OUT_OF_BAND_REGEX.exec(output))) { + output = output.substr(match[0].length); + + if (match[1] && token === undefined && match[1] !== 'undefined') { + token = parseInt(match[1]); + } + + if (match[2]) { + const classMatch = ASYNC_CLASS_REGEX.exec(output); + output = output.substr(classMatch[1].length); + const record: any = { + isStream: false, + type: asyncClassMap[match[2]], + asyncClass: classMatch[1], + output: [], + }; + let res; + while ((res = parseCommaResult())) { + record.output.push(res); + } + outOfBandRecords.push(record); + } else if (match[3]) { + const [content, rest] = parseString(output); + output = rest; + const record: any = { + isStream: true, + type: streamClassMap[match[3]], + content, + }; + outOfBandRecords.push(record); + } + + output = output.replace(NEWLINE_REGEX, ''); + } + + if ((match = RESULT_REGEX.exec(output))) { + output = output.substr(match[0].length); + if (match[1] && token === undefined) { + token = parseInt(match[1]); + } + resultRecords = { + resultClass: match[2], + results: [], + }; + let res; + while ((res = parseCommaResult())) { + resultRecords.results.push(res); + } + output = output.replace(NEWLINE_REGEX, ''); + } + + return new MINode(token, outOfBandRecords || [], resultRecords); +} diff --git a/src/backend/symbols.ts b/src/backend/symbols.ts new file mode 100644 index 0000000..9b08b14 --- /dev/null +++ b/src/backend/symbols.ts @@ -0,0 +1,167 @@ +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { SymbolType, SymbolScope } from '../common'; + +const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/; +const DEMANGLED_NAME_REGEX = /^_Z[^\d]*(\d+)(.+)$/; + +const TYPE_MAP: { [key: string]: SymbolType } = { + 'F': SymbolType.Function, + 'f': SymbolType.File, + 'O': SymbolType.Object, + ' ': SymbolType.Normal, +}; + +const SCOPE_MAP: { [key: string]: SymbolScope } = { + 'l': SymbolScope.Local, + 'g': SymbolScope.Global, + ' ': SymbolScope.Neither, + '!': SymbolScope.Both, +}; + +export interface SymbolInformation { + address: number; + type: SymbolType; + scope: SymbolScope; + section: string; + length: number; + name: string; + file: string | null; + instructions: any[] | null; + hidden: boolean; +} + +export class SymbolTable { + private symbols: SymbolInformation[] = []; + + constructor( + private toolchainBinDir: string, + private executable: string + ) {} + + loadSymbols(): void { + let objdumpPath = ''; + fs.readdirSync(this.toolchainBinDir).forEach((file) => { + if (file.includes('objdump') && fs.existsSync(path.join(this.toolchainBinDir, file))) { + objdumpPath = path.join(this.toolchainBinDir, file); + } + }); + + if (!objdumpPath) { + throw new Error('Could not find "objdump" program'); + } + + const lines = childProcess + .spawnSync(objdumpPath, ['--syms', this.executable]) + .stdout.toString() + .split('\n'); + + let currentFile: string | null = null; + + for (const line of lines) { + const match = line.match(SYMBOL_REGEX); + if (!match) { + continue; + } + + const type = TYPE_MAP[match[8]]; + const scope = SCOPE_MAP[match[2]]; + let name = match[11].trim(); + let hidden = false; + + if (match[7] === 'd' && match[8] === 'f') { + currentFile = name; + } else { + if (name.startsWith('.hidden')) { + name = name.substring(7).trim(); + hidden = true; + } + + const demangledMatch = name.match(DEMANGLED_NAME_REGEX); + if (demangledMatch) { + if (type !== SymbolType.Function) { + continue; + } + const nameLength = parseInt(demangledMatch[1]); + name = demangledMatch[2].substr(0, nameLength); + + if (demangledMatch[2].length > nameLength) { + const nestedMatch = demangledMatch[2].substr(nameLength).match(/^(\d+)(.+)$/); + if (nestedMatch) { + name += '::' + nestedMatch[2].substr(0, parseInt(nestedMatch[1])); + } + } + } + + this.symbols.push({ + address: parseInt(match[1], 16), + type, + scope, + section: match[9].trim(), + length: parseInt(match[10], 16), + name, + file: scope === SymbolScope.Local ? currentFile : null, + instructions: null, + hidden, + }); + } + } + } + + getFunctionAtAddress(address: number): SymbolInformation | undefined { + const matches = this.symbols.filter( + (sym) => + sym.type === SymbolType.Function && + sym.address <= address && + sym.address + sym.length > address + ); + if (matches && matches.length !== 0) { + return matches[0]; + } + } + + getFunctionSymbols(): SymbolInformation[] { + return this.symbols.filter((sym) => sym.type === SymbolType.Function); + } + + getGlobalVariables(): SymbolInformation[] { + return this.symbols.filter( + (sym) => sym.type === SymbolType.Object && sym.scope === SymbolScope.Global + ); + } + + getStaticVariables(file: string): SymbolInformation[] { + return this.symbols.filter( + (sym) => + sym.type === SymbolType.Object && + sym.scope === SymbolScope.Local && + sym.file === file + ); + } + + getFunctionByName(name: string, file: string): SymbolInformation | null { + let matches = this.symbols.filter( + (sym) => + sym.type === SymbolType.Function && + sym.scope === SymbolScope.Local && + sym.name === name && + sym.file === file + ); + if (matches.length !== 0) { + return matches[0]; + } + + matches = this.symbols.filter( + (sym) => + sym.type === SymbolType.Function && + sym.scope !== SymbolScope.Local && + sym.name === name + ); + if (matches.length !== 0) { + return matches[0]; + } + + return null; + } +} diff --git a/src/common.ts b/src/common.ts new file mode 100644 index 0000000..68c1008 --- /dev/null +++ b/src/common.ts @@ -0,0 +1,40 @@ +import { Event } from 'vscode-debugadapter'; + +export enum NumberFormat { + Auto = 0, + Hexidecimal = 1, + Decimal = 2, + Binary = 3, +} + +export class AdapterOutputEvent extends Event { + constructor(content: string, type: string) { + super('adapter-output', { content, type }); + } +} + +export class StoppedEvent extends Event { + constructor(reason: string, threadId: number, allThreadsStopped: boolean) { + super('stopped', { reason, threadId, allThreadsStopped }); + } +} + +export class TelemetryEvent extends Event { + constructor(category: string, action: string, label: string, parameters: any = {}) { + super('record-event', { category, action, label, parameters }); + } +} + +export enum SymbolType { + Function = 0, + File = 1, + Object = 2, + Normal = 3, +} + +export enum SymbolScope { + Local = 0, + Global = 1, + Neither = 2, + Both = 3, +} diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000..1972795 --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,431 @@ +import * as copyPaste from 'copy-paste'; +import * as vscode from 'vscode'; +import { NumberFormat, SymbolScope } from './common'; +import { encodeDisassembly } from './utils'; +import { PlatformIODebugConfigurationProvider } from './frontend/configprovider'; +import { DisassemblyContentProvider } from './frontend/disassembly_content_provider'; +import { DisassemblyTreeProvider } from './frontend/disassembly_tree_provider'; +import { MemoryContentProvider } from './frontend/memory_content_provider'; +import { MemoryTreeProvider } from './frontend/memory_tree_provider'; +import { PeripheralTreeProvider, RecordType as PeripheralRecordType } from './frontend/peripheral'; +import { RegisterTreeProvider, RecordType as RegisterRecordType } from './frontend/registers'; + +class PlatformIODebugExtension { + private adapterOutputChannel: vscode.OutputChannel = null; + private functionSymbols: any[] = null; + private context: vscode.ExtensionContext; + private registerProvider: RegisterTreeProvider; + private peripheralProvider: PeripheralTreeProvider; + private memoryTreeProvider: MemoryTreeProvider; + private disassemblyTreeProvider: DisassemblyTreeProvider; + private memoryContentProvider: MemoryContentProvider; + + constructor(context: vscode.ExtensionContext) { + this.context = context; + this.registerProvider = new RegisterTreeProvider(); + this.peripheralProvider = new PeripheralTreeProvider(); + this.memoryTreeProvider = new MemoryTreeProvider(); + this.disassemblyTreeProvider = new DisassemblyTreeProvider(); + this.memoryContentProvider = new MemoryContentProvider(); + + const peripheralTreeView = vscode.window.createTreeView('platformio-debug.peripherals', { + treeDataProvider: this.peripheralProvider, + }); + + context.subscriptions.push( + vscode.debug.registerDebugConfigurationProvider( + 'platformio-debug', + new PlatformIODebugConfigurationProvider() + ), + peripheralTreeView, + peripheralTreeView.onDidExpandElement( + this.peripheralProvider.onDidExpandElement.bind(this.peripheralProvider) + ), + peripheralTreeView.onDidCollapseElement( + this.peripheralProvider.onDidCollapseElement.bind(this.peripheralProvider) + ), + vscode.window.registerTreeDataProvider('platformio-debug.registers', this.registerProvider), + vscode.window.registerTreeDataProvider('platformio-debug.memory', this.memoryTreeProvider), + vscode.window.registerTreeDataProvider('platformio-debug.disassembly', this.disassemblyTreeProvider), + vscode.workspace.registerTextDocumentContentProvider('examinememory', this.memoryContentProvider), + vscode.workspace.registerTextDocumentContentProvider('disassembly', new DisassemblyContentProvider()), + + vscode.commands.registerCommand('platformio-debug.peripherals.updateNode', this.peripheralsUpdateNode.bind(this)), + vscode.commands.registerCommand('platformio-debug.peripherals.selectedNode', this.peripheralsSelectedNode.bind(this)), + vscode.commands.registerCommand('platformio-debug.peripherals.copyValue', this.peripheralsCopyValue.bind(this)), + vscode.commands.registerCommand('platformio-debug.peripherals.setFormat', this.peripheralsSetFormat.bind(this)), + vscode.commands.registerCommand('platformio-debug.registers.selectedNode', this.registersSelectedNode.bind(this)), + vscode.commands.registerCommand('platformio-debug.registers.copyValue', this.registersCopyValue.bind(this)), + vscode.commands.registerCommand('platformio-debug.registers.setFormat', this.registersSetFormat.bind(this)), + vscode.commands.registerCommand('platformio-debug.memory.deleteHistoryItem', this.memoryDeleteHistoryItem.bind(this)), + vscode.commands.registerCommand('platformio-debug.memory.clearHistory', this.memoryClearHistory.bind(this)), + vscode.commands.registerCommand('platformio-debug.examineMemory', this.examineMemory.bind(this)), + vscode.commands.registerCommand('platformio-debug.viewDisassembly', this.showDisassembly.bind(this)), + vscode.commands.registerCommand('platformio-debug.setForceDisassembly', this.setForceDisassembly.bind(this)), + + vscode.debug.onDidReceiveDebugSessionCustomEvent(this.receivedCustomEvent.bind(this)), + vscode.debug.onDidStartDebugSession(this.debugSessionStarted.bind(this)), + vscode.debug.onDidTerminateDebugSession(this.debugSessionTerminated.bind(this)), + vscode.window.onDidChangeActiveTextEditor(this.activeEditorChanged.bind(this)), + vscode.window.onDidChangeTextEditorSelection((e) => { + if (e && e.textEditor.document.fileName.endsWith('.dbgmem')) { + this.memoryContentProvider.handleSelection(e); + } + }) + ); + } + + private isPIODebugSession(): boolean { + return vscode.debug.activeDebugSession && vscode.debug.activeDebugSession.type === 'platformio-debug'; + } + + private activeEditorChanged(editor: vscode.TextEditor): void { + if (!editor || !this.isPIODebugSession()) { + return; + } + + const uri = editor.document.uri; + if (uri.scheme === 'file') { + vscode.debug.activeDebugSession.customRequest('set-active-editor', { path: uri.path }); + } else if (uri.scheme === 'disassembly') { + vscode.debug.activeDebugSession.customRequest('set-active-editor', { + path: `${uri.scheme}://${uri.authority}${uri.path}`, + }); + } + } + + private async showDisassembly(): Promise { + if (!this.isPIODebugSession()) { + vscode.window.showErrorMessage('No debugging session available'); + return; + } + + if (!this.functionSymbols) { + try { + const result = await vscode.debug.activeDebugSession.customRequest('load-function-symbols'); + this.functionSymbols = result.functionSymbols; + } catch (e) { + vscode.window.showErrorMessage('Unable to load symbol table. Disassembly view unavailable.'); + } + } + + try { + const funcName = await vscode.window.showInputBox({ + placeHolder: 'main', + ignoreFocusOut: true, + prompt: 'Function Name to Disassemble', + }); + + const matches = this.functionSymbols.filter((s) => s.name === funcName); + let uri: string; + + if (matches.length === 1) { + uri = encodeDisassembly(matches[0].name, matches[0].file); + } else if (matches.length > 1) { + const selected = await vscode.window.showQuickPick( + matches.map((m) => ({ + label: m.name, + name: m.name, + file: m.file, + scope: m.scope, + description: + m.scope === SymbolScope.Global ? 'Global Scope' : `Static in ${m.file}`, + })), + { ignoreFocusOut: true } + ); + uri = encodeDisassembly(selected.name, selected.file); + } else { + vscode.window.showErrorMessage(`No function with name ${funcName} found.`); + return; + } + + if (uri) { + vscode.window.showTextDocument(vscode.Uri.parse(uri)); + } + } catch (e) { + vscode.window.showErrorMessage('Unable to show disassembly.'); + } + } + + private setForceDisassembly(force?: string): void { + const doSet = (value: string) => { + const forced = value === 'Forced'; + this.disassemblyTreeProvider.updateForcedState(forced); + return vscode.debug.activeDebugSession.customRequest('set-force-disassembly', { force: forced }); + }; + + if (force) { + return doSet(force) as any; + } + + vscode.window + .showQuickPick( + [ + { + label: 'Auto', + description: 'Show disassembly for functions when source cannot be located.', + }, + { + label: 'Forced', + description: 'Always show disassembly for functions.', + }, + ], + { matchOnDescription: true, ignoreFocusOut: true } + ) + .then( + (selected) => { + doSet(selected.label); + }, + (err) => {} + ); + } + + private memoryDeleteHistoryItem(item: any): void { + const [address, length] = item.label.split('+'); + this.memoryTreeProvider.deleteHistory(address, length); + } + + private memoryClearHistory(): void { + this.memoryTreeProvider.clearHistory(); + } + + private examineMemory(address?: string, length?: string): any { + function validateInput(input: string): string | null { + if (/^0x[0-9a-f]{1,8}$/i.test(input) || /^[0-9]+$/i.test(input)) { + return input; + } + return null; + } + + if (!this.isPIODebugSession()) { + vscode.window.showErrorMessage('No debugging session available'); + return; + } + + if (address && length) { + return this.showMemoryContent(address, length); + } + + vscode.window + .showInputBox({ + placeHolder: 'Prefix with 0x for hexidecimal format', + ignoreFocusOut: true, + prompt: 'A start memory address', + }) + .then( + (addressInput) => { + if (validateInput(addressInput)) { + vscode.window + .showInputBox({ + placeHolder: 'Prefix with 0x for hexidecimal format', + ignoreFocusOut: true, + prompt: 'How many bytes to read?', + }) + .then( + (lengthInput) => { + if (validateInput(lengthInput)) { + this.memoryTreeProvider.pushHistory(addressInput, lengthInput); + this.showMemoryContent(addressInput, lengthInput); + } else { + vscode.window.showErrorMessage('Invalid length entered'); + } + }, + (err) => {} + ); + } else { + vscode.window.showErrorMessage('Invalid memory address entered'); + } + }, + (err) => {} + ); + } + + private showMemoryContent(address: string, length: string): void { + vscode.workspace + .openTextDocument( + vscode.Uri.parse( + `examinememory:///Memory%20[${address}+${length}].dbgmem?address=${address}&length=${length}×tamp=${new Date().getTime()}` + ) + ) + .then( + (doc) => { + vscode.window.showTextDocument(doc, { viewColumn: 2, preview: false }); + }, + (error) => { + vscode.window.showErrorMessage(`Failed to examine memory: ${error}`); + } + ); + } + + private peripheralsUpdateNode(node: any): void { + node.node.performUpdate().then( + (result: boolean) => { + if (result) { + this.peripheralProvider.refresh(); + } + }, + (error: any) => { + vscode.window.showErrorMessage(`Unable to update value: ${error.toString()}`); + } + ); + } + + private peripheralsSelectedNode(node: any): void { + if (node.recordType !== PeripheralRecordType.Field) { + node.expanded = !node.expanded; + } + node.selected().then( + (result: boolean) => { + if (result) { + this.peripheralProvider.refresh(); + } + }, + (error: any) => {} + ); + } + + private peripheralsCopyValue(node: any): void { + const value = node.node.getCopyValue(); + if (value) { + copyPaste.copy(value); + } + } + + private async peripheralsSetFormat(node: any): Promise { + const selected = await vscode.window.showQuickPick([ + { label: 'Auto', description: 'Automatically choose format (Inherits from parent)', value: NumberFormat.Auto }, + { label: 'Hex', description: 'Format value in hexidecimal', value: NumberFormat.Hexidecimal }, + { label: 'Decimal', description: 'Format value in decimal', value: NumberFormat.Decimal }, + { label: 'Binary', description: 'Format value in binary', value: NumberFormat.Binary }, + ]); + node.node.setFormat(selected.value); + this.peripheralProvider.refresh(); + } + + private registersSelectedNode(node: any): void { + if (node.recordType !== RegisterRecordType.Field) { + node.expanded = !node.expanded; + } + } + + private registersCopyValue(node: any): void { + const value = node.node.getCopyValue(); + if (value) { + copyPaste.copy(value); + } + } + + private async registersSetFormat(node: any): Promise { + const selected = await vscode.window.showQuickPick([ + { label: 'Auto', description: 'Automatically choose format (Inherits from parent)', value: NumberFormat.Auto }, + { label: 'Hex', description: 'Format value in hexidecimal', value: NumberFormat.Hexidecimal }, + { label: 'Decimal', description: 'Format value in decimal', value: NumberFormat.Decimal }, + { label: 'Binary', description: 'Format value in binary', value: NumberFormat.Binary }, + ]); + node.node.setFormat(selected.value); + this.registerProvider.refresh(); + } + + private debugSessionStarted(session: vscode.DebugSession): void { + if (session.type === 'platformio-debug') { + this.functionSymbols = null; + session.customRequest('get-arguments').then( + (args: any) => { + this.registerProvider.debugSessionStarted( + this.context.workspaceState.get('debugRegistersTreeState') + ); + this.peripheralProvider.debugSessionStarted( + args.svdPath, + this.context.workspaceState.get('debugPeripheralsTreeState') + ); + this.memoryTreeProvider.debugSessionStarted( + this.context.workspaceState.get('debugMemoryTreeState') + ); + this.disassemblyTreeProvider.debugSessionStarted(); + }, + (error: any) => { + console.error(error); + } + ); + } + } + + private debugSessionTerminated(session: vscode.DebugSession): void { + if (session.type === 'platformio-debug') { + this.context.workspaceState.update( + 'debugRegistersTreeState', + this.registerProvider.dumpSettings() + ); + this.context.workspaceState.update( + 'debugPeripheralsTreeState', + this.peripheralProvider.dumpSettings() + ); + this.context.workspaceState.update( + 'debugMemoryTreeState', + this.memoryTreeProvider.dumpSettings() + ); + + this.registerProvider.debugSessionTerminated(); + this.peripheralProvider.debugSessionTerminated(); + this.memoryTreeProvider.debugSessionTerminated(); + this.disassemblyTreeProvider.debugSessionTerminated(); + } + } + + private receivedCustomEvent(e: vscode.DebugSessionCustomEvent): void { + if (!this.isPIODebugSession()) { + return; + } + + switch (e.event) { + case 'custom-stop': + this.receivedStopEvent(e); + break; + case 'custom-continued': + this.receivedContinuedEvent(e); + break; + case 'adapter-output': + this.receivedAdapterOutput(e); + break; + case 'record-event': + this.receivedEvent(e); + break; + } + } + + private receivedStopEvent(e: vscode.DebugSessionCustomEvent): void { + this.peripheralProvider.debugStopped(); + this.registerProvider.debugStopped(); + + vscode.workspace.textDocuments + .filter((doc) => doc.fileName.endsWith('.dbgmem')) + .forEach((doc) => { + this.memoryContentProvider.update(doc); + }); + } + + private receivedContinuedEvent(e: vscode.DebugSessionCustomEvent): void { + this.peripheralProvider.debugContinued(); + this.registerProvider.debugContinued(); + } + + private receivedEvent(e: vscode.DebugSessionCustomEvent): void {} + + private receivedAdapterOutput(e: vscode.DebugSessionCustomEvent): void { + if (!this.adapterOutputChannel) { + this.adapterOutputChannel = vscode.window.createOutputChannel('Adapter Output'); + } + + let content: string = e.body.content; + if (!content.endsWith('\n')) { + content += '\n'; + } + this.adapterOutputChannel.append(content); + } +} + +export function activate(context: vscode.ExtensionContext): PlatformIODebugExtension { + return new PlatformIODebugExtension(context); +} + +export function deactivate(): void {} diff --git a/src/frontend/configprovider.ts b/src/frontend/configprovider.ts new file mode 100644 index 0000000..570a31f --- /dev/null +++ b/src/frontend/configprovider.ts @@ -0,0 +1,14 @@ +import * as vscode from 'vscode'; + +export class PlatformIODebugConfigurationProvider implements vscode.DebugConfigurationProvider { + constructor() {} + + async resolveDebugConfiguration( + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + token?: vscode.CancellationToken + ): Promise { + (config as any).cwd = folder ? folder.uri.fsPath : vscode.workspace.rootPath; + return config; + } +} diff --git a/src/frontend/disassembly_content_provider.ts b/src/frontend/disassembly_content_provider.ts new file mode 100644 index 0000000..9e36b6c --- /dev/null +++ b/src/frontend/disassembly_content_provider.ts @@ -0,0 +1,33 @@ +import * as vscode from 'vscode'; +import { parseQuery } from '../utils'; + +export class DisassemblyContentProvider implements vscode.TextDocumentContentProvider { + provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { + return new Promise((resolve, reject) => { + const params = parseQuery(uri.query); + vscode.debug.activeDebugSession + .customRequest('disassemble', { function: params.func, file: params.file }) + .then( + (result: any) => { + const instructions = result.instructions; + let output = ''; + instructions.forEach((instruction: any) => { + output += `${instruction.address}: ${this.padEnd(15, instruction.opcodes)} \t${instruction.instruction}\n`; + }); + resolve(output); + }, + (error: any) => { + vscode.window.showErrorMessage(error.message); + reject(error.message); + } + ); + }); + } + + private padEnd(targetLength: number, str: string): string { + for (let i = str.length; i < targetLength; i++) { + str += ' '; + } + return str; + } +} diff --git a/src/frontend/disassembly_tree_provider.ts b/src/frontend/disassembly_tree_provider.ts new file mode 100644 index 0000000..529952d --- /dev/null +++ b/src/frontend/disassembly_tree_provider.ts @@ -0,0 +1,50 @@ +import * as vscode from 'vscode'; + +export class DisassemblyTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + public onDidChangeTreeData = this._onDidChangeTreeData.event; + private forced: boolean = false; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getChildren(element?: vscode.TreeItem): vscode.TreeItem[] { + if (!vscode.debug.activeDebugSession) { + return []; + } + + const disassembleItem = new vscode.TreeItem('Disassemble function'); + disassembleItem.command = { + title: 'Disassemble function', + command: 'platformio-debug.viewDisassembly', + }; + + const switchLabel = 'Switch to ' + (this.forced ? 'code' : 'assembly'); + const switchItem = new vscode.TreeItem(switchLabel); + switchItem.command = { + title: switchLabel, + command: 'platformio-debug.setForceDisassembly', + arguments: [this.forced ? 'Auto' : 'Forced'], + }; + + return [disassembleItem, switchItem]; + } + + getTreeItem(element: vscode.TreeItem): vscode.TreeItem { + return element; + } + + updateForcedState(forced: boolean): void { + this.forced = forced; + this.refresh(); + } + + debugSessionStarted(): void { + this.refresh(); + } + + debugSessionTerminated(): void { + this.updateForcedState(false); + } +} diff --git a/src/frontend/memory_content_provider.ts b/src/frontend/memory_content_provider.ts new file mode 100644 index 0000000..1386337 --- /dev/null +++ b/src/frontend/memory_content_provider.ts @@ -0,0 +1,158 @@ +import * as vscode from 'vscode'; +import { hexFormat, parseQuery } from '../utils'; + +export class MemoryContentProvider implements vscode.TextDocumentContentProvider { + private _onDidChange = new vscode.EventEmitter(); + public onDidChange = this._onDidChange.event; + + private firstBytePos = 10; + private lastBytePos = this.firstBytePos + 48 - 1; + private firstAsciiPos = this.lastBytePos + 3; + private lastAsciiPos = this.firstAsciiPos + 16; + + private smallDecorationType = vscode.window.createTextEditorDecorationType({ + borderWidth: '1px', + borderStyle: 'solid', + overviewRulerColor: 'blue', + overviewRulerLane: vscode.OverviewRulerLane.Right, + light: { borderColor: 'darkblue' }, + dark: { borderColor: 'lightblue' }, + }); + + provideTextDocumentContent(uri: vscode.Uri): Thenable { + return new Promise((resolve, reject) => { + const params = parseQuery(uri.query); + const address = params.address.startsWith('0x') + ? parseInt(params.address.substring(2), 16) + : parseInt(params.address, 10); + const length = params.length.startsWith('0x') + ? parseInt(params.length.substring(2), 16) + : parseInt(params.length, 10); + + vscode.debug.activeDebugSession + .customRequest('read-memory', { address, length: length || 32 }) + .then( + (result: any) => { + const bytes: number[] = result.bytes; + let rowAddress = address - (address % 16); + const offset = address - rowAddress; + let output = ''; + + output += ' Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F \t\n'; + output += hexFormat(rowAddress, 8, false) + ': '; + + let asciiStr = ''; + for (let i = 0; i < offset; i++) { + output += ' '; + asciiStr += ' '; + } + + for (let i = 0; i < length; i++) { + const byte = bytes[i]; + output += hexFormat(byte, 2, false).toUpperCase() + ' '; + asciiStr += + byte <= 32 || (byte >= 127 && byte <= 159) + ? '.' + : String.fromCharCode(bytes[i]); + + if ((address + i) % 16 === 15 && i < length - 1) { + output += ' ' + asciiStr; + asciiStr = ''; + output += '\n'; + rowAddress += 16; + output += hexFormat(rowAddress, 8, false) + ': '; + } + } + + const remaining = (16 - ((address + length) % 16)) % 16; + for (let i = 0; i < remaining; i++) { + output += ' '; + } + output += ' ' + asciiStr; + output += '\n'; + + resolve(output); + }, + (error: any) => { + vscode.window.showErrorMessage( + `Unable to read memory from ${hexFormat(address, 8)} to ${hexFormat(address + length, 8)}` + ); + reject(error.toString()); + } + ); + }); + } + + update(document: vscode.TextDocument): void { + this._onDidChange.fire(document.uri); + } + + getOffset(position: vscode.Position): number | undefined { + if (position.line < 1 || position.character < this.firstBytePos) { + return; + } + + let offset = 16 * (position.line - 1); + const charOffset = position.character - this.firstBytePos; + + if (position.character >= this.firstBytePos && position.character <= this.lastBytePos) { + offset += Math.floor(charOffset / 3); + } else if (position.character >= this.firstAsciiPos) { + offset += position.character - this.firstAsciiPos; + } + + return offset; + } + + getPosition(offset: number, isAscii: boolean = false): vscode.Position { + const line = 1 + Math.floor(offset / 16); + let character = offset % 16; + if (isAscii) { + character += this.firstAsciiPos; + } else { + character = this.firstBytePos + 3 * character; + } + return new vscode.Position(line, character); + } + + getRanges(startOffset: number, endOffset: number, isAscii: boolean): vscode.Range[] { + const startPos = this.getPosition(startOffset, isAscii); + let endPos = this.getPosition(endOffset, isAscii); + endPos = new vscode.Position(endPos.line, endPos.character + (isAscii ? 1 : 2)); + + const ranges: vscode.Range[] = []; + const startChar = isAscii ? this.firstAsciiPos : this.firstBytePos; + const endChar = isAscii ? this.lastAsciiPos : this.lastBytePos; + + for (let line = startPos.line; line <= endPos.line; ++line) { + const lineStart = new vscode.Position(line, line === startPos.line ? startPos.character : startChar); + const lineEnd = new vscode.Position(line, line === endPos.line ? endPos.character : endChar); + ranges.push(new vscode.Range(lineStart, lineEnd)); + } + + return ranges; + } + + handleSelection(event: vscode.TextEditorSelectionChangeEvent): void { + const lineCount = event.textEditor.document.lineCount; + if ( + event.selections[0].start.line + 1 === lineCount || + event.selections[0].end.line + 1 === lineCount + ) { + event.textEditor.setDecorations(this.smallDecorationType, []); + return; + } + + const startOffset = this.getOffset(event.selections[0].start); + const endOffset = this.getOffset(event.selections[0].end); + + if (startOffset === undefined || endOffset === undefined) { + event.textEditor.setDecorations(this.smallDecorationType, []); + return; + } + + let ranges = this.getRanges(startOffset, endOffset, false); + ranges = ranges.concat(this.getRanges(startOffset, endOffset, true)); + event.textEditor.setDecorations(this.smallDecorationType, ranges); + } +} diff --git a/src/frontend/memory_tree_provider.ts b/src/frontend/memory_tree_provider.ts new file mode 100644 index 0000000..79f598c --- /dev/null +++ b/src/frontend/memory_tree_provider.ts @@ -0,0 +1,79 @@ +import * as vscode from 'vscode'; + +export class MemoryTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + public onDidChangeTreeData = this._onDidChangeTreeData.event; + private history: string[] = []; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + dumpSettings(): string[] { + return this.history; + } + + getChildren(element?: vscode.TreeItem): vscode.TreeItem[] { + if (!vscode.debug.activeDebugSession) { + return []; + } + + if (this.history.length) { + return this.getHistoryNodes(); + } + + const item = new vscode.TreeItem('Enter address...'); + item.command = { + title: 'Enter memory address...', + command: 'platformio-debug.examineMemory', + }; + return [item]; + } + + private getHistoryNodes(): vscode.TreeItem[] { + return this.history.map((entry) => { + const item = new vscode.TreeItem(entry); + item.command = { + title: `Examine memory at ${entry}`, + command: 'platformio-debug.examineMemory', + arguments: entry.split('+'), + }; + return item; + }); + } + + getTreeItem(element: vscode.TreeItem): vscode.TreeItem { + return element; + } + + pushHistory(address: string, length: string): void { + const entry = `${address}+${length}`; + if (!this.history.includes(entry)) { + this.history.push(entry); + this.refresh(); + } + } + + deleteHistory(address: string, length: string): void { + const entry = `${address}+${length}`; + if (this.history.includes(entry)) { + this.history = this.history.filter((e) => e !== entry); + this.refresh(); + } + } + + clearHistory(): void { + this.history = []; + this.refresh(); + } + + debugSessionStarted(savedState: string[]): void { + this.history = savedState || []; + this.refresh(); + } + + debugSessionTerminated(): void { + this.history = []; + this.refresh(); + } +} diff --git a/src/frontend/peripheral.ts b/src/frontend/peripheral.ts new file mode 100644 index 0000000..2a00a34 --- /dev/null +++ b/src/frontend/peripheral.ts @@ -0,0 +1,1181 @@ +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as xml2js from 'xml2js'; +import { NumberFormat } from '../common'; +import { + hexFormat, + binaryFormat, + createMask, + extractBits, +} from '../utils'; + +export enum RecordType { + Peripheral = 1, + Register = 2, + Field = 3, + Cluster = 4, +} + +export enum AccessType { + ReadOnly = 1, + ReadWrite = 2, + WriteOnly = 3, +} + +const ACCESS_MAP: { [key: string]: AccessType } = { + 'read-only': AccessType.ReadOnly, + 'write-only': AccessType.WriteOnly, + 'read-write': AccessType.ReadWrite, + 'writeOnce': AccessType.WriteOnly, + 'read-writeOnce': AccessType.ReadWrite, +}; + +export class TreeNode extends vscode.TreeItem { + constructor( + public label: string, + public collapsibleState: vscode.TreeItemCollapsibleState, + public contextValue: string, + public node: BaseNode + ) { + super(label, collapsibleState); + this.command = { + command: 'platformio-debug.peripherals.selectedNode', + arguments: [node], + title: 'Selected Node', + }; + this.tooltip = (node ? node.description : undefined) || label; + } +} + +export class BaseNode { + public expanded: boolean = false; + public format: NumberFormat = NumberFormat.Auto; + public description: string; + + constructor(public recordType: RecordType) {} + + selected(): Promise { + return Promise.resolve(false); + } + + update(): Promise { + return Promise.resolve(false); + } + + performUpdate(): Promise { + return Promise.resolve(false); + } + + getChildren(): BaseNode[] { + return []; + } + + getTreeNode(): TreeNode { + return null; + } + + getCopyValue(): string | null { + return null; + } + + setFormat(format: NumberFormat): void { + this.format = format; + } +} + +function parseInteger(value: string): number | undefined { + if (/^0b([01]+)$/i.test(value)) { + return parseInt(value.substring(2), 2); + } + if (/^0x([0-9a-f]+)$/i.test(value)) { + return parseInt(value.substring(2), 16); + } + if (/^[0-9]+/i.test(value)) { + return parseInt(value, 10); + } + if (/^#[0-1]+/i.test(value)) { + return parseInt(value.substring(1), 2); + } + return undefined; +} + +function parseDimIndex(dimIndex: string, count: number): string[] { + if (dimIndex.indexOf(',') !== -1) { + const items = dimIndex.split(',').map((s) => s.trim()); + if (items.length !== count) { + throw new Error('dimIndex Element has invalid specification.'); + } + return items; + } + + if (/^([0-9]+)\-([0-9]+)$/i.test(dimIndex)) { + const parts = dimIndex.split('-').map((s) => parseInteger(s)); + const start = parts[0]; + if (parts[1] - start + 1 < count) { + throw new Error('dimIndex Element has invalid specification.'); + } + const result: string[] = []; + for (let i = 0; i < count; i++) { + result.push(`${start + i}`); + } + return result; + } + + if (/^[a-zA-Z]\-[a-zA-Z]$/.test(dimIndex)) { + const startChar = dimIndex.charCodeAt(0); + if (dimIndex.charCodeAt(2) - startChar + 1 < count) { + throw new Error('dimIndex Element has invalid specification.'); + } + const result: string[] = []; + for (let i = 0; i < count; i++) { + result.push(String.fromCharCode(startChar + i)); + } + return result; + } + + return []; +} + +class EnumerationValue { + constructor( + public name: string, + public description: string, + public value: number + ) {} +} + +// ============================================================================ +// PeripheralNode +// ============================================================================ + +export class PeripheralNode extends BaseNode { + public name: string; + public description: string; + public baseAddress: number; + public totalLength: number; + public groupName: string; + public resetValue: number; + public size: number; + public children: BaseNode[] = []; + public currentValue: number[]; + + constructor(options: any) { + super(RecordType.Peripheral); + this.name = options.name; + this.description = options.description; + this.baseAddress = options.baseAddress; + this.totalLength = options.totalLength; + this.groupName = options.groupName || ''; + this.resetValue = options.resetValue || 0; + this.size = options.size || 32; + } + + getTreeNode(): TreeNode { + const label = this.name + ' [' + hexFormat(this.baseAddress) + ']'; + return new TreeNode( + label, + this.expanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed, + 'peripheral', + this + ); + } + + getChildren(): BaseNode[] { + return this.children; + } + + setChildren(children: BaseNode[]): void { + this.children = children; + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + addChild(child: BaseNode): void { + this.children.push(child); + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + getBytes(offset: number, size: number): Uint8Array { + try { + return new Uint8Array(this.currentValue.slice(offset, offset + size)); + } catch (e) { + return new Uint8Array(0); + } + } + + getAddress(offset: number): number { + return this.baseAddress + offset; + } + + getFormat(): NumberFormat { + return this.format; + } + + update(): Promise { + return new Promise((resolve, reject) => { + if (this.expanded) { + vscode.debug.activeDebugSession + .customRequest('read-memory', { + address: this.baseAddress, + length: this.totalLength > 32768 ? 32768 : this.totalLength, + }) + .then( + (result: any) => { + this.currentValue = result.bytes; + this.children.forEach((child) => child.update()); + resolve(true); + }, + (error: any) => { + reject(error); + } + ); + } else { + resolve(false); + } + }); + } + + selected(): Promise { + return this.update(); + } + + dumpSettings(): any[] { + const settings: any[] = []; + if (this.format !== NumberFormat.Auto || this.expanded) { + settings.push({ + node: `${this.name}`, + expanded: this.expanded, + format: this.format, + }); + } + this.children.forEach((child: any) => { + settings.push(...child.dumpSettings(`${this.name}`)); + }); + return settings; + } + + _findByPath(path: string[]): BaseNode | null { + if (path.length === 0) { + return this; + } + const child = (this.children as any[]).find((c) => c.name === path[0]); + return child ? child._findByPath(path.slice(1)) : null; + } +} + +// ============================================================================ +// ClusterNode +// ============================================================================ + +export class ClusterNode extends BaseNode { + public name: string; + public description: string; + public offset: number; + public accessType: AccessType; + public size: number; + public resetValue: number; + public children: BaseNode[] = []; + + constructor(public parent: any, options: any) { + super(RecordType.Cluster); + this.name = options.name; + this.description = options.description; + this.offset = options.addressOffset; + this.accessType = options.accessType || AccessType.ReadWrite; + this.size = options.size || parent.size; + this.resetValue = options.resetValue || parent.resetValue; + this.parent.addChild(this); + } + + getTreeNode(): TreeNode { + const label = `${this.name} [${hexFormat(this.offset, 0)}]`; + return new TreeNode( + label, + this.expanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed, + 'cluster', + this + ); + } + + getChildren(): BaseNode[] { + return this.children; + } + + setChildren(children: BaseNode[]): void { + this.children = children.slice(0, children.length); + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + addChild(child: BaseNode): void { + this.children.push(child); + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + getBytes(offset: number, size: number): Uint8Array { + return this.parent.getBytes(this.offset + offset, size); + } + + getAddress(offset: number): number { + return this.parent.getAddress(this.offset + offset); + } + + getFormat(): NumberFormat { + return this.format !== NumberFormat.Auto ? this.format : this.parent.getFormat(); + } + + update(): Promise { + return Promise.resolve(true); + } + + dumpSettings(parentPath: string): any[] { + const settings: any[] = []; + if (this.format !== NumberFormat.Auto || this.expanded) { + settings.push({ + node: `${parentPath}.${this.name}`, + expanded: this.expanded, + format: this.format, + }); + } + this.children.forEach((child: any) => { + settings.push(...child.dumpSettings(`${parentPath}.${this.name}`)); + }); + return settings; + } + + _findByPath(path: string[]): BaseNode | null { + if (path.length === 0) { + return this; + } + const child = (this.children as any[]).find((c) => c.name === path[0]); + return child ? child._findByPath(path.slice(1)) : null; + } +} + +// ============================================================================ +// RegisterNode +// ============================================================================ + +export class RegisterNode extends BaseNode { + public name: string; + public description: string; + public offset: number; + public accessType: AccessType; + public size: number; + public resetValue: number; + public currentValue: number; + public hexLength: number; + public maxValue: number; + public binaryRegex: RegExp; + public hexRegex: RegExp; + public children: BaseNode[] = []; + + constructor(public parent: any, options: any) { + super(RecordType.Register); + this.name = options.name; + this.description = options.description; + this.offset = options.addressOffset; + this.accessType = options.accessType || parent.accessType; + this.size = options.size || parent.size; + this.resetValue = options.resetValue !== undefined ? options.resetValue : parent.resetValue; + this.currentValue = this.resetValue; + this.hexLength = Math.ceil(this.size / 4); + this.maxValue = Math.pow(2, this.size); + this.binaryRegex = new RegExp(`^0b[01]{1,${this.size}}$`, 'i'); + this.hexRegex = new RegExp(`^0x[0-9a-f]{1,${this.hexLength}}$`, 'i'); + this.parent; + this.parent.addChild(this); + } + + reset(): void { + this.currentValue = this.resetValue; + } + + extractBits(offset: number, width: number): number { + return extractBits(this.currentValue, offset, width); + } + + updateBits(offset: number, width: number, value: number): Promise { + return new Promise((resolve, reject) => { + const maxVal = Math.pow(2, width); + if (value > maxVal) { + return reject( + `Value entered is invalid. Maximum value for this field is ${maxVal - 1} (${hexFormat(maxVal - 1, 0)})` + ); + } + const mask = createMask(offset, width); + const shiftedValue = value << offset; + const newValue = (this.currentValue & ~mask) | shiftedValue; + this.updateValueInternal(newValue).then(resolve, reject); + }); + } + + getTreeNode(): TreeNode { + let contextValue = 'registerRW'; + if (this.accessType === AccessType.ReadOnly) { + contextValue = 'registerRO'; + } else if (this.accessType === AccessType.WriteOnly) { + contextValue = 'registerWO'; + } + + let label = `${this.name} [${hexFormat(this.offset, 0)}]`; + if (this.accessType === AccessType.WriteOnly) { + label += ' - '; + } else { + switch (this.getFormat()) { + case NumberFormat.Decimal: + label += ` = ${this.currentValue.toString()}`; + break; + case NumberFormat.Binary: + label += ` = ${binaryFormat(this.currentValue, this.hexLength * 4, false, true)}`; + break; + default: + label += ` = ${hexFormat(this.currentValue, this.hexLength)}`; + break; + } + } + + const collapsible = + this.children && this.children.length > 0 + ? this.expanded + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None; + + return new TreeNode(label, collapsible, contextValue, this); + } + + getChildren(): BaseNode[] { + return this.children || []; + } + + setChildren(children: BaseNode[]): void { + this.children = children.slice(0, children.length); + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + addChild(child: BaseNode): void { + this.children.push(child); + this.children.sort((a: any, b: any) => (a.offset > b.offset ? 1 : -1)); + } + + getFormat(): NumberFormat { + return this.format !== NumberFormat.Auto ? this.format : this.parent.getFormat(); + } + + getCopyValue(): string { + switch (this.getFormat()) { + case NumberFormat.Decimal: + return this.currentValue.toString(); + case NumberFormat.Binary: + return binaryFormat(this.currentValue, this.hexLength * 4); + default: + return hexFormat(this.currentValue, this.hexLength); + } + } + + performUpdate(): Promise { + return new Promise((resolve, reject) => { + vscode.window + .showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)' }) + .then((input) => { + let value: number; + if (input.match(this.hexRegex)) { + value = parseInt(input.substr(2), 16); + } else if (input.match(this.binaryRegex)) { + value = parseInt(input.substr(2), 2); + } else if (input.match(/^[0-9]+/)) { + value = parseInt(input, 10); + if (value >= this.maxValue) { + return reject( + `Value entered (${value}) is greater than the maximum value of ${this.maxValue}` + ); + } + } else { + return reject('Value entered is not a valid format.'); + } + this.updateValueInternal(value).then(resolve, reject); + }); + }); + } + + private updateValueInternal(newValue: number): Promise { + const address = this.parent.getAddress(this.offset); + const bytes: string[] = []; + const byteCount = this.size / 8; + + for (let i = 0; i < byteCount; i++) { + const byte = newValue & 0xff; + newValue >>>= 8; + let hexByte = byte.toString(16); + if (hexByte.length === 1) { + hexByte = '0' + hexByte; + } + bytes[i] = hexByte; + } + + return new Promise((resolve, reject) => { + vscode.debug.activeDebugSession + .customRequest('write-memory', { address, data: bytes.join('') }) + .then( + (result: any) => { + this.parent.update().then( + () => {}, + () => {} + ); + resolve(true); + }, + reject + ); + }); + } + + update(): Promise { + const byteCount = this.size / 8; + const bytes = this.parent.getBytes(this.offset, byteCount); + const buffer = Buffer.from(bytes); + + switch (byteCount) { + case 1: + this.currentValue = buffer.readUInt8(0); + break; + case 2: + this.currentValue = buffer.readUInt16LE(0); + break; + case 4: + this.currentValue = buffer.readUInt32LE(0); + break; + default: + vscode.window.showErrorMessage( + `Register ${this.name} has invalid size: ${this.size}. Should be 8, 16 or 32.` + ); + } + + this.children.forEach((child) => child.update()); + return Promise.resolve(true); + } + + dumpSettings(parentPath: string): any[] { + const settings: any[] = []; + if (this.format !== NumberFormat.Auto || this.expanded) { + settings.push({ + node: `${parentPath}.${this.name}`, + expanded: this.expanded, + format: this.format, + }); + } + this.children.forEach((child: any) => { + settings.push(...child.dumpSettings(`${parentPath}.${this.name}`)); + }); + return settings; + } + + _findByPath(path: string[]): BaseNode | null { + if (path.length === 0) { + return this; + } + if (path.length === 1) { + return (this.children as any[]).find((c) => c.name === path[0]); + } + return null; + } +} + +// ============================================================================ +// FieldNode +// ============================================================================ + +export class FieldNode extends BaseNode { + public name: string; + public description: string; + public offset: number; + public width: number; + public accessType: AccessType; + public enumeration: any; + public enumerationMap: { [name: string]: number }; + public enumerationValues: string[]; + + constructor(public parent: RegisterNode, options: any) { + super(RecordType.Field); + this.name = options.name; + this.description = options.description; + this.offset = options.offset; + this.width = options.width; + + if (options.accessType) { + if (parent.accessType === AccessType.ReadOnly && options.accessType !== AccessType.ReadOnly) { + this.accessType = AccessType.ReadOnly; + } else if (parent.accessType === AccessType.WriteOnly && options.accessType !== AccessType.WriteOnly) { + this.accessType = AccessType.WriteOnly; + } else { + this.accessType = options.accessType; + } + } else { + this.accessType = parent.accessType; + } + + if (options.enumeration) { + this.enumeration = options.enumeration; + this.enumerationMap = {}; + this.enumerationValues = []; + for (const key in options.enumeration) { + const name = options.enumeration[key].name; + this.enumerationValues.push(name); + this.enumerationMap[name] = key as any; + } + } + + this.parent.addChild(this); + } + + getTreeNode(): TreeNode { + const value = this.parent.extractBits(this.offset, this.width); + let enumEntry: EnumerationValue | null = null; + let label = this.name; + const startBit = this.offset; + let contextValue = 'field'; + + label += `[${this.offset + this.width - 1}:${startBit}]`; + + if (this.name.toLowerCase() === 'reserved') { + contextValue = 'field-res'; + } else if (this.accessType === AccessType.WriteOnly) { + label += ' - '; + } else { + let formattedValue = ''; + switch (this.getFormat()) { + case NumberFormat.Decimal: + formattedValue = value.toString(); + break; + case NumberFormat.Binary: + formattedValue = binaryFormat(value, this.width); + break; + case NumberFormat.Hexidecimal: + formattedValue = hexFormat(value, Math.ceil(this.width / 4), true); + break; + default: + formattedValue = + this.width >= 4 + ? hexFormat(value, Math.ceil(this.width / 4), true) + : binaryFormat(value, this.width); + break; + } + + if (this.enumeration && this.enumeration[value]) { + enumEntry = this.enumeration[value]; + label += ` = ${enumEntry.name} (${formattedValue})`; + } else { + label += ` = ${formattedValue}`; + } + } + + if (this.parent.accessType === AccessType.ReadOnly) { + contextValue = 'field-ro'; + } + + return new TreeNode(label, vscode.TreeItemCollapsibleState.None, contextValue, this); + } + + performUpdate(): Promise { + return new Promise((resolve, reject) => { + if (this.enumeration) { + vscode.window.showQuickPick(this.enumerationValues).then( + (selected) => { + if (selected === undefined) { + return reject('Input not selected'); + } + const value = this.enumerationMap[selected]; + this.parent.updateBits(this.offset, this.width, value as any).then(resolve, reject); + } + ); + } else { + vscode.window + .showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)' }) + .then((input) => { + const value = parseInteger(input); + if (value === undefined) { + return reject('Unable to parse input value.'); + } + this.parent.updateBits(this.offset, this.width, value).then(resolve, reject); + }); + } + }); + } + + getCopyValue(): string { + const value = this.parent.extractBits(this.offset, this.width); + switch (this.getFormat()) { + case NumberFormat.Decimal: + return value.toString(); + case NumberFormat.Binary: + return binaryFormat(value, this.width); + case NumberFormat.Hexidecimal: + return hexFormat(value, Math.ceil(this.width / 4), true); + default: + return this.width >= 4 + ? hexFormat(value, Math.ceil(this.width / 4), true) + : binaryFormat(value, this.width); + } + } + + getFormat(): NumberFormat { + return this.format !== NumberFormat.Auto ? this.format : this.parent.getFormat(); + } + + dumpSettings(parentPath: string): any[] { + if (this.format !== NumberFormat.Auto) { + return [{ node: `${parentPath}.${this.name}`, format: this.format }]; + } + return []; + } + + _findByPath(path: string[]): BaseNode | null { + return path.length === 0 ? this : null; + } +} + +// ============================================================================ +// PeripheralTreeProvider +// ============================================================================ + +export class PeripheralTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + public onDidChangeTreeData = this._onDidChangeTreeData.event; + private peripherials: PeripheralNode[] = []; + private loaded: boolean = false; + private viewExpanded: boolean = false; + private svdPath: string; + private initialSettings: any[]; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + dumpSettings(): any[] { + const settings: any[] = []; + this.peripherials.forEach((peripheral) => { + settings.push(...peripheral.dumpSettings()); + }); + return settings; + } + + _parseFields(fieldDefs: any[], parent: RegisterNode): FieldNode[] { + const fields: FieldNode[] = []; + fieldDefs.map((field) => { + let offset: number; + let width: number; + const description = field.description ? field.description[0] : ''; + + if (field.bitOffset && field.bitWidth) { + offset = parseInteger(field.bitOffset[0]); + width = parseInteger(field.bitWidth[0]); + } else if (field.bitRange) { + let range = field.bitRange[0]; + range = range.substring(1, range.length - 1); + range = range.split(':'); + const msb = parseInteger(range[0]); + const lsb = parseInteger(range[1]); + width = msb - lsb + 1; + offset = lsb; + } else if (field.msb && field.lsb) { + const msb = parseInteger(field.msb[0]); + const lsb = parseInteger(field.lsb[0]); + width = msb - lsb + 1; + offset = lsb; + } else { + throw new Error( + `Unable to parse SVD file: field ${field.name[0]} must have either bitOffset and bitWidth elements, bitRange Element, or msb and lsb elements.` + ); + } + + let enumeration: any = null; + if (field.enumeratedValues) { + enumeration = {}; + field.enumeratedValues[0].enumeratedValue.map((enumVal: any) => { + if (enumVal.value && enumVal.value.length > 0) { + const name = enumVal.name[0]; + const desc = enumVal.description ? enumVal.description[0] : name; + const val = parseInteger(enumVal.value[0].toLowerCase()); + enumeration[val] = new EnumerationValue(name, desc, val); + } + }); + } + + const fieldOptions: any = { + name: field.name[0], + description, + offset, + width, + enumeration, + }; + + if (field.dim) { + if (!field.dimIncrement) { + throw new Error( + `Unable to parse SVD file: field ${field.name[0]} has dim element, with no dimIncrement element.` + ); + } + const dimCount = parseInteger(field.dim[0]); + const dimIncrement = parseInteger(field.dimIncrement[0]); + let dimIndices: string[] = []; + if (field.dimIndex) { + dimIndices = parseDimIndex(field.dimIndex[0], dimCount); + } else { + for (let i = 0; i < dimCount; i++) { + dimIndices.push(`${i}`); + } + } + const baseName = field.name[0]; + const baseOffset = offset; + for (let i = 0; i < dimCount; i++) { + const name = baseName.replace('%s', dimIndices[i]); + fields.push( + new FieldNode(parent, { + ...fieldOptions, + name, + offset: baseOffset + dimIncrement * i, + }) + ); + } + } else { + fields.push(new FieldNode(parent, { ...fieldOptions })); + } + }); + return fields; + } + + _parseRegisters(registerDefs: any[], parent: any): RegisterNode[] { + const registers: RegisterNode[] = []; + registerDefs.forEach((reg) => { + const options: any = {}; + if (reg.description) { + options.description = reg.description[0]; + } + if (reg.access) { + options.accessType = ACCESS_MAP[reg.access[0]]; + } + if (reg.size) { + options.size = parseInteger(reg.size[0]); + } + if (reg.resetValue) { + options.resetValue = parseInteger(reg.resetValue[0]); + } + + if (reg.dim) { + if (!reg.dimIncrement) { + throw new Error( + `Unable to parse SVD file: register ${reg.name[0]} has dim element, with no dimIncrement element.` + ); + } + const dimCount = parseInteger(reg.dim[0]); + const dimIncrement = parseInteger(reg.dimIncrement[0]); + let dimIndices: string[] = []; + if (reg.dimIndex) { + dimIndices = parseDimIndex(reg.dimIndex[0], dimCount); + } else { + for (let i = 0; i < dimCount; i++) { + dimIndices.push(`${i}`); + } + } + const baseName = reg.name[0]; + const baseOffset = parseInteger(reg.addressOffset[0]); + for (let i = 0; i < dimCount; i++) { + const name = baseName.replace('%s', dimIndices[i]); + const registerNode = new RegisterNode(parent, { + ...options, + name, + addressOffset: baseOffset + dimIncrement * i, + }); + if (reg.fields && reg.fields.length === 1) { + this._parseFields(reg.fields[0].field, registerNode); + } + registers.push(registerNode); + } + } else { + const registerNode = new RegisterNode(parent, { + ...options, + name: reg.name[0], + addressOffset: parseInteger(reg.addressOffset[0]), + }); + if (reg.fields && reg.fields.length === 1) { + this._parseFields(reg.fields[0].field, registerNode); + } + registers.push(registerNode); + } + }); + registers.sort((a, b) => (a.offset < b.offset ? -1 : a.offset > b.offset ? 1 : 0)); + return registers; + } + + _parseClusters(clusterDefs: any[], parent: any): ClusterNode[] { + const clusters: ClusterNode[] = []; + if (!clusterDefs) { + return []; + } + clusterDefs.forEach((cluster) => { + const options: any = {}; + if (cluster.description) { + options.description = cluster.description[0]; + } + if (cluster.access) { + options.accessType = ACCESS_MAP[cluster.access[0]]; + } + if (cluster.size) { + options.size = parseInteger(cluster.size[0]); + } + if (cluster.resetValue) { + options.resetValue = parseInteger(cluster.resetValue); + } + + if (cluster.dim) { + if (!cluster.dimIncrement) { + throw new Error( + `Unable to parse SVD file: cluster ${cluster.name[0]} has dim element, with no dimIncrement element.` + ); + } + const dimCount = parseInteger(cluster.dim[0]); + const dimIncrement = parseInteger(cluster.dimIncrement[0]); + let dimIndices: string[] = []; + if (cluster.dimIndex) { + dimIndices = parseDimIndex(cluster.dimIndex[0], dimCount); + } else { + for (let i = 0; i < dimCount; i++) { + dimIndices.push(`${i}`); + } + } + const baseName = cluster.name[0]; + const baseOffset = parseInteger(cluster.addressOffset[0]); + for (let i = 0; i < dimCount; i++) { + const name = baseName.replace('%s', dimIndices[i]); + const clusterNode = new ClusterNode(parent, { + ...options, + name, + addressOffset: baseOffset + dimIncrement * i, + }); + if (cluster.register) { + this._parseRegisters(cluster.register, clusterNode); + } + clusters.push(clusterNode); + } + } else { + const clusterNode = new ClusterNode(parent, { + ...options, + name: cluster.name[0], + addressOffset: parseInteger(cluster.addressOffset[0]), + }); + if (cluster.register) { + this._parseRegisters(cluster.register, clusterNode); + clusters.push(clusterNode); + } + } + }); + return clusters; + } + + _parsePeripheral(peripheralDef: any, defaults: any): PeripheralNode { + const totalLength = parseInteger(peripheralDef.addressBlock[0].size[0]); + const options: any = { + name: peripheralDef.name[0], + baseAddress: parseInteger(peripheralDef.baseAddress[0]), + description: peripheralDef.description[0], + totalLength, + }; + + if (peripheralDef.access) { + options.accessType = ACCESS_MAP[peripheralDef.access[0]]; + } + if (peripheralDef.size) { + options.size = parseInteger(peripheralDef.size[0]); + } + if (peripheralDef.resetValue) { + options.resetValue = parseInteger(peripheralDef.resetValue[0]); + } + if (peripheralDef.groupName) { + options.groupName = peripheralDef.groupName[0]; + } + + const peripheral = new PeripheralNode(options); + + if (peripheralDef.registers[0].register) { + this._parseRegisters(peripheralDef.registers[0].register, peripheral); + } + if (peripheralDef.registers[0].cluster) { + this._parseClusters(peripheralDef.registers[0].cluster, peripheral); + } + + return peripheral; + } + + _loadSVD(svdPath: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(svdPath, 'utf8', (err, data) => { + if (err) { + return reject(err); + } + xml2js.parseString(data, (parseErr: any, result: any) => { + if (parseErr) { + return reject(parseErr); + } + try { + const peripheralMap: { [name: string]: any } = {}; + const defaults: any = { + accessType: AccessType.ReadWrite, + size: 32, + resetValue: 0, + }; + + if (result.device.resetValue) { + defaults.resetValue = parseInteger(result.device.resetValue[0]); + } + if (result.device.size) { + defaults.size = parseInteger(result.device.size[0]); + } + if (result.device.access) { + defaults.accessType = ACCESS_MAP[result.device.access[0]]; + } + + result.device.peripherals[0].peripheral.forEach((periph: any) => { + const name = periph.name[0]; + peripheralMap[name] = periph; + }); + + // Handle derived peripherals + for (const name in peripheralMap) { + const periph = peripheralMap[name]; + if (periph.$ && periph.$.derivedFrom) { + const base = peripheralMap[periph.$.derivedFrom]; + peripheralMap[name] = { ...base, ...periph }; + } + } + + this.peripherials = []; + for (const name in peripheralMap) { + this.peripherials.push(this._parsePeripheral(peripheralMap[name], defaults)); + } + + this.peripherials.sort((a, b) => + a.groupName > b.groupName + ? 1 + : a.groupName < b.groupName + ? -1 + : a.name > b.name + ? 1 + : a.name < b.name + ? -1 + : 0 + ); + + return resolve(true); + } catch (e) { + return reject(e); + } + }); + }); + }); + } + + _findNodeByPath(path: string): BaseNode | null { + const parts = path.split('.'); + const peripheral = this.peripherials.find((p) => p.name === parts[0]); + return peripheral ? peripheral._findByPath(parts.slice(1)) : null; + } + + getTreeItem(element: TreeNode): TreeNode { + return element; + } + + getChildren(element?: TreeNode): TreeNode[] { + this.viewExpanded = true; + if (!vscode.debug.activeDebugSession) { + return []; + } + + if (this.peripherials.length > 0) { + if (element) { + return element.node.getChildren().map((child) => child.getTreeNode()); + } + return this.peripherials.map((p) => p.getTreeNode()); + } + + if (!this.loaded) { + this._update(); + } + + return [ + new TreeNode( + this.svdPath ? 'Loading...' : 'No Information', + vscode.TreeItemCollapsibleState.None, + 'message', + null + ), + ]; + } + + private async _load(): Promise { + if (this.svdPath) { + this.loaded = true; + this.peripherials = []; + return new Promise((resolve) => { + setTimeout(async () => { + try { + await this._loadSVD(this.svdPath); + if (this.initialSettings) { + this.initialSettings.forEach((setting) => { + const node = this._findNodeByPath(setting.node); + if (node) { + node.expanded = setting.expanded || false; + node.format = setting.format; + } + }); + } + } catch (e) { + this.peripherials = []; + vscode.window.showErrorMessage(`Unable to parse SVD file: ${e.toString()}`); + } + resolve(true); + }, 1000); + }); + } + } + + private async _update(): Promise { + if (this.viewExpanded) { + if (!this.loaded) { + await this._load(); + } + try { + await Promise.all(this.peripherials.map((p) => p.update())); + } catch (e) { + // Ignore update errors + } + this.refresh(); + } + } + + onDidExpandElement(event: any): void { + event.element.node.expanded = true; + event.element.node.update(); + this.refresh(); + } + + onDidCollapseElement(event: any): void { + event.element.node.expanded = false; + } + + debugSessionStarted(svdPath: string, savedState: any[]): void { + this.peripherials = []; + this.loaded = false; + this.svdPath = svdPath; + this.initialSettings = savedState; + } + + debugSessionTerminated(): void { + this.peripherials = []; + this.loaded = false; + this.refresh(); + } + + debugStopped(): Promise { + return this._update(); + } + + debugContinued(): void {} +} diff --git a/src/frontend/registers.ts b/src/frontend/registers.ts new file mode 100644 index 0000000..e37f9e5 --- /dev/null +++ b/src/frontend/registers.ts @@ -0,0 +1,360 @@ +import * as vscode from 'vscode'; +import { NumberFormat } from '../common'; +import { hexFormat, binaryFormat, extractBits } from '../utils'; + +export enum RecordType { + Register = 0, + Field = 1, +} + +export class TreeNode extends vscode.TreeItem { + constructor( + public label: string, + public collapsibleState: vscode.TreeItemCollapsibleState, + public contextValue: string, + public node: BaseNode + ) { + super(label, collapsibleState); + this.command = { + command: 'platformio-debug.registers.selectedNode', + arguments: [node], + title: 'Selected Node', + }; + } +} + +export class BaseNode { + public format: NumberFormat = NumberFormat.Auto; + public expanded: boolean = false; + + constructor(public recordType: RecordType) {} + + getChildren(): BaseNode[] { + return []; + } + + getTreeNode(): TreeNode { + return null; + } + + getCopyValue(): string | null { + return null; + } + + setFormat(format: NumberFormat): void { + this.format = format; + } +} + +export class RegisterNode extends BaseNode { + public name: string; + public index: number; + public fields: FieldNode[]; + public currentValue: number; + + constructor(name: string, index: number) { + super(RecordType.Register); + this.name = name; + this.index = index; + + if (name.toUpperCase() === 'XPSR' || name.toUpperCase() === 'CPSR') { + this.fields = [ + new FieldNode('Negative Flag (N)', 31, 1, this), + new FieldNode('Zero Flag (Z)', 30, 1, this), + new FieldNode('Carry or borrow flag (C)', 29, 1, this), + new FieldNode('Overflow Flag (V)', 28, 1, this), + new FieldNode('Saturation Flag (Q)', 27, 1, this), + new FieldNode('GE', 16, 4, this), + new FieldNode('Interrupt Number', 0, 8, this), + new FieldNode('ICI/IT', 25, 2, this), + new FieldNode('ICI/IT', 10, 6, this), + new FieldNode('Thumb State (T)', 24, 1, this), + ]; + } else if (name.toUpperCase() === 'CONTROL') { + this.fields = [ + new FieldNode('FPCA', 2, 1, this), + new FieldNode('SPSEL', 1, 1, this), + new FieldNode('nPRIV', 0, 1, this), + ]; + } + + this.currentValue = 0; + } + + extractBits(offset: number, width: number): number { + return extractBits(this.currentValue, offset, width); + } + + getTreeNode(): TreeNode { + let label = `${this.name} = `; + switch (this.getFormat()) { + case NumberFormat.Decimal: + label += this.currentValue.toString(); + break; + case NumberFormat.Binary: + label += binaryFormat(this.currentValue, 32, false, true); + break; + default: + label += hexFormat(this.currentValue, 8); + break; + } + + if (this.fields && this.fields.length > 0) { + return new TreeNode( + label, + this.expanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed, + 'register', + this + ); + } + return new TreeNode(label, vscode.TreeItemCollapsibleState.None, 'register', this); + } + + getChildren(): BaseNode[] { + return this.fields; + } + + setValue(value: number): void { + this.currentValue = value; + } + + getCopyValue(): string { + switch (this.getFormat()) { + case NumberFormat.Decimal: + return this.currentValue.toString(); + case NumberFormat.Binary: + return binaryFormat(this.currentValue, 32); + default: + return hexFormat(this.currentValue, 8); + } + } + + getFormat(): NumberFormat { + return this.format; + } + + dumpSettings(): any[] { + const settings: any[] = []; + if (this.expanded || this.format !== NumberFormat.Auto) { + settings.push({ + node: this.name, + format: this.format, + expanded: this.expanded, + }); + } + if (this.fields) { + settings.push( + ...this.fields.map((field) => field.dumpSettings()).filter((s) => s !== null) + ); + } + return settings; + } +} + +export class FieldNode extends BaseNode { + constructor( + public name: string, + public offset: number, + public size: number, + public register: RegisterNode + ) { + super(RecordType.Field); + } + + getTreeNode(): TreeNode { + const value = this.register.extractBits(this.offset, this.size); + let label = `${this.name} = `; + + switch (this.getFormat()) { + case NumberFormat.Decimal: + label += value.toString(); + break; + case NumberFormat.Binary: + label += binaryFormat(value, this.size, false, true); + break; + case NumberFormat.Hexidecimal: + label += hexFormat(value, Math.ceil(this.size / 4), true); + break; + default: + label += + this.size >= 4 + ? hexFormat(value, Math.ceil(this.size / 4), true) + : binaryFormat(value, this.size, false, true); + break; + } + + return new TreeNode(label, vscode.TreeItemCollapsibleState.None, 'field', this); + } + + getCopyValue(): string { + const value = this.register.extractBits(this.offset, this.size); + switch (this.getFormat()) { + case NumberFormat.Decimal: + return value.toString(); + case NumberFormat.Binary: + return binaryFormat(value, this.size); + case NumberFormat.Hexidecimal: + return hexFormat(value, Math.ceil(this.size / 4), true); + default: + return this.size >= 4 + ? hexFormat(value, Math.ceil(this.size / 4), true) + : binaryFormat(value, this.size); + } + } + + getFormat(): NumberFormat { + return this.format === NumberFormat.Auto ? this.register.getFormat() : this.format; + } + + dumpSettings(): any { + if (this.format !== NumberFormat.Auto) { + return { node: `${this.register.name}.${this.name}`, format: this.format }; + } + return null; + } +} + +export class RegisterTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + public onDidChangeTreeData = this._onDidChangeTreeData.event; + private loaded: boolean = false; + private viewExpanded: boolean = false; + private registers: RegisterNode[] = []; + private registerMap: { [index: number]: RegisterNode } = {}; + private initialSettings: any[]; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + dumpSettings(): any[] { + const settings: any[] = []; + this.registers.forEach((reg) => { + settings.push(...reg.dumpSettings()); + }); + return settings; + } + + fetchRegisterList(): void { + if (!vscode.debug.activeDebugSession) { + return; + } + + if (this.loaded) { + this._fetchRegisterValues(); + } else { + vscode.debug.activeDebugSession.customRequest('read-register-list').then((names: any) => { + this.loaded = true; + this.createRegisters(names); + this._fetchRegisterValues(); + }); + } + } + + private _fetchRegisterValues(): void { + vscode.debug.activeDebugSession.customRequest('read-registers').then((registers: any) => { + registers.forEach((reg: any) => { + const index = parseInt(reg.number, 10); + const value = parseInt(reg.value, 16); + const registerNode = this.registerMap[index]; + if (registerNode) { + registerNode.setValue(value); + } + }); + this.refresh(); + }); + } + + getTreeItem(element: TreeNode): TreeNode { + return element; + } + + private createRegisters(names: string[]): void { + this.registerMap = {}; + this.registers = []; + + names.forEach((name, index) => { + if (name) { + const reg = new RegisterNode(name, index); + this.registers.push(reg); + this.registerMap[index] = reg; + } + }); + + if (this.initialSettings) { + this.initialSettings.forEach((setting) => { + if (setting.node.indexOf('.') === -1) { + const reg = this.registers.find((r) => r.name === setting.node); + if (reg) { + if (setting.expanded) { + reg.expanded = setting.expanded; + } + if (setting.format) { + reg.setFormat(setting.format); + } + } + } else { + const [regName, fieldName] = setting.node.split('.'); + const reg = this.registers.find((r) => r.name === regName); + if (reg) { + const field = reg.getChildren().find((f: any) => f.name === fieldName); + if (field && setting.format) { + field.setFormat(setting.format); + } + } + } + }); + } + + this.refresh(); + } + + updateRegisterValues(values: any[]): void { + values.forEach((val) => { + this.registerMap[val.number].setValue(val.value); + }); + this.refresh(); + } + + getChildren(element?: TreeNode): any[] { + this.viewExpanded = true; + if (!vscode.debug.activeDebugSession) { + return []; + } + + if (this.registers.length > 0) { + if (element) { + return element.node.getChildren().map((child) => child.getTreeNode()); + } + return this.registers.map((reg) => reg.getTreeNode()); + } + + if (!this.loaded) { + setTimeout(() => this.fetchRegisterList(), 1000); + } + + return [new TreeNode('Loading...', vscode.TreeItemCollapsibleState.None, 'message', null)]; + } + + debugSessionTerminated(): void { + this.loaded = false; + this.registers = []; + this.registerMap = {}; + this.refresh(); + } + + debugSessionStarted(savedState: any[]): void { + this.loaded = false; + this.registers = []; + this.registerMap = {}; + this.initialSettings = savedState; + } + + debugStopped(): void { + if (this.viewExpanded) { + this.fetchRegisterList(); + } + } + + debugContinued(): void {} +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..a4d0327 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,63 @@ +export function hexFormat(value: number, padding: number = 8, includePrefix: boolean = true): string { + let result = value.toString(16); + while (result.length < padding) { + result = '0' + result; + } + return includePrefix ? '0x' + result : result; +} + +export function binaryFormat( + value: number, + padding: number = 0, + includePrefix: boolean = true, + groupByNibble: boolean = false +): string { + let result = (value >>> 0).toString(2); + while (result.length < padding) { + result = '0' + result; + } + + if (groupByNibble) { + const extraZeros = 4 - (result.length % 4); + for (let i = 0; i < extraZeros; i++) { + result = '0' + result; + } + const groups = result.match(/[01]{4}/g); + result = groups.join(' '); + result = result.substring(extraZeros); + } + + return includePrefix ? '0b' + result : result; +} + +export function createMask(offset: number, width: number): number { + let mask = 0; + const end = offset + width - 1; + for (let i = offset; i <= end; i++) { + mask = (mask | (1 << i)) >>> 0; + } + return mask; +} + +export function extractBits(value: number, offset: number, width: number): number { + return ((value & createMask(offset, width)) >>> offset) >>> 0; +} + +export function parseQuery(queryString: string): { [key: string]: string } { + const params: { [key: string]: string } = {}; + const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&'); + for (const pair of pairs) { + const parts = pair.split('='); + params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1] || ''); + } + return params; +} + +export function encodeDisassembly(name: string, file: string): string { + let uri = 'disassembly:///'; + if (file) { + uri += `${file}:`; + } + uri += `${name}.dbgasm?func=${name}&file=${file || ''}`; + return uri; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c8b6e27 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": ["es6"], + "sourceMap": true, + "strict": false, + "rootDir": "src", + "skipLibCheck": true, + "esModuleInterop": true + }, + "exclude": ["node_modules", "dist", ".vscode-test"] +}