From b5befcf5d5aac98201139710a037b5a1f01d5c80 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 17 Nov 2017 11:46:58 +0300 Subject: [PATCH] [debug] it's possible to inspect array elements in debugger --- .../jetbrains/kotlin/compiletest/Matchers.kt | 11 +- .../jetbrains/kotlin/compiletest/LldbTests.kt | 16 +++ llvmDebugInfoC/src/scripts/konan_lldb.py | 106 +++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/backend.native/debugger-tests/src/main/kotlin/org/jetbrains/kotlin/compiletest/Matchers.kt b/backend.native/debugger-tests/src/main/kotlin/org/jetbrains/kotlin/compiletest/Matchers.kt index bba9279621b..65dcaa03673 100644 --- a/backend.native/debugger-tests/src/main/kotlin/org/jetbrains/kotlin/compiletest/Matchers.kt +++ b/backend.native/debugger-tests/src/main/kotlin/org/jetbrains/kotlin/compiletest/Matchers.kt @@ -133,7 +133,16 @@ private class LldbSessionSpecification private constructor( && commands.zip(executedCommands).all { (cmd, h) -> h == "(lldb) $cmd" } if (!responsesMatch) { - fail("Responses do not match commands.\nResponses: $executedCommands\nCommands: $commands") + val message = """ +Responses do not match commands. + +COMMANDS: $commands +RESPONSES: $executedCommands + +FULL SESSION: +$output +""" + fail(message) } for ((patternBody, command) in patterns.zip(bodies).zip(executedCommands)) { diff --git a/backend.native/debugger-tests/src/test/kotlin/org/jetbrains/kotlin/compiletest/LldbTests.kt b/backend.native/debugger-tests/src/test/kotlin/org/jetbrains/kotlin/compiletest/LldbTests.kt index 4d9b72967e2..452f2dc8b9f 100644 --- a/backend.native/debugger-tests/src/test/kotlin/org/jetbrains/kotlin/compiletest/LldbTests.kt +++ b/backend.native/debugger-tests/src/test/kotlin/org/jetbrains/kotlin/compiletest/LldbTests.kt @@ -94,4 +94,20 @@ class LldbTests { (ObjHeader *) xs = [1, 2, 3] (ObjHeader *) ys = [Point(x=1, y=2), null] """) + + @Test + fun `can inspect array children`() = lldbTest(""" + fun main(args: Array) { + val xs = intArrayOf(3, 5, 8) + return + } + + data class Point(val x: Int, val y: Int) + """, """ + > type summary add "ObjHeader *" --inline-children + > b main.kt:3 + > r + > fr var xs + (ObjHeader *) xs = [..] (0 = 3, 1 = 5, 2 = 8) + """) } \ No newline at end of file diff --git a/llvmDebugInfoC/src/scripts/konan_lldb.py b/llvmDebugInfoC/src/scripts/konan_lldb.py index 30a38def65d..ba780e72819 100644 --- a/llvmDebugInfoC/src/scripts/konan_lldb.py +++ b/llvmDebugInfoC/src/scripts/konan_lldb.py @@ -24,17 +24,28 @@ import lldb import ctypes +def lldb_val_to_ptr(lldb_val): + addr = lldb_val.GetValueAsUnsigned() + if addr == 0: + return None + return '((struct ObjHeader *) {})'.format(addr) + def kotlin_object_type_summary(lldb_val, internal_dict): """Hook that is run by lldb to display a Kotlin object.""" fallback = lldb_val.GetValue() if str(lldb_val.type) != "struct ObjHeader *": return fallback + ptr = lldb_val_to_ptr(lldb_val) + if ptr is None: + return fallback + def evaluate(expr): return lldb_val.GetTarget().EvaluateExpression(expr, lldb.SBExpressionOptions()) + buff_len = evaluate( - "(int)Konan_DebugObjectToUtf8Array((struct ObjHeader *) %s, (char *)Konan_DebugBuffer(), (int)Konan_DebugBufferSize());" % lldb_val.GetValueAsUnsigned() + "(int)Konan_DebugObjectToUtf8Array({}, (char *)Konan_DebugBuffer(), (int)Konan_DebugBufferSize());".format(ptr) ).unsigned if not buff_len: @@ -47,12 +58,105 @@ def kotlin_object_type_summary(lldb_val, internal_dict): return s if error.Success() else fallback +class KonanArraySyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + + self._element_type = None + self._fn_prefix = None + self._num_children = None + self._ptr = None + + def update(self): + self._element_type = None + self._fn_prefix = None + self._num_children = None + self._ptr = lldb_val_to_ptr(self.valobj) + + if self._ptr is None: + return + + types = [("Boolean", "uint8_t"), + ("Byte", "int8_t"), + ("Char", "uint16_t"), + ("Short", "int16_t"), + ("Int", "int32_t"), + ("Long", "int64_t"), + ("Float", "float"), + ("Double", "double")] + + for (ktype, ctype) in types: + instance_check = "IsInstance({self._ptr}, the{ktype}ArrayTypeInfo)".format( + self=self, ktype=ktype + ) + if self._evaluate_bool(instance_check): + self._element_type = ctype + self._fn_prefix = "Kotlin_{}Array".format(ktype) + break + else: + return + + self._num_children = self._evaluate_int( + "%s_getArrayLength(%s)" % (self._fn_prefix, self._ptr) + ) + + def num_children(self): + if self._element_type is None: + return None + + return self._num_children + + def has_children(self): + if self._element_type is None: + return None + + return self._num_children > 0 + + def get_child_index(self, name): + if self._element_type is None: + return None + + try: + index = int(name) + except ValueError: + return None + + return index if (0 <= index < self._num_children) else None + + def get_child_at_index(self, index): + if self._element_type is None: + return None + + if not (0 <= index < self._num_children): + return None + return self._evaluate( + "({self._element_type}){self._fn_prefix}_get({self._ptr}, {index})".format(self=self, index=index), + name=str(index) + ) + + def _evaluate(self, expr, name="tmp"): + return self.valobj.CreateValueFromExpression(name, expr) + + def _evaluate_bool(self, expr): + return self._evaluate("(uint8_t)" + expr).unsigned == 1 + + def _evaluate_int(self, expr): + return self._evaluate("(int32_t)" + expr).signed + + def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand('\ type summary add \ --no-value \ + --expand \ --python-function konan_lldb.kotlin_object_type_summary \ "ObjHeader *" \ --category Kotlin\ ') + debugger.HandleCommand('\ + type synthetic add \ + --python-class konan_lldb.KonanArraySyntheticChildrenProvider\ + "ObjHeader *" \ + --category Kotlin\ + ') debugger.HandleCommand('type category enable Kotlin')