Windows DAP debugger and some misc fixes
This commit is contained in:
parent
f8def6e1de
commit
f7014b49eb
16 changed files with 951 additions and 94 deletions
|
@ -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"))
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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).getSafe()
|
||||||
server.goto_(args)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,10 +1102,21 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
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\\{.*?}")
|
|
@ -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 ?: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,21 +51,27 @@ 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
|
||||||
val dialog = DialogBuilder()
|
var permission = settings.msvcConsent
|
||||||
dialog.setTitle(ZigDebugBundle.message("msvc.consent.title"))
|
if (permission == MSVCDownloadPermission.AskMe) {
|
||||||
dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny"))
|
val allowDownload = withCurrentEDTModalityContext {
|
||||||
dialog.addOkAction().setText(ZigDebugBundle.message("msvc.consent.allow"))
|
val dialog = DialogBuilder()
|
||||||
val centerPanel = JBPanel<JBPanel<*>>()
|
dialog.setTitle(ZigDebugBundle.message("msvc.consent.title"))
|
||||||
centerPanel.setLayout(BoxLayout(centerPanel, BoxLayout.Y_AXIS))
|
dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny"))
|
||||||
val lines = ZigDebugBundle.message("msvc.consent.body").split('\n')
|
dialog.addOkAction().setText(ZigDebugBundle.message("msvc.consent.allow"))
|
||||||
for (line in lines) {
|
val centerPanel = JBPanel<JBPanel<*>>()
|
||||||
centerPanel.add(JBLabel(line))
|
centerPanel.setLayout(BoxLayout(centerPanel, BoxLayout.Y_AXIS))
|
||||||
|
val lines = ZigDebugBundle.message("msvc.consent.body").split('\n')
|
||||||
|
for (line in lines) {
|
||||||
|
centerPanel.add(JBLabel(line))
|
||||||
|
}
|
||||||
|
dialog.centerPanel(centerPanel)
|
||||||
|
dialog.showAndGet()
|
||||||
}
|
}
|
||||||
dialog.centerPanel(centerPanel)
|
permission = if (allowDownload) MSVCDownloadPermission.Allow else MSVCDownloadPermission.Deny
|
||||||
dialog.showAndGet()
|
settings.msvcConsent = permission
|
||||||
}
|
}
|
||||||
val data = if (allowDownload) {
|
val data = if (permission == MSVCDownloadPermission.Allow) {
|
||||||
withTimeoutOrNull(3000L) {
|
withTimeoutOrNull(3000L) {
|
||||||
downloadMSVCProps()
|
downloadMSVCProps()
|
||||||
} ?: run {
|
} ?: run {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
cidr/src/main/resources/coconut.jpg
Normal file
BIN
cidr/src/main/resources/coconut.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -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") {
|
||||||
|
|
|
@ -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,20 +150,26 @@ 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 {
|
|
||||||
toolchainVersion.text = version ?: ""
|
|
||||||
toolchainVersion.foreground = JBColor.foreground()
|
|
||||||
|
|
||||||
if (!stdFieldOverride.isSelected) {
|
if (!stdFieldOverride.isSelected) {
|
||||||
pathToStd.text = stdPath?.pathString ?: ""
|
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()
|
||||||
|
|
||||||
|
if (!stdFieldOverride.isSelected) {
|
||||||
|
pathToStd.text = stdPath?.pathString ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue