begin DAP port
This commit is contained in:
parent
fc760497a9
commit
f8def6e1de
3 changed files with 798 additions and 0 deletions
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.dap
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
|
||||
class BlockingPipedInputStream(src: PipedOutputStream, pipeSize: Int) : PipedInputStream(src, pipeSize) {
|
||||
var closed = false
|
||||
|
||||
override fun read(): Int {
|
||||
if (closed) {
|
||||
throw IOException("stream closed")
|
||||
} else {
|
||||
while (super.`in` < 0) {
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
this as java.lang.Object
|
||||
|
||||
this.notifyAll()
|
||||
|
||||
try {
|
||||
this.wait(750)
|
||||
} catch (e: InterruptedException) {
|
||||
throw InterruptedIOException()
|
||||
}
|
||||
}
|
||||
|
||||
val ret = buffer[this.out++].toUInt()
|
||||
if (this.out >= buffer.size) {
|
||||
this.out = 0
|
||||
}
|
||||
|
||||
if (this.`in` == this.out) {
|
||||
this.`in` = -1
|
||||
}
|
||||
|
||||
return ret.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
closed = true
|
||||
super.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* 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.dap
|
||||
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.process.BaseProcessHandler
|
||||
import com.intellij.execution.process.ProcessAdapter
|
||||
import com.intellij.execution.process.ProcessEvent
|
||||
import com.intellij.execution.process.ProcessOutputType
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.KeyWithDefaultValue
|
||||
import com.intellij.util.progress.getMaybeCancellable
|
||||
import com.intellij.util.system.CpuArch
|
||||
import com.jetbrains.cidr.ArchitectureType
|
||||
import com.jetbrains.cidr.execution.Installer
|
||||
import com.jetbrains.cidr.execution.debugger.backend.*
|
||||
import com.jetbrains.cidr.execution.debugger.memory.Address
|
||||
import com.jetbrains.cidr.system.HostMachine
|
||||
import com.jetbrains.cidr.system.LocalHost
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.future.asDeferred
|
||||
import org.eclipse.lsp4j.debug.*
|
||||
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient
|
||||
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
|
||||
import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either3
|
||||
import java.io.File
|
||||
import java.io.PipedOutputStream
|
||||
import java.lang.Exception
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
abstract class DAPDriver<Server : IDebugProtocolServer, Client : IDebugProtocolClient>(
|
||||
handler: Handler,
|
||||
) : DebuggerDriver(handler) {
|
||||
private lateinit var driverName: String
|
||||
private lateinit var processHandler: BaseProcessHandler<*>
|
||||
protected lateinit var client: Client
|
||||
protected lateinit var server: Server
|
||||
@Volatile
|
||||
protected lateinit var capabilities: Capabilities
|
||||
protected lateinit var initializeFuture: Job
|
||||
|
||||
protected abstract fun createDebuggerClient(): Client
|
||||
protected abstract fun getServerInterface(): Class<Server>
|
||||
protected abstract fun wrapMessageConsumer(mc: MessageConsumer): MessageConsumer
|
||||
protected abstract suspend fun postInitialize(capabilities: Capabilities)
|
||||
|
||||
fun initialize(config: DAPDebuggerDriverConfiguration) {
|
||||
driverName = config.driverName
|
||||
processHandler = createDebugProcessHandler(config.createDriverCommandLine(this, ArchitectureType.forVmCpuArch(
|
||||
CpuArch.CURRENT
|
||||
)), config)
|
||||
|
||||
val pipeOutput = PipedOutputStream()
|
||||
val pipeInput = BlockingPipedInputStream(pipeOutput, 1024 * 1024)
|
||||
processHandler.addProcessListener(object: ProcessAdapter() {
|
||||
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
|
||||
if (ProcessOutputType.isStdout(outputType)) {
|
||||
val text = event.text ?: return
|
||||
pipeOutput.write(text.toByteArray(Charsets.UTF_8))
|
||||
pipeOutput.flush()
|
||||
}
|
||||
}
|
||||
})
|
||||
client = createDebuggerClient()
|
||||
val executorServer = Executors.newSingleThreadExecutor()
|
||||
val launcher = DebugLauncher.createLauncher(client, getServerInterface(), pipeInput, processHandler.processInput, executorServer, this::wrapMessageConsumer)
|
||||
server = launcher.remoteProxy
|
||||
launcher.startListening()
|
||||
|
||||
val initArgs = InitializeRequestArguments()
|
||||
|
||||
//Identification
|
||||
initArgs.clientID = "zigbrains"
|
||||
initArgs.clientName = "ZigBrains"
|
||||
|
||||
//IntelliJ editor thing
|
||||
initArgs.linesStartAt1 = true
|
||||
initArgs.columnsStartAt1 = true
|
||||
|
||||
initArgs.supportsMemoryReferences = true
|
||||
initArgs.supportsVariableType = false
|
||||
|
||||
|
||||
config.customizeInitializeArguments(initArgs)
|
||||
|
||||
initializeFuture = zigCoroutineScope.launch {
|
||||
val caps = server.initialize(initArgs).asDeferred().await()
|
||||
capabilities = caps
|
||||
postInitialize(caps)
|
||||
}
|
||||
}
|
||||
|
||||
override fun supportsWatchpointLifetime(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun supportsMemoryWrite(): Boolean {
|
||||
return capabilities.supportsWriteMemoryRequest
|
||||
}
|
||||
|
||||
override fun writeMemory(address: Address, bytes: ByteArray) {
|
||||
val args = WriteMemoryArguments()
|
||||
args.memoryReference = "0x${address.unsignedLongValue.toString(16)}"
|
||||
args.data = bytes.encodeBase64()
|
||||
server.writeMemory(args).getSafe()
|
||||
}
|
||||
|
||||
override fun getProcessHandler(): BaseProcessHandler<*> {
|
||||
return processHandler
|
||||
}
|
||||
|
||||
override fun isInPromptMode(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getHostMachine(): HostMachine {
|
||||
return LocalHost.INSTANCE
|
||||
}
|
||||
|
||||
override fun setValuesFilteringEnabled(p0: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
protected inner class DAPInferior: Inferior() {
|
||||
override fun startImpl(): Long {
|
||||
server.configurationDone(ConfigurationDoneArguments()).getSafe()
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun detachImpl() {
|
||||
val args = DisconnectArguments()
|
||||
server.disconnect(args).getSafe()
|
||||
}
|
||||
|
||||
override fun destroyImpl(): Boolean {
|
||||
detachImpl()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadForLaunch(installer: Installer, s: String?): Inferior {
|
||||
val cli = installer.install()
|
||||
val args = HashMap<String, Any>()
|
||||
args["program"] = Util.toWinPath(cli.exePath)
|
||||
args["cmd"] = cli.workDirectory.toString()
|
||||
args["name"] = "CPP Debug"
|
||||
args["type"] = "cppvsdbg"
|
||||
args["request"] = "launch"
|
||||
args["console"] = "integratedTerminal"
|
||||
args["logging"] = mapOf("moduleLoad" to true)
|
||||
args["__configurationTarget"] = 2
|
||||
val params = cli.parametersList.array
|
||||
if (params.isNotEmpty()) {
|
||||
args["args"] = params
|
||||
}
|
||||
server.launch(args).getSafe()
|
||||
runBlocking {
|
||||
initializeFuture.join()
|
||||
}
|
||||
return DAPInferior()
|
||||
}
|
||||
|
||||
override fun loadCoreDump(p0: File, p1: File?, p2: File?, p3: MutableList<PathMapping>): Inferior {
|
||||
notSupported()
|
||||
}
|
||||
|
||||
override fun loadCoreDump(
|
||||
p0: File,
|
||||
p1: File?,
|
||||
p2: File?,
|
||||
p3: MutableList<PathMapping>,
|
||||
p4: MutableList<String>
|
||||
): Inferior {
|
||||
notSupported()
|
||||
}
|
||||
|
||||
override fun loadForAttach(p0: Int): Inferior {
|
||||
notSupported()
|
||||
}
|
||||
|
||||
override fun loadForAttach(p0: String, p1: Boolean): Inferior {
|
||||
notSupported()
|
||||
}
|
||||
|
||||
override fun loadForRemote(p0: String, p1: File?, p2: File?, p3: MutableList<PathMapping>): Inferior {
|
||||
notSupported()
|
||||
}
|
||||
|
||||
override fun interrupt(): Boolean {
|
||||
val pause = PauseArguments()
|
||||
pause.threadId = -1
|
||||
server.pause(pause)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun resume(): Boolean {
|
||||
val args = ContinueArguments()
|
||||
server.continue_(args)
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun stepOver(p0: Boolean) {
|
||||
deprecated()
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun stepInto(p0: Boolean, p1: Boolean) {
|
||||
deprecated()
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun stepOut(p0: Boolean) {
|
||||
deprecated()
|
||||
}
|
||||
|
||||
override fun stepOver(thread: LLThread, stepByInstruction: Boolean) {
|
||||
val args = NextArguments()
|
||||
args.threadId = Math.toIntExact(thread.id)
|
||||
args.granularity = if (stepByInstruction)
|
||||
SteppingGranularity.INSTRUCTION
|
||||
else
|
||||
SteppingGranularity.LINE
|
||||
server.next(args)
|
||||
}
|
||||
|
||||
override fun stepInto(thread: LLThread, forceStepIntoFramesWithNoDebugInfo: Boolean, stepByInstruction: Boolean) {
|
||||
val args = StepInArguments()
|
||||
args.targetId = Math.toIntExact(thread.id)
|
||||
args.granularity = if (stepByInstruction)
|
||||
SteppingGranularity.INSTRUCTION
|
||||
else
|
||||
SteppingGranularity.LINE
|
||||
server.stepIn(args)
|
||||
}
|
||||
|
||||
override fun stepOut(thread: LLThread, stopInFramesWithNoDebugInfo: Boolean) {
|
||||
val args = StepOutArguments()
|
||||
args.threadId = Math.toIntExact(thread.id)
|
||||
server.stepOut(args)
|
||||
}
|
||||
|
||||
override fun runTo(path: String, line: Int) {
|
||||
val targetArgs = GotoTargetsArguments()
|
||||
val src = Util.toSource(path)
|
||||
targetArgs.source = src
|
||||
targetArgs.line = line
|
||||
zigCoroutineScope.launch {
|
||||
val locations = server.gotoTargets(targetArgs).asDeferred().await()
|
||||
val targets = locations.targets.firstOrNull() ?: throw RuntimeException("Could not find runTo target!")
|
||||
val args = GotoArguments()
|
||||
args.targetId = targets.id
|
||||
args.threadId = -1
|
||||
server.goto_(args)
|
||||
}
|
||||
}
|
||||
|
||||
override fun runTo(p0: Address) {
|
||||
throw UnsupportedOperationException("RunTo address not implemented!")
|
||||
}
|
||||
|
||||
override fun doExit(): Boolean {
|
||||
val disconnectArgs = DisconnectArguments()
|
||||
disconnectArgs.terminateDebuggee = true
|
||||
server.disconnect(disconnectArgs)
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO
|
||||
override fun jumpToLine(thread: LLThread, path: String, line: Int, canLeaveFunction: Boolean): StopPlace {
|
||||
throw DebuggerCommandException("Can't resolve address for line $path:$line")
|
||||
}
|
||||
|
||||
// TODO
|
||||
override fun jumpToAddress(thread: LLThread, address: Address, canLeaveFunction: Boolean): StopPlace {
|
||||
throw DebuggerCommandException("Can't jump to address $address")
|
||||
}
|
||||
|
||||
override fun addPathMapping(index: Int, from: String, to: String) {
|
||||
throw ExecutionException("addPathMapping not implemented!")
|
||||
}
|
||||
|
||||
override fun addForcedFileMapping(index: Int, from: String, hash: DebuggerSourceFileHash?, to: String) {
|
||||
addPathMapping(index, from, to)
|
||||
}
|
||||
|
||||
override fun completeConsoleCommand(p0: String, p1: Int): ResultList<String> {
|
||||
throw ExecutionException("completeConsoleCommand")
|
||||
}
|
||||
|
||||
override fun addWatchpoint(
|
||||
threadId: Long,
|
||||
frameIndex: Int,
|
||||
value: LLValue,
|
||||
expr: String,
|
||||
lifetime: LLWatchpoint.Lifetime?,
|
||||
accessType: LLWatchpoint.AccessType
|
||||
): LLWatchpoint {
|
||||
throw ExecutionException("TODO")
|
||||
}
|
||||
|
||||
override fun removeWatchpoint(ids: MutableList<Int>) {
|
||||
throw ExecutionException("TODO")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
val bp = SourceBreakpoint()
|
||||
bp.line = line + 1
|
||||
bp.condition = condition
|
||||
val bps = breakpoints.values.filter { it.ref.isFirst && it.ref.first.path == path }
|
||||
.map { it.ref.first.src }
|
||||
.toMutableList()
|
||||
|
||||
bps.add(bp)
|
||||
val bpsRes = updateSourceBreakpoints(path, bps)
|
||||
|
||||
val dapBP = bpsRes.last()
|
||||
|
||||
val mbp = MappedBreakpoint(dapBP, Either3.forFirst(PathedSourceBreakpoint(path, bp)))
|
||||
|
||||
breakpoints.compute(dapBP.id, { _, _ -> mbp})
|
||||
|
||||
return AddBreakpointResult(mbp.java, listOfNotNull(mbp.loc))
|
||||
}
|
||||
|
||||
private fun updateSourceBreakpoints(path: String, bps: List<SourceBreakpoint>): Array<Breakpoint> {
|
||||
val args = SetBreakpointsArguments()
|
||||
val src = Util.toSource(path)
|
||||
args.source = src
|
||||
args.breakpoints = bps.toTypedArray()
|
||||
args.sourceModified = false
|
||||
val res = server.setBreakpoints(args).getSafe()
|
||||
return res.breakpoints
|
||||
}
|
||||
|
||||
override fun addSymbolicBreakpoint(symBreakpoint: SymbolicBreakpoint): LLSymbolicBreakpoint? {
|
||||
if (!capabilities.supportsFunctionBreakpoints)
|
||||
throw DebuggerCommandException("Server doesn't support function breakpoints!")
|
||||
|
||||
val fbp = FunctionBreakpoint()
|
||||
fbp.name = symBreakpoint.pattern
|
||||
fbp.condition = symBreakpoint.condition
|
||||
|
||||
val bps = breakpoints.values.filter { it.ref.isSecond }.map { it.ref.second }.toMutableList()
|
||||
|
||||
bps.add(fbp)
|
||||
|
||||
val bpsRes = updateSymbolicBreakpoints(bps)
|
||||
|
||||
val dapBP = bpsRes.last()
|
||||
|
||||
val mbp = MappedBreakpoint(dapBP, Either3.forSecond(fbp))
|
||||
|
||||
breakpoints.compute(dapBP.id, {_, _ -> mbp})
|
||||
|
||||
return LLSymbolicBreakpoint(mbp.id)
|
||||
}
|
||||
|
||||
private fun updateSymbolicBreakpoints(bps: List<FunctionBreakpoint>): Array<Breakpoint> {
|
||||
val args = SetFunctionBreakpointsArguments()
|
||||
args.breakpoints = bps.toTypedArray()
|
||||
val res = server.setFunctionBreakpoints(args).getSafe()
|
||||
return res.breakpoints
|
||||
}
|
||||
|
||||
override fun addAddressBreakpoint(address: Address, condition: String?): AddBreakpointResult {
|
||||
if (!capabilities.supportsInstructionBreakpoints)
|
||||
throw DebuggerCommandException("Server doesn't support instruction breakpoints!")
|
||||
val ibp = InstructionBreakpoint()
|
||||
ibp.instructionReference = Util.stringifyAddress(address.unsignedLongValue)
|
||||
ibp.condition = condition
|
||||
val bps = breakpoints.values.filter { it.ref.isThird }.map { it.ref.third }.toMutableList()
|
||||
|
||||
bps.add(ibp)
|
||||
|
||||
val bpsRes = updateAddressBreakpoints(bps)
|
||||
|
||||
val dapBP = bpsRes.last()
|
||||
|
||||
val mbp = MappedBreakpoint(dapBP, Either3.forThird(ibp))
|
||||
|
||||
breakpoints.compute(dapBP.id, {_, _ -> mbp})
|
||||
|
||||
return AddBreakpointResult(mbp.java, listOfNotNull(mbp.loc))
|
||||
}
|
||||
|
||||
private fun updateAddressBreakpoints(bps: List<InstructionBreakpoint>): Array<Breakpoint> {
|
||||
val args = SetInstructionBreakpointsArguments()
|
||||
args.breakpoints = bps.toTypedArray()
|
||||
return server.setInstructionBreakpoints(args).getSafe().breakpoints
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LLVALUE_FRAME = Key.create<Int>("DAPDriver.LLVALUE_FRAME")
|
||||
val LLVALUE_CHILDREN_REF = KeyWithDefaultValue.create<Int>("DAPDriver.LLVALUE_CHILDREN_REF", 0)
|
||||
val LLVALUE_DATA = Key.create<LLValueData>("DAPDriver.LLVALUE_DATA")
|
||||
val LLVALUE_CHILDREN = Key.create<List<LLValue>>("DAPDriver.LLVALUE_CHILDREN")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
fun <T> CompletableFuture<T>.getSafe(): T {
|
||||
try {
|
||||
return getMaybeCancellable()
|
||||
} catch (e: Exception) {
|
||||
throw ExecutionException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notSupported(): Nothing {
|
||||
throw ExecutionException("Not supported")
|
||||
}
|
||||
|
||||
private fun deprecated(): Nothing {
|
||||
throw ExecutionException("Deprecated")
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright 2023-2024 FalsePattern
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.falsepattern.zigbrains.debugger.dap
|
||||
|
||||
import com.falsepattern.zigbrains.debugger.ZigDebuggerLanguage
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.findDocument
|
||||
import com.jetbrains.cidr.execution.debugger.backend.*
|
||||
import com.jetbrains.cidr.execution.debugger.memory.Address
|
||||
import com.jetbrains.cidr.execution.debugger.memory.AddressRange
|
||||
import org.eclipse.lsp4j.debug.*
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object Util {
|
||||
fun threadJBFromDAP(DAPThread: Thread): LLThread {
|
||||
return LLThread(DAPThread.id.toLong(), null, null, DAPThread.name, null)
|
||||
}
|
||||
|
||||
fun threadDAPFromJB(JBThread: LLThread): Thread {
|
||||
val DAPThread = Thread()
|
||||
DAPThread.id = JBThread.getId().toInt()
|
||||
return DAPThread
|
||||
}
|
||||
|
||||
fun breakpointJBFromDAP(DAPBreakpoint: Breakpoint): LLBreakpoint {
|
||||
val source = DAPBreakpoint.source
|
||||
var sourcePath = if (source == null) "" else Objects.requireNonNullElseGet(
|
||||
source.path
|
||||
) { Objects.requireNonNullElse(source.origin, "unknown") }
|
||||
sourcePath = toJBPath(sourcePath)!!
|
||||
return LLBreakpoint(
|
||||
DAPBreakpoint.id,
|
||||
sourcePath,
|
||||
Objects.requireNonNullElse<Int>(DAPBreakpoint.line, 0) - 1,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
fun getLocation(DAPBreakpoint: Breakpoint): LLBreakpointLocation? {
|
||||
val ref = DAPBreakpoint.instructionReference ?: return null
|
||||
val addr = ref.substring(2).toLong(16)
|
||||
var fl: FileLocation? = null
|
||||
val src = DAPBreakpoint.source
|
||||
if (src != null) {
|
||||
fl = FileLocation(src.path, DAPBreakpoint.line)
|
||||
}
|
||||
return LLBreakpointLocation(DAPBreakpoint.id.toString() + "", Address.fromUnsignedLong(addr), fl)
|
||||
}
|
||||
|
||||
fun breakpointDAPFromJB(JBBreakpoint: LLBreakpoint): Breakpoint {
|
||||
val DAPBreakpoint = Breakpoint()
|
||||
DAPBreakpoint.id = JBBreakpoint.getId()
|
||||
DAPBreakpoint.line = JBBreakpoint.getOrigLine() + 1
|
||||
val source = Source()
|
||||
source.path = JBBreakpoint.getOrigFile()
|
||||
DAPBreakpoint.source = source
|
||||
DAPBreakpoint.message = JBBreakpoint.getCondition()
|
||||
return DAPBreakpoint
|
||||
}
|
||||
|
||||
fun moduleJBFromDAP(DAPModule: Module): LLModule {
|
||||
return LLModule(toJBPath(DAPModule.path))
|
||||
}
|
||||
|
||||
fun moduleDAPFromJB(JBModule: LLModule): Module {
|
||||
val DAPModule = Module()
|
||||
DAPModule.path = toJBPath(JBModule.path)
|
||||
DAPModule.name = JBModule.name
|
||||
return DAPModule
|
||||
}
|
||||
|
||||
fun frameJBFromDAP(
|
||||
DAPFrame: StackFrame,
|
||||
helperBreakpoint: DAPDriver.MappedBreakpoint?,
|
||||
modules: Map<Int, DAPDriver.MappedModule>
|
||||
): LLFrame {
|
||||
val ptr = parseAddress(DAPFrame.instructionPointerReference)
|
||||
val name = DAPFrame.name
|
||||
val inline = name.startsWith("[Inline Frame] ")
|
||||
val function = name.substring(name.indexOf('!') + 1, name.indexOf('('))
|
||||
val moduleID = DAPFrame.moduleId
|
||||
var moduleName: String? = null
|
||||
if (moduleID != null) {
|
||||
if (moduleID.isRight) {
|
||||
moduleName = moduleID.right
|
||||
} else {
|
||||
val module = modules[moduleID.left]!!
|
||||
moduleName = module.java.name
|
||||
}
|
||||
}
|
||||
var line = DAPFrame.line
|
||||
var sourcePath: String?
|
||||
run {
|
||||
val src = DAPFrame.source
|
||||
sourcePath = if (src == null) null else toJBPath(src.path)
|
||||
}
|
||||
if (helperBreakpoint != null) {
|
||||
if (line == 0) {
|
||||
line = helperBreakpoint.dap.line
|
||||
}
|
||||
if (sourcePath == null) {
|
||||
val src = helperBreakpoint.dap.source
|
||||
if (src != null) {
|
||||
sourcePath = toJBPath(src.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return LLFrame(
|
||||
DAPFrame.id,
|
||||
function,
|
||||
sourcePath,
|
||||
null,
|
||||
line - 1,
|
||||
ptr,
|
||||
ZigDebuggerLanguage,
|
||||
false,
|
||||
inline,
|
||||
moduleName
|
||||
)
|
||||
}
|
||||
|
||||
fun toSource(path: String): Source {
|
||||
val src = Source()
|
||||
val absolute = Path.of(path).toAbsolutePath()
|
||||
src.name = absolute.fileName.toString()
|
||||
src.path = toWinPath(absolute.toString())
|
||||
return src
|
||||
}
|
||||
|
||||
fun toWinPath(path: String): String {
|
||||
return path.replace('/', '\\')
|
||||
}
|
||||
|
||||
fun toJBPath(path: String): String {
|
||||
return path.replace('\\', '/')
|
||||
}
|
||||
|
||||
fun parseAddressNullable(address: String?): Long? {
|
||||
if (address == null) return null
|
||||
return parseAddress(address)
|
||||
}
|
||||
|
||||
fun parseAddress(address: String?): Long {
|
||||
if (address == null) return 0L
|
||||
if (!address.startsWith("0x")) return java.lang.Long.parseUnsignedLong(address)
|
||||
return java.lang.Long.parseUnsignedLong(address.substring(2), 16)
|
||||
}
|
||||
|
||||
fun stringifyAddress(address: Long): String {
|
||||
return "0x" + java.lang.Long.toHexString(address)
|
||||
}
|
||||
|
||||
private val HEX_FIX_REGEX: Pattern = Pattern.compile("([0-9A-F]+)(?<!\\W)h")
|
||||
suspend fun instructionJBFromDAP(
|
||||
DAPInstruction: DisassembledInstruction,
|
||||
loc: Source?,
|
||||
startLine: Int?,
|
||||
endLine: Int?,
|
||||
uniq: Boolean,
|
||||
symbol: LLSymbolOffset?
|
||||
): LLInstruction {
|
||||
var startLine = startLine
|
||||
var endLine = endLine
|
||||
val address: Address = Address.parseHexString(DAPInstruction.address)
|
||||
val byteStrings =
|
||||
DAPInstruction.instructionBytes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val bytes = ArrayList<Byte>(byteStrings.size)
|
||||
for (byteString in byteStrings) {
|
||||
bytes.add(byteString.toInt(16) as Byte)
|
||||
}
|
||||
val result: ArrayList<LLInstruction> = ArrayList<LLInstruction>()
|
||||
var comment: String? = null
|
||||
if (loc != null && startLine != null && endLine != null && uniq) run {
|
||||
val pathStr = toJBPath(loc.path)
|
||||
val path: Path
|
||||
try {
|
||||
path = Path.of(pathStr)
|
||||
} catch (ignored: InvalidPathException) {
|
||||
return@run
|
||||
}
|
||||
val text = readAction {
|
||||
val file = VfsUtil.findFile(path, true) ?: return@readAction null
|
||||
val doc: Document = file.findDocument() ?: return@readAction null
|
||||
doc.immutableCharSequence.toString().split("(\r\n|\r|\n)".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
}
|
||||
if (text == null) return@run
|
||||
startLine -= 1
|
||||
endLine -= 1
|
||||
if (text.size <= endLine) return@run
|
||||
comment = text[endLine]
|
||||
}
|
||||
val nicerDisassembly = StringBuilder()
|
||||
val disassembly = DAPInstruction.instruction
|
||||
val matcher = HEX_FIX_REGEX.matcher(disassembly)
|
||||
var prevEnd = 0
|
||||
while (matcher.find()) {
|
||||
nicerDisassembly.append(disassembly, prevEnd, matcher.start())
|
||||
val hex = matcher.group(1).lowercase(Locale.getDefault())
|
||||
nicerDisassembly.append("0x").append(hex)
|
||||
prevEnd = matcher.end()
|
||||
}
|
||||
if (prevEnd < disassembly.length) nicerDisassembly.append(disassembly, prevEnd, disassembly.length)
|
||||
return LLInstruction.create(
|
||||
address,
|
||||
bytes,
|
||||
nicerDisassembly.toString(),
|
||||
comment,
|
||||
symbol
|
||||
)
|
||||
}
|
||||
|
||||
fun memoryJBFromDAP(DAPMemory: ReadMemoryResponse): LLMemoryHunk {
|
||||
val address = parseAddress(DAPMemory.address)
|
||||
val bytes = Base64.getDecoder().decode(DAPMemory.data)
|
||||
val range = AddressRange(
|
||||
Address.fromUnsignedLong(address),
|
||||
Address.fromUnsignedLong(address + bytes.size - 1)
|
||||
)
|
||||
return LLMemoryHunk(range, bytes)
|
||||
}
|
||||
|
||||
@Throws(com.intellij.execution.ExecutionException::class)
|
||||
fun <T> get(future: CompletableFuture<T>): T {
|
||||
try {
|
||||
return future[4, TimeUnit.SECONDS]
|
||||
} catch (e: InterruptedException) {
|
||||
throw com.intellij.execution.ExecutionException(e)
|
||||
} catch (e: TimeoutException) {
|
||||
throw com.intellij.execution.ExecutionException(e)
|
||||
} catch (e: ExecutionException) {
|
||||
throw com.intellij.execution.ExecutionException(e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
fun emptyIfNull(str: String?): String {
|
||||
return str ?: ""
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue