Windows DAP debugger and some misc fixes

This commit is contained in:
FalsePattern 2024-11-07 14:49:43 +01:00
parent f8def6e1de
commit f7014b49eb
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
16 changed files with 951 additions and 94 deletions

View file

@ -1,9 +1,26 @@
import de.undercouch.gradle.tasks.download.Download
import org.jetbrains.intellij.platform.gradle.Constants import org.jetbrains.intellij.platform.gradle.Constants
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
plugins {
id("de.undercouch.download") version("5.6.0")
}
val lsp4jVersion: String by project val lsp4jVersion: String by project
val clionVersion: String by project val clionVersion: String by project
val genOutputDir = layout.buildDirectory.dir("generated-resources")
sourceSets["main"].resources.srcDir(genOutputDir)
tasks {
register<Download>("downloadProps") {
src("https://falsepattern.com/zigbrains/msvc.properties")
dest(genOutputDir.map { it.file("msvc.properties") })
}
processResources {
dependsOn("downloadProps")
}
}
dependencies { dependencies {
intellijPlatform { intellijPlatform {
create(IntelliJPlatformType.CLion, providers.gradleProperty("clionVersion")) create(IntelliJPlatformType.CLion, providers.gradleProperty("clionVersion"))

View file

@ -30,6 +30,7 @@ import java.io.PipedOutputStream
class BlockingPipedInputStream(src: PipedOutputStream, pipeSize: Int) : PipedInputStream(src, pipeSize) { class BlockingPipedInputStream(src: PipedOutputStream, pipeSize: Int) : PipedInputStream(src, pipeSize) {
var closed = false var closed = false
@Synchronized
override fun read(): Int { override fun read(): Int {
if (closed) { if (closed) {
throw IOException("stream closed") throw IOException("stream closed")

View file

@ -22,24 +22,31 @@
package com.falsepattern.zigbrains.debugger.dap package com.falsepattern.zigbrains.debugger.dap
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.process.BaseProcessHandler import com.intellij.execution.process.BaseProcessHandler
import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessListener
import com.intellij.execution.process.ProcessOutputType import com.intellij.execution.process.ProcessOutputType
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.KeyWithDefaultValue import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.progress.getMaybeCancellable import com.intellij.util.progress.getMaybeCancellable
import com.intellij.util.system.CpuArch import com.intellij.util.system.CpuArch
import com.jetbrains.cidr.ArchitectureType import com.jetbrains.cidr.ArchitectureType
import com.jetbrains.cidr.execution.Installer import com.jetbrains.cidr.execution.Installer
import com.jetbrains.cidr.execution.debugger.CidrDebuggerSettings
import com.jetbrains.cidr.execution.debugger.backend.* import com.jetbrains.cidr.execution.debugger.backend.*
import com.jetbrains.cidr.execution.debugger.memory.Address import com.jetbrains.cidr.execution.debugger.memory.Address
import com.jetbrains.cidr.execution.debugger.memory.AddressRange
import com.jetbrains.cidr.system.HostMachine import com.jetbrains.cidr.system.HostMachine
import com.jetbrains.cidr.system.LocalHost import com.jetbrains.cidr.system.LocalHost
import io.ktor.util.* import io.ktor.util.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.asDeferred import kotlinx.coroutines.future.asDeferred
import org.eclipse.lsp4j.debug.* import org.eclipse.lsp4j.debug.*
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient import org.eclipse.lsp4j.debug.services.IDebugProtocolClient
@ -47,13 +54,15 @@ import org.eclipse.lsp4j.debug.services.IDebugProtocolServer
import org.eclipse.lsp4j.jsonrpc.MessageConsumer import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher
import org.eclipse.lsp4j.jsonrpc.messages.Either3 import org.eclipse.lsp4j.jsonrpc.messages.Either3
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.OutputStream
import java.io.PipedOutputStream import java.io.PipedOutputStream
import java.lang.Exception import java.lang.Exception
import java.nio.file.Path import java.util.TreeMap
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.io.path.pathString import kotlin.math.min
abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolClient>( abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolClient>(
handler: Handler, handler: Handler,
@ -66,6 +75,41 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
protected lateinit var capabilities: Capabilities protected lateinit var capabilities: Capabilities
protected lateinit var initializeFuture: Job protected lateinit var initializeFuture: Job
protected val breakpoints = HashMap<Int, MappedBreakpoint>()
protected val modules = HashMap<Int, MappedModule>()
private val registerSets = TreeMap<String, List<LLValue>>()
@Volatile
private var childProcess: BaseProcessHandler<*>? = null
@Volatile
private var processInput: OutputStream? = null
@Volatile
private var dummyOutput: ByteArrayOutputStream? = ByteArrayOutputStream()
private val multiplexer = object: OutputStream() {
private val inferior get() = processInput ?: dummyOutput
override fun close() {
inferior?.close()
}
override fun flush() {
inferior?.flush()
}
override fun write(b: Int) {
inferior?.write(b)
}
override fun write(b: ByteArray) {
inferior?.write(b)
}
override fun write(b: ByteArray, off: Int, len: Int) {
inferior?.write(b, off, len)
}
}
protected abstract fun createDebuggerClient(): Client protected abstract fun createDebuggerClient(): Client
protected abstract fun getServerInterface(): Class<Server> protected abstract fun getServerInterface(): Class<Server>
protected abstract fun wrapMessageConsumer(mc: MessageConsumer): MessageConsumer protected abstract fun wrapMessageConsumer(mc: MessageConsumer): MessageConsumer
@ -213,30 +257,34 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
notSupported() notSupported()
} }
/**
* User presses "Pause Program" button.
* {@link #handleInterrupted} supposed to be called asynchronously when actual pause happened
*/
override fun interrupt(): Boolean { override fun interrupt(): Boolean {
val pause = PauseArguments() val pause = PauseArguments()
pause.threadId = -1 pause.threadId = -1
server.pause(pause) server.pause(pause).getSafe()
return true return true
} }
override fun resume(): Boolean { override fun resume(): Boolean {
val args = ContinueArguments() val args = ContinueArguments()
server.continue_(args) server.continue_(args).getSafe()
return true return true
} }
@Suppress("OVERRIDE_DEPRECATION") @Deprecated("Inherited from deprecated")
override fun stepOver(p0: Boolean) { override fun stepOver(p0: Boolean) {
deprecated() deprecated()
} }
@Suppress("OVERRIDE_DEPRECATION") @Deprecated("Inherited from deprecated")
override fun stepInto(p0: Boolean, p1: Boolean) { override fun stepInto(p0: Boolean, p1: Boolean) {
deprecated() deprecated()
} }
@Suppress("OVERRIDE_DEPRECATION") @Deprecated("Inherited from deprecated")
override fun stepOut(p0: Boolean) { override fun stepOut(p0: Boolean) {
deprecated() deprecated()
} }
@ -248,44 +296,57 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
SteppingGranularity.INSTRUCTION SteppingGranularity.INSTRUCTION
else else
SteppingGranularity.LINE SteppingGranularity.LINE
server.next(args) server.next(args).getSafe()
} }
override fun stepInto(thread: LLThread, forceStepIntoFramesWithNoDebugInfo: Boolean, stepByInstruction: Boolean) { override fun stepInto(thread: LLThread, forceStepIntoFramesWithNoDebugInfo: Boolean, stepByInstruction: Boolean) {
val args = StepInArguments() val args = StepInArguments()
args.targetId = Math.toIntExact(thread.id) args.threadId = Math.toIntExact(thread.id)
args.granularity = if (stepByInstruction) args.granularity = if (stepByInstruction)
SteppingGranularity.INSTRUCTION SteppingGranularity.INSTRUCTION
else else
SteppingGranularity.LINE SteppingGranularity.LINE
server.stepIn(args) server.stepIn(args).getSafe()
} }
override fun stepOut(thread: LLThread, stopInFramesWithNoDebugInfo: Boolean) { override fun stepOut(thread: LLThread, stopInFramesWithNoDebugInfo: Boolean) {
val args = StepOutArguments() val args = StepOutArguments()
args.threadId = Math.toIntExact(thread.id) args.threadId = Math.toIntExact(thread.id)
server.stepOut(args) server.stepOut(args).getSafe()
} }
/**
* Run to source file line
*
* @see #stepOver
*/
override fun runTo(path: String, line: Int) { override fun runTo(path: String, line: Int) {
val targetArgs = GotoTargetsArguments() val targetArgs = GotoTargetsArguments()
val src = Util.toSource(path) val src = Util.toSource(path)
targetArgs.source = src targetArgs.source = src
targetArgs.line = line targetArgs.line = line
zigCoroutineScope.launch { val locations = server.gotoTargets(targetArgs).getSafe()
val locations = server.gotoTargets(targetArgs).asDeferred().await() val targets = locations.targets.firstOrNull() ?: throw ExecutionException("Could not find runTo target!")
val targets = locations.targets.firstOrNull() ?: throw RuntimeException("Could not find runTo target!")
val args = GotoArguments() val args = GotoArguments()
args.targetId = targets.id args.targetId = targets.id
args.threadId = -1 args.threadId = -1
server.goto_(args) server.goto_(args).getSafe()
}
} }
override fun runTo(p0: Address) { /**
* Run to PC address
*
* @see #stepOver
*/
override fun runTo(address: Address) {
throw UnsupportedOperationException("RunTo address not implemented!") throw UnsupportedOperationException("RunTo address not implemented!")
} }
/**
* Perform debugger exit
*
* @see #stepOver
*/
override fun doExit(): Boolean { override fun doExit(): Boolean {
val disconnectArgs = DisconnectArguments() val disconnectArgs = DisconnectArguments()
disconnectArgs.terminateDebuggee = true disconnectArgs.terminateDebuggee = true
@ -293,12 +354,16 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
return true return true
} }
// TODO /**
* "Jump" to support
*/
override fun jumpToLine(thread: LLThread, path: String, line: Int, canLeaveFunction: Boolean): StopPlace { override fun jumpToLine(thread: LLThread, path: String, line: Int, canLeaveFunction: Boolean): StopPlace {
throw DebuggerCommandException("Can't resolve address for line $path:$line") throw DebuggerCommandException("Can't resolve address for line $path:$line")
} }
// TODO /**
* "Jump" to support
*/
override fun jumpToAddress(thread: LLThread, address: Address, canLeaveFunction: Boolean): StopPlace { override fun jumpToAddress(thread: LLThread, address: Address, canLeaveFunction: Boolean): StopPlace {
throw DebuggerCommandException("Can't jump to address $address") throw DebuggerCommandException("Can't jump to address $address")
} }
@ -311,10 +376,16 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
addPathMapping(index, from, to) addPathMapping(index, from, to)
} }
/**
* Autocomplete support for debugger console
*/
override fun completeConsoleCommand(p0: String, p1: Int): ResultList<String> { override fun completeConsoleCommand(p0: String, p1: Int): ResultList<String> {
throw ExecutionException("completeConsoleCommand") throw ExecutionException("completeConsoleCommand")
} }
/**
* Watchpoint handling
*/
override fun addWatchpoint( override fun addWatchpoint(
threadId: Long, threadId: Long,
frameIndex: Int, frameIndex: Int,
@ -326,37 +397,16 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
throw ExecutionException("TODO") throw ExecutionException("TODO")
} }
/**
* Watchpoint handling
*/
override fun removeWatchpoint(ids: MutableList<Int>) { override fun removeWatchpoint(ids: MutableList<Int>) {
throw ExecutionException("TODO") throw ExecutionException("TODO")
} }
data class PathedSourceBreakpoint( /** User adds a breakpoint
val path: String, * {@link #handleBreakpointAdded} supposed to be called asynchronously when done
val src: SourceBreakpoint */
)
data class MappedBreakpoint(
val id: Int,
val java: LLBreakpoint,
val loc: LLBreakpointLocation?,
val dap: Breakpoint,
val ref: Either3<PathedSourceBreakpoint, FunctionBreakpoint, InstructionBreakpoint>
) {
constructor(dap: Breakpoint, ref: Either3<PathedSourceBreakpoint, FunctionBreakpoint, InstructionBreakpoint>) :
this(dap.id, Util.breakpointJBFromDAP(dap), Util.getLocation(dap), dap, ref)
}
protected val breakpoints = HashMap<Int, MappedBreakpoint>()
data class MappedModule(
val java: LLModule,
val dap: Module
) {
constructor(dap: Module): this(Util.moduleJBFromDAP(dap), dap)
}
protected val modules = HashMap<Int, MappedModule>()
override fun addBreakpoint(path: String, line: Int, condition: String?, ignoreSourceHash: Boolean): AddBreakpointResult { override fun addBreakpoint(path: String, line: Int, condition: String?, ignoreSourceHash: Boolean): AddBreakpointResult {
val bp = SourceBreakpoint() val bp = SourceBreakpoint()
bp.line = line + 1 bp.line = line + 1
@ -387,6 +437,9 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
return res.breakpoints return res.breakpoints
} }
/**
* User adds a symbolic breakpoint
*/
override fun addSymbolicBreakpoint(symBreakpoint: SymbolicBreakpoint): LLSymbolicBreakpoint? { override fun addSymbolicBreakpoint(symBreakpoint: SymbolicBreakpoint): LLSymbolicBreakpoint? {
if (!capabilities.supportsFunctionBreakpoints) if (!capabilities.supportsFunctionBreakpoints)
throw DebuggerCommandException("Server doesn't support function breakpoints!") throw DebuggerCommandException("Server doesn't support function breakpoints!")
@ -417,6 +470,9 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
return res.breakpoints return res.breakpoints
} }
/**
* User adds an address breakpoint
*/
override fun addAddressBreakpoint(address: Address, condition: String?): AddBreakpointResult { override fun addAddressBreakpoint(address: Address, condition: String?): AddBreakpointResult {
if (!capabilities.supportsInstructionBreakpoints) if (!capabilities.supportsInstructionBreakpoints)
throw DebuggerCommandException("Server doesn't support instruction breakpoints!") throw DebuggerCommandException("Server doesn't support instruction breakpoints!")
@ -444,16 +500,601 @@ abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolC
return server.setInstructionBreakpoints(args).getSafe().breakpoints return server.setInstructionBreakpoints(args).getSafe().breakpoints
} }
/**
* User removes symbolic or line breakpoint.
*
* [handleBreakpointRemoved] supposed to be called asynchronously when done
*/
override fun removeCodepoints(ids: MutableCollection<Int>) {
val removed = ArrayList<MappedBreakpoint>()
for (id in ids) {
breakpoints.remove(id)?.let { removed.add(it) }
}
val anySrc = removed.any { it.ref.isFirst }
val anyFunc = removed.any { it.ref.isSecond }
val anyAddr = removed.any { it.ref.isThird }
if (anySrc) {
val sources = removed.asSequence().filter { it.ref.isFirst }.map { it.ref.first.path }.distinct().toList()
val bps = breakpoints.values.asSequence().filter { it.ref.isFirst }.map { it.ref.first }.toList()
for (source in sources) {
val sourceBps = bps.asSequence().filter { it.path == source }.map { it.src }.toList()
updateSourceBreakpoints(source, sourceBps)
}
}
if (anyFunc) {
updateSymbolicBreakpoints(breakpoints.values.asSequence().filter { it.ref.isSecond }.map { it.ref.second }.toList())
}
if (anyAddr) {
updateAddressBreakpoints(breakpoints.values.asSequence().filter { it.ref.isThird }.map { it.ref.third }.toList())
}
}
/**
* List of threads. For instance, RTOS tasks
*/
override fun getThreads(): List<LLThread> {
return server.threads().getSafe().threads.asSequence().map(Util::threadJBFromDAP).toList()
}
override fun cancelSymbolsDownload(details: String) {
throw DebuggerCommandException("cancelSymbolsDownload not implemented")
}
/**
* Stack trace for a thread
*/
override fun getFrames(thread: LLThread, from: Int, count: Int, untilFirstLineWithCode: Boolean): ResultList<LLFrame> {
val args = StackTraceArguments()
args.threadId = Math.toIntExact(thread.id)
args.startFrame = from
args.levels = count
val stackTrace = server.stackTrace(args).getSafe()
val stackFrames = stackTrace.stackFrames
val resultList = stackFrames.map { Util.frameJBFromDAP(it, null, modules) }
return ResultList.create(resultList, false)
}
@Deprecated("Inherited from deprecated")
override fun getVariables(p0: Long, p1: Int): MutableList<LLValue> {
deprecated()
}
override fun getFrameVariables(thread: LLThread, frame: LLFrame): FrameVariables {
return FrameVariables(getWrappedScopes(frame), true)
}
// TODO registers
override fun supportsRegisters(): Boolean {
return true
}
override fun getRegisters(thread: LLThread, frame: LLFrame): List<LLValue> {
return registerSets.values
.asSequence()
.flatMap { it.asSequence() }
.toList()
}
override fun getRegisters(thread: LLThread, frame: LLFrame, registerNames: Set<String>): List<LLValue> {
if (registerNames.isEmpty()) {
return getRegisters(thread, frame)
}
return registerSets.values
.asSequence()
.flatMap { it.asSequence() }
.filter { registerNames.contains(it.name.lowercase()) }
.toList()
}
override fun getRegisterSets(): List<LLRegisterSet> {
return registerSets.entries
.asSequence()
.map { LLRegisterSet(it.key, it.value.map(LLValue::getName)) }
.toList()
}
@Throws(ExecutionException::class)
protected fun getWrappedScopes(frame: LLFrame): List<LLValue> {
val scopeArgs = ScopesArguments()
val frameID = frame.index
scopeArgs.frameId = frameID
val scopes = server.scopes(scopeArgs).getSafe()
val result = ArrayList<LLValue>()
for (scope in scopes.scopes) {
val ref = scope.variablesReference
if ("registers" == scope.name.lowercase()) {
updateRegisters(frameID, ref)
continue
}
result.addAll(getVariables(frameID, scope.variablesReference, null, null))
}
return result
}
@Throws(ExecutionException::class)
private fun updateRegisters(frameID: Int, rootRef: Int) {
val registerGroups = getVariables(frameID, rootRef, null, null)
registerSets.clear()
var c = 0
for (registerGroup in registerGroups) {
val name = "${c++} - ${registerGroup.name}"
val ref = registerGroup.getUserData(LLVALUE_CHILDREN_REF)
if (ref == null || ref == 0)
continue
val registers = getVariables(frameID, ref, null, null)
val renamedRegisters = ArrayList<LLValue>(registers.size)
for (register in registers) {
val renamedRegister = LLValue(register.name.lowercase(), register.type, register.displayType, register.address, register.typeClass, register.referenceExpression)
register.copyUserDataTo(renamedRegister)
val oldData = renamedRegister.getUserData(LLVALUE_DATA)
if (oldData != null && HEX_REGEX.matches(oldData.value)) {
val newData = LLValueData("0x${oldData.value.lowercase()}", oldData.description, oldData.hasLongerDescription(), oldData.mayHaveChildren(), oldData.isSynthetic)
renamedRegister.putUserData(LLVALUE_DATA, newData)
}
renamedRegisters.add(renamedRegister)
}
registerSets[name] = renamedRegisters
}
val arch = architecture
if (arch == null)
return
val toggles = HashMap<String, Boolean>()
var first = true
for (registerSet in registerSets.keys) {
toggles[registerSet] = first
first = false
}
val cds = CidrDebuggerSettings.getInstance()
val settings = cds.getRegisterSetSettings(arch, driverName)
if (settings == null || !settings.keys.containsAll(toggles.keys)) {
cds.setRegisterSetSettings(arch, driverName, toggles)
}
}
override fun getArchitecture(): String? {
return null
}
@Throws(ExecutionException::class)
protected fun getVariables(frameID: Int, variablesReference: Int, start: Int?, count: Int?): List<LLValue> {
val variableArgs = VariablesArguments()
variableArgs.variablesReference = variablesReference
variableArgs.start = start
variableArgs.count = count
val variables = server.variables(variableArgs).getSafe().variables
val javaVariables = ArrayList<LLValue>(variables.size)
for (variable in variables) {
val address = Util.parseAddressNullable(variable.memoryReference)
val type = variable.type ?: ""
val truncated = type.replace(ERROR_REGEX, "error{...}")
val name = variable.name
val evalName = variable.evaluateName ?: ""
val childRef = variable.variablesReference
val knownValue = variable.value
val llValue = LLValue(name, type, truncated, address, null, evalName)
llValue.putUserData(LLVALUE_FRAME, frameID)
llValue.putUserData(LLVALUE_CHILDREN_REF, childRef)
if (knownValue != null) {
llValue.putUserData(LLVALUE_DATA, LLValueData(knownValue, null, false, childRef > 0, false))
}
javaVariables.add(llValue)
}
return javaVariables
}
override fun getVariables(thread: LLThread, frame: LLFrame): MutableList<LLValue> {
return getFrameVariables(thread, frame).variables
}
/**
* Read value of a variable
*/
override fun getData(value: LLValue): LLValueData {
var result = ""
var childrenRef = 0
var failed = false
if (value.referenceExpression.isBlank()) {
failed = true
} else {
val args = EvaluateArguments()
args.context = EvaluateArgumentsContext.VARIABLES
args.expression = value.referenceExpression
args.frameId = value.getUserData(LLVALUE_FRAME)
val res = server.evaluate(args).getSafe()
childrenRef = res.variablesReference
if (childrenRef > 0) {
value.putUserData(LLVALUE_CHILDREN_REF, childrenRef)
}
val attribs = res.presentationHint?.attributes
if (attribs != null) {
for (attrib in attribs) {
if ("failedEvaluation" == attrib) {
failed = true
}
}
}
result = res.result
}
if (failed) {
val known = value.getUserData(LLVALUE_DATA)
if (known != null)
return known
val cRef = value.getUserData(LLVALUE_CHILDREN_REF)
if (cRef != null)
childrenRef = cRef
}
return LLValueData(result, null, false, childrenRef > 0, false)
}
/**
* Read description of a variable
*/
override fun getDescription(value: LLValue, maxLength: Int): String {
val type = value.type
val length = min(type.length, maxLength)
return type.substring(0, length)
}
/**
* Unions, structures, or classes are hierarchical. This method help to obtain the hierarchy
*/
override fun getChildrenCount(value: LLValue): Int {
val frame = value.getUserData(LLVALUE_FRAME)
val childrenRef = value.getUserData(LLVALUE_CHILDREN_REF)
val children = if (childrenRef == null || frame == null)
emptyList()
else
getVariables(frame, childrenRef, null, null)
value.putUserData(LLVALUE_CHILDREN, children)
return children.size
}
/**
* Unions, structures, or classes are hierarchical. This method help to obtain the hierarchy
*/
override fun getVariableChildren(value: LLValue, from: Int, count: Int): ResultList<LLValue> {
val size = getChildrenCount(value)
val children = value.getUserData(LLVALUE_CHILDREN)
return if (children == null || from > size) {
ResultList.empty()
} else if (from + count >= size) {
ResultList.create(children.subList(from, size), false)
} else {
ResultList.create(children.subList(from, from + count), true)
}
}
@Deprecated("Inherited from deprecated")
override fun evaluate(threadId: Long, frameIndex: Int, expression: String, language: DebuggerLanguage?): LLValue {
val evalArgs = EvaluateArguments()
evalArgs.frameId = frameIndex
evalArgs.expression = expression
val res = server.evaluate(evalArgs).getSafe()
val type = res.type ?: "unknown"
val addr = res.memoryReference?.let { Util.parseAddress(it) }
val result = LLValue("result", type, addr, null, "")
result.putUserData(LLVALUE_DATA, LLValueData(res.result, null, false, false, false))
result.putUserData(LLVALUE_FRAME, frameIndex)
return result
}
@Suppress("DEPRECATION")
override fun evaluate(thread: LLThread, frame: LLFrame, expression: String, language: DebuggerLanguage?): LLValue {
return evaluate(thread.id, frame.index, expression, language)
}
override fun disassembleFunction(address: Address, fallbackRange: AddressRange): List<LLInstruction> {
return disassemble(fallbackRange)
}
override fun disassemble(range: AddressRange): List<LLInstruction> {
return runBlocking {
disassembleSuspend(range)
}
}
private suspend fun disassembleSuspend(range: AddressRange): List<LLInstruction> {
if (!capabilities.supportsDisassembleRequest)
throw DebuggerCommandException("disassemble is not supported by debugger!")
val args = DisassembleArguments()
args.memoryReference = java.lang.Long.toHexString(range.start.unsignedLongValue)
args.instructionCount = Math.toIntExact(range.size)
args.resolveSymbols = true
val disassembly = server.disassemble(args).getSuspend()
val dapInstructions = disassembly.instructions
val jbInstructions = ArrayList<LLInstruction>(dapInstructions.size)
var loc: Source? = null
var startLine: Int? = null
var endLine: Int? = null
var symbol: String? = null
var baseOffset = 0L
for (dapInstruction in dapInstructions) {
val dapLoc = dapInstruction.location
val dapStartLine = dapInstruction.line
val dapEndLine = dapInstruction.endLine
val dapSymbol = dapInstruction.symbol
val dapAddr = Util.parseAddress(dapInstruction.address)
var uniq = true
if (dapLoc != null) {
loc = dapLoc
} else if (startLine != null && dapStartLine == startLine && endLine != null && dapEndLine == endLine) {
uniq = false
} else {
startLine = dapStartLine
endLine = dapEndLine
}
if (dapSymbol != null && dapSymbol != symbol) {
symbol = dapSymbol
baseOffset = dapAddr
}
val llSymbol = symbol?.let {LLSymbolOffset(symbol, dapAddr - baseOffset)}
jbInstructions.add(Util.instructionJBFromDAP(dapInstruction, loc, startLine, endLine, uniq, llSymbol))
}
return jbInstructions
}
override fun dumpMemory(range: AddressRange): List<LLMemoryHunk> {
if (!capabilities.supportsReadMemoryRequest)
throw DebuggerCommandException("dumpMemory is not supported by debugger!")
val start = range.start.unsignedLongValue
val length = range.size
val hunks = ArrayList<LLMemoryHunk>((length / (Integer.MAX_VALUE.toLong() + 1)).toInt())
var offset = 0L
while (offset < length) {
val blockLength = Math.toIntExact(min(Integer.MAX_VALUE.toLong(), length - offset))
val args = ReadMemoryArguments()
args.memoryReference = Util.stringifyAddress(start + offset)
args.count = blockLength
hunks.add(Util.memoryJBFromDAP(server.readMemory(args).getSafe()))
offset += Integer.MAX_VALUE
}
return hunks
}
override fun getLoadedModules(): List<LLModule> {
if (!capabilities.supportsModulesRequest)
throw DebuggerCommandException("getLoadedModules is not supported by debugger!")
val args = ModulesArguments()
val modulesResponse = server.modules(args).getSafe()
val modules = modulesResponse.modules
return modules.map(Util::moduleJBFromDAP)
}
override fun getModuleSections(module: LLModule): List<LLSection> {
throw DebuggerCommandException("GetModuleSections is not implemented")
}
override fun executeShellCommand(executable: String, params: List<String>?, workingDir: String?, timeoutSecs: Int): ShellCommandResult {
throw ExecutionException("ExecuteShellCommand is not implemented")
}
override fun executeInterpreterCommand(command: String): String {
return executeInterpreterCommand(-1, -1, command)
}
override fun executeInterpreterCommand(threadId: Long, frameIndex: Int, text: String): String {
val args = EvaluateArguments()
args.expression = text
args.frameId = frameIndex
return server.evaluate(args).getSafe().result
}
override fun handleSignal(signalName: String, stop: Boolean, pass: Boolean, notify: Boolean) {
throw DebuggerCommandException("handleSignal is not implemented")
}
override fun getPromptText(): String {
return ""
}
/**
* Verify if driver is in OK state
*
* @throws ExecutionException if something is wrong
*/
override fun checkErrors() {
// TODO
}
override fun addSymbolsFile(file: File, module: File?) {
throw ExecutionException("addSymbolsFile not implemented!")
}
override fun getProcessInput(): OutputStream? {
return multiplexer
}
override fun getDisasmFlavor(): DisasmFlavor {
return DisasmFlavor.INTEL
}
companion object { companion object {
val LLVALUE_FRAME = Key.create<Int>("DAPDriver.LLVALUE_FRAME") val LLVALUE_FRAME = Key.create<Int>("DAPDriver.LLVALUE_FRAME")
val LLVALUE_CHILDREN_REF = KeyWithDefaultValue.create<Int>("DAPDriver.LLVALUE_CHILDREN_REF", 0) val LLVALUE_CHILDREN_REF = KeyWithDefaultValue.create<Int>("DAPDriver.LLVALUE_CHILDREN_REF", 0)
val LLVALUE_DATA = Key.create<LLValueData>("DAPDriver.LLVALUE_DATA") val LLVALUE_DATA = Key.create<LLValueData>("DAPDriver.LLVALUE_DATA")
val LLVALUE_CHILDREN = Key.create<List<LLValue>>("DAPDriver.LLVALUE_CHILDREN") val LLVALUE_CHILDREN = Key.create<List<LLValue>>("DAPDriver.LLVALUE_CHILDREN")
} }
data class PathedSourceBreakpoint(
val path: String,
val src: SourceBreakpoint
)
data class MappedBreakpoint(
val id: Int,
val java: LLBreakpoint,
val loc: LLBreakpointLocation?,
val dap: Breakpoint,
val ref: Either3<PathedSourceBreakpoint, FunctionBreakpoint, InstructionBreakpoint>
) {
constructor(dap: Breakpoint, ref: Either3<PathedSourceBreakpoint, FunctionBreakpoint, InstructionBreakpoint>) :
this(dap.id, Util.breakpointJBFromDAP(dap), Util.getLocation(dap), dap, ref)
}
data class MappedModule(
val java: LLModule,
val dap: Module
) {
constructor(dap: Module): this(Util.moduleJBFromDAP(dap), dap)
}
abstract inner class DAPDebuggerClient: IDebugProtocolClient {
override fun runInTerminal(args: RunInTerminalRequestArguments): CompletableFuture<RunInTerminalResponse> {
return zigCoroutineScope.async {
runInTerminalAsync(args)
}.asCompletableFuture()
}
fun runInTerminalAsync(args: RunInTerminalRequestArguments): RunInTerminalResponse {
val cli = PtyCommandLine(args.args.toList())
cli.charset = Charsets.UTF_8
val cwd = args.cwd?.ifBlank { null }?.toNioPathOrNull()
if (cwd != null) {
cli.withWorkingDirectory(cwd)
}
val childProcess = ZigProcessHandler(cli)
this@DAPDriver.childProcess = childProcess
childProcess.addProcessListener(object: ProcessListener {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
handleTargetOutput(
event.text,
if (ProcessOutputType.isStdout(outputType)) {
ProcessOutputType.STDOUT
} else if (ProcessOutputType.isStderr(outputType)) {
ProcessOutputType.STDERR
} else {
ProcessOutputType.SYSTEM
}
)
}
})
childProcess.startNotify()
val processInput = childProcess.processInput
this@DAPDriver.processInput = processInput
val resp = RunInTerminalResponse()
resp.shellProcessId = childProcess.process.pid().toInt()
zigCoroutineScope.launch {
dummyOutput?.toByteArray()?.let { processInput.write(it) }
dummyOutput = null
}
return resp
}
override fun output(args: OutputEventArguments) {
when(args.category) {
"stdout" -> handleTargetOutput(args.output, ProcessOutputType.STDOUT)
"stderr" -> handleTargetOutput(args.output, ProcessOutputType.STDERR)
else -> handleDebuggerOutput(args.output, ProcessOutputType.STDOUT)
}
}
override fun breakpoint(args: BreakpointEventArguments) {
val bp = args.breakpoint
when(args.reason) {
BreakpointEventArgumentsReason.CHANGED -> {
val mbp = updateBP(bp)
handleBreakpointUpdated(mbp.java)
handleBreakpointLocationsReplaced(mbp.id, listOfNotNull(mbp.loc))
}
BreakpointEventArgumentsReason.NEW -> {
val mbp = updateBP(bp)
handleBreakpointAdded(mbp.java)
handleBreakpointLocationsReplaced(mbp.id, listOfNotNull(mbp.loc))
}
BreakpointEventArgumentsReason.REMOVED -> {
breakpoints.remove(bp.id)
handleBreakpointRemoved(bp.id)
}
}
}
private fun updateBP(bp: Breakpoint): MappedBreakpoint {
return breakpoints.compute(bp.id, { _, mbp ->
if (mbp != null)
return@compute MappedBreakpoint(bp, mbp.ref)
val ins = InstructionBreakpoint()
ins.instructionReference = bp.instructionReference
return@compute MappedBreakpoint(bp, Either3.forThird(ins))
})!!
}
override fun exited(args: ExitedEventArguments) {
handleExited(args.exitCode)
}
override fun stopped(args: StoppedEventArguments) {
server.threads().thenAccept { threadsResponse ->
val threads = threadsResponse.threads
val thread = if (args.threadId != null) {
val id = args.threadId!!
threads.asSequence().filter { it.id == id }.first()
} else {
threads.asSequence().sortedBy { it.id }.first()
}
val jbThread = Util.threadJBFromDAP(thread)
val stArgs = StackTraceArguments()
stArgs.threadId = thread.id
stArgs.startFrame = 0
stArgs.levels = 1
server.stackTrace(stArgs).thenAccept { st ->
var helperBreakpoint: MappedBreakpoint? = null
val isBreakpoint = "breakpoint" == args.reason
if (isBreakpoint) {
helperBreakpoint = breakpoints.get(args.hitBreakpointIds[0])
}
val frame = Util.frameJBFromDAP(st.stackFrames[0], helperBreakpoint, modules)
val stopPlace = StopPlace(jbThread, frame)
if (isBreakpoint) {
handleBreakpoint(stopPlace, args.hitBreakpointIds[0])
} else {
handleInterrupted(stopPlace)
}
}
}
}
override fun continued(args: ContinuedEventArguments) {
handleRunning()
}
override fun module(args: ModuleEventArguments) {
val module = args.module
when(args.reason) {
ModuleEventArgumentsReason.NEW -> {
val mm = MappedModule(module)
modules[module.id.left] = mm
handleModulesLoaded(listOf(mm.java))
}
ModuleEventArgumentsReason.CHANGED -> {
val newModule = MappedModule(module)
val oldModule = modules.put(module.id.left, newModule)
if (oldModule != null) {
handleModulesUnloaded(listOf(oldModule.java))
}
handleModulesLoaded(listOf(newModule.java))
}
ModuleEventArgumentsReason.REMOVED -> {
val oldModule = modules.remove(module.id.left)
if (oldModule != null) {
handleModulesUnloaded(listOf(oldModule.java))
}
}
null -> {}
}
}
}
} }
@Throws(ExecutionException::class) @Throws(ExecutionException::class)
fun <T> CompletableFuture<T>.getSafe(): T { private fun <T> CompletableFuture<T>.getSafe(): T {
try { try {
return getMaybeCancellable() return getMaybeCancellable()
} catch (e: Exception) { } catch (e: Exception) {
@ -461,6 +1102,14 @@ fun <T> CompletableFuture<T>.getSafe(): T {
} }
} }
private suspend fun <T> CompletableFuture<T>.getSuspend(): T {
try {
return asDeferred().await()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
private fun notSupported(): Nothing { private fun notSupported(): Nothing {
throw ExecutionException("Not supported") throw ExecutionException("Not supported")
} }
@ -468,3 +1117,6 @@ private fun notSupported(): Nothing {
private fun deprecated(): Nothing { private fun deprecated(): Nothing {
throw ExecutionException("Deprecated") throw ExecutionException("Deprecated")
} }
private val HEX_REGEX = Regex("[0-9a-fA-F]+")
private val ERROR_REGEX = Regex("error\\{.*?}")

View file

@ -49,7 +49,7 @@ object Util {
var sourcePath = if (source == null) "" else Objects.requireNonNullElseGet( var sourcePath = if (source == null) "" else Objects.requireNonNullElseGet(
source.path source.path
) { Objects.requireNonNullElse(source.origin, "unknown") } ) { Objects.requireNonNullElse(source.origin, "unknown") }
sourcePath = toJBPath(sourcePath)!! sourcePath = toJBPath(sourcePath)
return LLBreakpoint( return LLBreakpoint(
DAPBreakpoint.id, DAPBreakpoint.id,
sourcePath, sourcePath,
@ -176,21 +176,20 @@ object Util {
suspend fun instructionJBFromDAP( suspend fun instructionJBFromDAP(
DAPInstruction: DisassembledInstruction, DAPInstruction: DisassembledInstruction,
loc: Source?, loc: Source?,
startLine: Int?, startLineIn: Int?,
endLine: Int?, endLineIn: Int?,
uniq: Boolean, uniq: Boolean,
symbol: LLSymbolOffset? symbol: LLSymbolOffset?
): LLInstruction { ): LLInstruction {
var startLine = startLine var startLine = startLineIn
var endLine = endLine var endLine = endLineIn
val address: Address = Address.parseHexString(DAPInstruction.address) val address: Address = Address.parseHexString(DAPInstruction.address)
val byteStrings = val byteStrings =
DAPInstruction.instructionBytes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() DAPInstruction.instructionBytes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val bytes = ArrayList<Byte>(byteStrings.size) val bytes = ArrayList<Byte>(byteStrings.size)
for (byteString in byteStrings) { for (byteString in byteStrings) {
bytes.add(byteString.toInt(16) as Byte) bytes.add(byteString.toInt(16).toByte())
} }
val result: ArrayList<LLInstruction> = ArrayList<LLInstruction>()
var comment: String? = null var comment: String? = null
if (loc != null && startLine != null && endLine != null && uniq) run { if (loc != null && startLine != null && endLine != null && uniq) run {
val pathStr = toJBPath(loc.path) val pathStr = toJBPath(loc.path)
@ -254,8 +253,4 @@ object Util {
throw com.intellij.execution.ExecutionException(e.cause) throw com.intellij.execution.ExecutionException(e.cause)
} }
} }
fun emptyIfNull(str: String?): String {
return str ?: ""
}
} }

View file

@ -0,0 +1,29 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger.settings
enum class MSVCDownloadPermission {
AskMe,
Allow,
Deny
}

View file

@ -35,8 +35,9 @@ class ZigDebuggerSettings: XDebuggerSettings<ZigDebuggerSettings>("Zig") {
var downloadAutomatically = false var downloadAutomatically = false
var useClion = true var useClion = true
var msvcConsent = MSVCDownloadPermission.AskMe
override fun getState(): ZigDebuggerSettings? { override fun getState(): ZigDebuggerSettings {
return this return this
} }

View file

@ -23,7 +23,10 @@
package com.falsepattern.zigbrains.debugger.toolchain package com.falsepattern.zigbrains.debugger.toolchain
import com.falsepattern.zigbrains.debugger.ZigDebugBundle import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.debugger.settings.MSVCDownloadPermission
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings
import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService.Companion.downloadPath import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService.Companion.downloadPath
import com.falsepattern.zigbrains.shared.coroutine.withCurrentEDTModalityContext
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.intellij.notification.Notification import com.intellij.notification.Notification
import com.intellij.notification.NotificationType import com.intellij.notification.NotificationType
@ -48,7 +51,10 @@ suspend fun msvcMetadata(): Properties {
cache?.let { return it } cache?.let { return it }
mutex.withLock { mutex.withLock {
cache?.let { return it } cache?.let { return it }
val allowDownload = withEDTContext(ModalityState.current()) { val settings = ZigDebuggerSettings.instance
var permission = settings.msvcConsent
if (permission == MSVCDownloadPermission.AskMe) {
val allowDownload = withCurrentEDTModalityContext {
val dialog = DialogBuilder() val dialog = DialogBuilder()
dialog.setTitle(ZigDebugBundle.message("msvc.consent.title")) dialog.setTitle(ZigDebugBundle.message("msvc.consent.title"))
dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny")) dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny"))
@ -62,7 +68,10 @@ suspend fun msvcMetadata(): Properties {
dialog.centerPanel(centerPanel) dialog.centerPanel(centerPanel)
dialog.showAndGet() dialog.showAndGet()
} }
val data = if (allowDownload) { permission = if (allowDownload) MSVCDownloadPermission.Allow else MSVCDownloadPermission.Deny
settings.msvcConsent = permission
}
val data = if (permission == MSVCDownloadPermission.Allow) {
withTimeoutOrNull(3000L) { withTimeoutOrNull(3000L) {
downloadMSVCProps() downloadMSVCProps()
} ?: run { } ?: run {

View file

@ -157,7 +157,7 @@ class ZigDebuggerToolchainService {
val centerPanel = JBPanel<JBPanel<*>>() val centerPanel = JBPanel<JBPanel<*>>()
val hyperlink = HyperlinkLabel() val hyperlink = HyperlinkLabel()
hyperlink.setTextWithHyperlink(msvcUrl.dialogBody) hyperlink.setTextWithHyperlink(msvcUrl.dialogBody)
hyperlink.setHyperlinkText(msvcUrl.dialogLink) hyperlink.setHyperlinkTarget(msvcUrl.dialogLink)
hyperlink.addHyperlinkListener(BrowserHyperlinkListener()) hyperlink.addHyperlinkListener(BrowserHyperlinkListener())
centerPanel.add(hyperlink) centerPanel.add(hyperlink)
dialog.centerPanel(centerPanel) dialog.centerPanel(centerPanel)
@ -229,7 +229,7 @@ class ZigDebuggerToolchainService {
val binaryToDownload = binariesToDownload.first { it.url == downloadUrl } val binaryToDownload = binariesToDownload.first { it.url == downloadUrl }
val propertyName = binaryToDownload.propertyName val propertyName = binaryToDownload.propertyName
val archiveFile = result.first val archiveFile = result.first
Unarchiver.unarchive(archiveFile, downloadDir) Unarchiver.unarchive(archiveFile, downloadDir, binaryToDownload.prefix)
archiveFile.delete() archiveFile.delete()
versions[propertyName] = binaryToDownload.version versions[propertyName] = binaryToDownload.version
} }

View file

@ -34,14 +34,14 @@ abstract class MSVCDriverConfiguration: DAPDebuggerDriverConfiguration() {
protected abstract val debuggerExecutable: Path protected abstract val debuggerExecutable: Path
override fun createDriver(handler: DebuggerDriver.Handler, arch: ArchitectureType): DebuggerDriver { override fun createDriver(handler: DebuggerDriver.Handler, arch: ArchitectureType): DebuggerDriver {
TODO("Not yet implemented") return WinDAPDriver(handler).also { it.initialize(this) }
} }
override fun createDriverCommandLine(driver: DebuggerDriver, arch: ArchitectureType): GeneralCommandLine { override fun createDriverCommandLine(driver: DebuggerDriver, arch: ArchitectureType): GeneralCommandLine {
val path = debuggerExecutable val path = debuggerExecutable
val cli = GeneralCommandLine() val cli = GeneralCommandLine()
cli.exePath = path.pathString cli.exePath = path.pathString
cli.addParameters("--interpreter=vscode", "--extconfigdir=%USERPROFILE\\.cppvsdbg\\extensions") cli.addParameters("--interpreter=vscode", "--extconfigdir=%USERPROFILE%\\.cppvsdbg\\extensions")
cli.withWorkingDirectory(path.parent) cli.withWorkingDirectory(path.parent)
return cli return cli
} }

View file

@ -0,0 +1,129 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger.win
import com.falsepattern.zigbrains.debugger.dap.DAPDriver
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.util.system.CpuArch
import com.jetbrains.cidr.ArchitectureType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.sync.Semaphore
import org.eclipse.lsp4j.debug.Capabilities
import org.eclipse.lsp4j.debug.OutputEventArguments
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer
import org.eclipse.lsp4j.debug.util.ToStringBuilder
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.debug.messages.DebugResponseMessage
import org.eclipse.lsp4j.jsonrpc.messages.Message
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
import java.lang.RuntimeException
import java.security.MessageDigest
import java.util.Base64
import java.util.concurrent.CompletableFuture
import java.util.zip.Inflater
class WinDAPDriver(handler: Handler) : DAPDriver<IDebugProtocolServer, WinDAPDriver.WinDAPDebuggerClient>(handler) {
private val handshakeFinished = Semaphore(1, 1)
override fun createDebuggerClient(): WinDAPDebuggerClient {
return WinDAPDebuggerClient()
}
override fun getServerInterface(): Class<IDebugProtocolServer> {
return IDebugProtocolServer::class.java
}
override fun wrapMessageConsumer(mc: MessageConsumer): MessageConsumer {
return object: MessageConsumer {
private var verifyHandshake = true
override fun consume(message: Message) {
if (verifyHandshake && message is DebugResponseMessage && message.method == "handshake") {
verifyHandshake = false
message.setResponseId(1)
}
mc.consume(message)
}
}
}
override suspend fun postInitialize(capabilities: Capabilities) {
handshakeFinished.acquire()
}
inner class WinDAPDebuggerClient: DAPDriver<IDebugProtocolServer, WinDAPDriver.WinDAPDebuggerClient>.DAPDebuggerClient() {
override fun output(args: OutputEventArguments) {
if ("telemetry" == args.category)
return
super.output(args)
}
@JsonRequest
fun handshake(handshake: HandshakeRequest): CompletableFuture<HandshakeResponse> {
return zigCoroutineScope.async(Dispatchers.IO) {
handshakeSuspend(handshake)
}.asCompletableFuture()
}
private fun handshakeSuspend(handshake: HandshakeRequest): HandshakeResponse {
val hasher = MessageDigest.getInstance("SHA-256")
hasher.update(handshake.value.encodeToByteArray())
val inflater = Inflater(true)
val coconut = DAPDebuggerClient::class.java.getResourceAsStream("/coconut.jpg").use { it.readAllBytes() } ?: throw RuntimeException("No coconut")
inflater.setInput(coconut, coconut.size - 80, 77)
inflater.finished()
val b = ByteArray(1)
while (inflater.inflate(b) > 0)
hasher.update(b)
val result = HandshakeResponse(String(coconut, coconut.size - 3, 3) + Base64.getEncoder().encodeToString(hasher.digest()))
handshakeFinished.release()
return result
}
}
override fun getArchitecture(): String {
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).id
}
data class HandshakeRequest(var value: String) {
constructor() : this("")
override fun toString(): String {
val b = ToStringBuilder(this)
b.add("value", value)
return b.toString()
}
}
data class HandshakeResponse(var signature: String) {
constructor() : this("")
override fun toString(): String {
val b = ToStringBuilder(this)
b.add("signature", this.signature)
return b.toString()
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -33,6 +33,8 @@ import com.intellij.execution.configurations.RunnerSettings
import com.intellij.execution.runners.AsyncProgramRunner import com.intellij.execution.runners.AsyncProgramRunner
import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.ui.RunContentDescriptor import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.rd.util.toPromise import com.intellij.openapi.rd.util.toPromise
import com.intellij.platform.ide.progress.ModalTaskOwner import com.intellij.platform.ide.progress.ModalTaskOwner
@ -59,7 +61,14 @@ abstract class ZigProgramRunner<ProfileState: ZigProfileState<*>>(protected val
val state = castProfileState(baseState) ?: return null val state = castProfileState(baseState) ?: return null
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: return null val toolchain = environment.project.zigProjectSettings.state.toolchain ?: run {
Notification(
"zigbrains",
"Zig project toolchain not set, cannot execute program!",
NotificationType.ERROR
).notify(environment.project)
return null
}
return reportProgress { reporter -> return reportProgress { reporter ->
reporter.indeterminateStep("Saving all documents") { reporter.indeterminateStep("Saving all documents") {

View file

@ -51,6 +51,8 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.swing.event.DocumentEvent import javax.swing.event.DocumentEvent
import kotlin.io.path.exists
import kotlin.io.path.notExists
import kotlin.io.path.pathString import kotlin.io.path.pathString
class ZigProjectSettingsPanel(private val project: Project?) : Disposable { class ZigProjectSettingsPanel(private val project: Project?) : Disposable {
@ -100,8 +102,8 @@ class ZigProjectSettingsPanel(private val project: Project?) : Disposable {
get() = ZigProjectSettings( get() = ZigProjectSettings(
direnv.isSelected, direnv.isSelected,
stdFieldOverride.isSelected, stdFieldOverride.isSelected,
pathToStd.text, pathToStd.text.ifBlank { null },
pathToToolchain.text pathToToolchain.text.ifBlank { null }
) )
set(value) { set(value) {
direnv.isSelected = value.direnv direnv.isSelected = value.direnv
@ -148,22 +150,28 @@ class ZigProjectSettingsPanel(private val project: Project?) : Disposable {
private suspend fun updateUI() { private suspend fun updateUI() {
val pathToToolchain = this.pathToToolchain.text.toNioPathOrNull() val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull()
delay(200) delay(200)
val toolchain = pathToToolchain?.let { LocalZigToolchain(it) } val toolchain = pathToToolchain?.let { LocalZigToolchain(it) }
val zig = toolchain?.zig val zig = toolchain?.zig
val env = zig?.getEnv(project) if (zig?.path()?.exists() != true) {
val version = env?.version toolchainVersion.text = ""
val stdPath = env?.stdPath(toolchain)
withEDTContext { if (!stdFieldOverride.isSelected) {
toolchainVersion.text = version ?: "" pathToStd.text = ""
}
return
}
val env = zig.getEnv(project)
val version = env.version
val stdPath = env.stdPath(toolchain, project)
toolchainVersion.text = version
toolchainVersion.foreground = JBColor.foreground() toolchainVersion.foreground = JBColor.foreground()
if (!stdFieldOverride.isSelected) { if (!stdFieldOverride.isSelected) {
pathToStd.text = stdPath?.pathString ?: "" pathToStd.text = stdPath?.pathString ?: ""
} }
} }
}
override fun dispose() { override fun dispose() {
debounce?.cancel("Disposed") debounce?.cancel("Disposed")

View file

@ -21,6 +21,7 @@
*/ */
package com.falsepattern.zigbrains.project.toolchain package com.falsepattern.zigbrains.project.toolchain
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.io.toNioPathOrNull
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -37,12 +38,12 @@ data class ZigToolchainEnvironmentSerializable(
@SerialName("version") val version: String, @SerialName("version") val version: String,
@SerialName("target") val target: String @SerialName("target") val target: String
) { ) {
fun stdPath(toolchain: LocalZigToolchain): Path? { fun stdPath(toolchain: LocalZigToolchain, project: Project?): Path? {
val path = stdDirectory.toNioPathOrNull() ?: return null val path = stdDirectory.toNioPathOrNull() ?: return null
if (path.isAbsolute) if (path.isAbsolute)
return path return path
val resolvedPath = toolchain.location.resolve(path) val resolvedPath = toolchain.workingDirectory(project)?.resolve(path) ?: return null
if (resolvedPath.isAbsolute) if (resolvedPath.isAbsolute)
return resolvedPath return resolvedPath

View file

@ -103,7 +103,7 @@ private suspend fun getRoots(
} }
} }
if (toolchain != null) { if (toolchain != null) {
val stdPath = toolchain.zig.getEnv(project).stdPath(toolchain) ?: return emptySet() val stdPath = toolchain.zig.getEnv(project).stdPath(toolchain, project) ?: return emptySet()
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return emptySet() val roots = stdPath.refreshAndFindVirtualDirectory() ?: return emptySet()
return setOf(roots) return setOf(roots)
} }

View file

@ -54,6 +54,12 @@ suspend inline fun <T> withEDTContext(state: ModalityState = ModalityState.defau
return withContext(Dispatchers.EDT + state.asContextElement(), block = block) return withContext(Dispatchers.EDT + state.asContextElement(), block = block)
} }
suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend CoroutineScope.() -> T): T {
return withContext(Dispatchers.EDT + ModalityState.defaultModalityState().asContextElement()) {
withContext(Dispatchers.EDT + ModalityState.current().asContextElement(), block = block)
}
}
suspend inline fun <T> runInterruptibleEDT(state: ModalityState = ModalityState.defaultModalityState(), noinline targetAction: () -> T): T { suspend inline fun <T> runInterruptibleEDT(state: ModalityState = ModalityState.defaultModalityState(), noinline targetAction: () -> T): T {
return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction) return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction)
} }