Step through the <compiler-generated> functions via a stop hook in konan_lldb.py
^KT-63598
This commit is contained in:
committed by
Space Team
parent
b83d8595e5
commit
04dec2769b
@@ -627,6 +627,39 @@ class KonanStepOut(KonanStep):
|
|||||||
return self.thread_plan.QueueThreadPlanForStepOut(0)
|
return self.thread_plan.QueueThreadPlanForStepOut(0)
|
||||||
|
|
||||||
|
|
||||||
|
KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS = 'KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS'
|
||||||
|
MAX_SIZE_FOR_STOP_REASON = 20
|
||||||
|
PLAN_FROM_STOP_REASON = {
|
||||||
|
'step in' : KonanStepIn.__name__,
|
||||||
|
'step out' : KonanStepOut.__name__,
|
||||||
|
'step over' : KonanStepOver.__name__,
|
||||||
|
}
|
||||||
|
|
||||||
|
class KonanHook:
|
||||||
|
def __init__(self, target, extra_args, _):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_stop(self, execution_context, stream) -> bool:
|
||||||
|
is_bridging_functions_skip_enabled = not execution_context.target.GetEnvironment().Get(KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS)
|
||||||
|
|
||||||
|
def is_kotlin_bridging_function() -> bool:
|
||||||
|
addr = execution_context.frame.addr
|
||||||
|
function_name = addr.function.name
|
||||||
|
if function_name is None:
|
||||||
|
return False
|
||||||
|
file_name = addr.line_entry.file.basename
|
||||||
|
if file_name is None:
|
||||||
|
return False
|
||||||
|
return function_name.startswith('objc2kotlin_') and file_name == '<compiler-generated>'
|
||||||
|
|
||||||
|
if is_bridging_functions_skip_enabled and is_kotlin_bridging_function():
|
||||||
|
stop_reason = execution_context.frame.thread.GetStopDescription(MAX_SIZE_FOR_STOP_REASON)
|
||||||
|
plan = PLAN_FROM_STOP_REASON.get(stop_reason)
|
||||||
|
if plan is not None:
|
||||||
|
execution_context.thread.StepUsingScriptedThreadPlan('{}.{}'.format(__name__, plan))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def __lldb_init_module(debugger, _):
|
def __lldb_init_module(debugger, _):
|
||||||
log(lambda: "init start")
|
log(lambda: "init start")
|
||||||
__FACTORY['object'] = lambda x, y, z: KonanObjectSyntheticProvider(x, y, z)
|
__FACTORY['object'] = lambda x, y, z: KonanObjectSyntheticProvider(x, y, z)
|
||||||
@@ -652,4 +685,5 @@ def __lldb_init_module(debugger, _):
|
|||||||
debugger.HandleCommand('command script add -f {}.symbol_by_name_command symbol_by_name'.format(__name__))
|
debugger.HandleCommand('command script add -f {}.symbol_by_name_command symbol_by_name'.format(__name__))
|
||||||
# Avoid Kotlin/Native runtime
|
# Avoid Kotlin/Native runtime
|
||||||
debugger.HandleCommand('settings set target.process.thread.step-avoid-regexp ^::Kotlin_')
|
debugger.HandleCommand('settings set target.process.thread.step-avoid-regexp ^::Kotlin_')
|
||||||
|
debugger.HandleCommand('target stop-hook add -P {}.KonanHook'.format(__name__))
|
||||||
log(lambda: "init end")
|
log(lambda: "init end")
|
||||||
|
|||||||
+276
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.konan.test.blackbox
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.incremental.createDirectory
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.*
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.ObjCFrameworkCompilation
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationArtifact
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationResult.Companion.assertSuccess
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestExecutable
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunCheck
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunCheck.ExecutionTimeout
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunChecks
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.LLDB
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.PipelineType
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Timeouts
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.configurables
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.util.LLDBSessionSpec
|
||||||
|
import org.junit.jupiter.api.Assumptions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
@EnforcedProperty(ClassLevelProperty.COMPILER_OUTPUT_INTERCEPTOR, "NONE")
|
||||||
|
@EnforcedHostTarget
|
||||||
|
class ObjCToKotlinSteppingInLLDBTest : AbstractNativeSimpleTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepInFromObjCToKotlin___WithDisabledStopHook___StopsAtABridgingRoutine() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${CLANG_FILE_NAME}:3
|
||||||
|
> env KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS=1
|
||||||
|
> run
|
||||||
|
> thread step-in
|
||||||
|
[..] stop reason = step in
|
||||||
|
[..]`objc2kotlin_kfun:#bar(){} at <compiler-generated>:1
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepInFromObjCToKotlin___WithDisabledStopHook___StopsAtABridgingRoutine.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepInFromObjCToKotlin___WithStopHook___StepsThroughToKotlinCode() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${CLANG_FILE_NAME}:3
|
||||||
|
> run
|
||||||
|
> thread step-in
|
||||||
|
[..] stop reason = Python thread plan implemented by class konan_lldb.KonanStepIn.
|
||||||
|
[..]`kfun:#bar(){} at lib.kt:1:1
|
||||||
|
-> 1 fun bar() {
|
||||||
|
^
|
||||||
|
2 print("")
|
||||||
|
3 }
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepInFromObjCToKotlin___WithStopHook___StepsThroughToKotlinCode.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepOutFromKotlinToObjC___WithDisabledStopHook___StopsAtABridgingRoutine() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${KOTLIN_FILE_NAME}:2
|
||||||
|
> env KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS=1
|
||||||
|
> run
|
||||||
|
> thread step-out
|
||||||
|
[..] stop reason = step out
|
||||||
|
[..]`objc2kotlin_kfun:#bar(){} at <compiler-generated>:1
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepOutFromKotlinToObjC___WithDisabledStopHook___StopsAtABridgingRoutine.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepOutFromKotlinToObjC___WithStopHook___StepsOutToObjCCode() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${KOTLIN_FILE_NAME}:2
|
||||||
|
> run
|
||||||
|
> thread step-out
|
||||||
|
[..] stop reason = Python thread plan implemented by class konan_lldb.KonanStepOut.
|
||||||
|
[..]`main at main.m:3:5
|
||||||
|
1 @import Kotlin;
|
||||||
|
2 int main() {
|
||||||
|
-> 3 [KotlinLibKt bar];
|
||||||
|
^
|
||||||
|
4 }
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepOutFromKotlinToObjC___WithStopHook___StepsOutToObjCCode.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepOverFromKotlinToObjC___WithDisabledStopHook___StopsAtABridgingRoutine() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${KOTLIN_FILE_NAME}:3
|
||||||
|
> env KONAN_LLDB_DONT_SKIP_BRIDGING_FUNCTIONS=1
|
||||||
|
> run
|
||||||
|
> thread step-over
|
||||||
|
[..] stop reason = step over
|
||||||
|
[..]`objc2kotlin_kfun:#bar(){} at <compiler-generated>:1
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepOverFromKotlinToObjC___WithDisabledStopHook___StopsAtABridgingRoutine.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stepOverFromKotlinToObjC___WithStopHook___StepsOverToObjCCode() {
|
||||||
|
testSteppingFromObjcToKotlin(
|
||||||
|
"""
|
||||||
|
> b ${KOTLIN_FILE_NAME}:3
|
||||||
|
> run
|
||||||
|
> thread step-over
|
||||||
|
[..] stop reason = Python thread plan implemented by class konan_lldb.KonanStepOver.
|
||||||
|
[..]`main at main.m:3:5
|
||||||
|
1 @import Kotlin;
|
||||||
|
2 int main() {
|
||||||
|
-> 3 [KotlinLibKt bar];
|
||||||
|
^
|
||||||
|
4 }
|
||||||
|
> c
|
||||||
|
""".trimIndent(),
|
||||||
|
CLANG_FILE_NAME,
|
||||||
|
KOTLIN_FILE_NAME,
|
||||||
|
"${ObjCToKotlinSteppingInLLDBTest::class.qualifiedName}.${::stepOverFromKotlinToObjC___WithStopHook___StepsOverToObjCCode.name}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testSteppingFromObjcToKotlin(
|
||||||
|
lldbSpec: String,
|
||||||
|
clangFileName: String,
|
||||||
|
kotlinFileName: String,
|
||||||
|
testName: String,
|
||||||
|
) {
|
||||||
|
if (!targets.testTarget.family.isAppleFamily) { Assumptions.abort<Nothing>("This test is supported only on Apple targets") }
|
||||||
|
|
||||||
|
val kotlinFrameworkName = "Kotlin"
|
||||||
|
val clangMainSources = """
|
||||||
|
@import ${kotlinFrameworkName};
|
||||||
|
int main() {
|
||||||
|
[${kotlinFrameworkName}LibKt bar];
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val kotlinLibrarySources = """
|
||||||
|
fun bar() {
|
||||||
|
print("")
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
runTestWithLLDB(
|
||||||
|
kotlinLibrarySources = kotlinLibrarySources,
|
||||||
|
kotlinFileName = kotlinFileName,
|
||||||
|
kotlinFrameworkName = kotlinFrameworkName,
|
||||||
|
clangMainSources = clangMainSources,
|
||||||
|
clangFileName = clangFileName,
|
||||||
|
lldbSpec = lldbSpec,
|
||||||
|
testName = testName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTestWithLLDB(
|
||||||
|
kotlinLibrarySources: String,
|
||||||
|
kotlinFileName: String,
|
||||||
|
kotlinFrameworkName: String,
|
||||||
|
clangMainSources: String,
|
||||||
|
clangFileName: String,
|
||||||
|
lldbSpec: String,
|
||||||
|
testName: String,
|
||||||
|
) {
|
||||||
|
// 1. Create sources
|
||||||
|
val sourceDirectory = buildDir.resolve("sources")
|
||||||
|
sourceDirectory.createDirectory()
|
||||||
|
val clangFile = sourceDirectory.resolve(clangFileName)
|
||||||
|
clangFile.writeText(clangMainSources)
|
||||||
|
sourceDirectory.resolve(kotlinFileName).writeText(kotlinLibrarySources)
|
||||||
|
|
||||||
|
// 2. Build Kotlin framework
|
||||||
|
val freeCompilerArgs = TestCompilerArgs(
|
||||||
|
testRunSettings.get<PipelineType>().compilerFlags + listOf(
|
||||||
|
"-Xstatic-framework",
|
||||||
|
"-Xbinary=bundleId=stub",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val module = generateTestCaseWithSingleModule(sourceDirectory, freeCompilerArgs)
|
||||||
|
ObjCFrameworkCompilation(
|
||||||
|
testRunSettings,
|
||||||
|
freeCompilerArgs = freeCompilerArgs,
|
||||||
|
sourceModules = module.modules,
|
||||||
|
dependencies = emptyList(),
|
||||||
|
expectedArtifact = TestCompilationArtifact.ObjCFramework(
|
||||||
|
buildDir,
|
||||||
|
kotlinFrameworkName,
|
||||||
|
)
|
||||||
|
).result.assertSuccess()
|
||||||
|
|
||||||
|
// 3. Compile the executable
|
||||||
|
val clangExecutableName = "clangMain"
|
||||||
|
val executableFile = File(buildDir, clangExecutableName)
|
||||||
|
|
||||||
|
// FIXME: absoluteTargetToolchain might not work correctly with KONAN_USE_INTERNAL_SERVER because
|
||||||
|
// :kotlin-native:dependencies:update is not a dependency of :native:native.tests:test where this test runs
|
||||||
|
val process = ProcessBuilder(
|
||||||
|
"${testRunSettings.configurables.absoluteTargetToolchain}/bin/clang",
|
||||||
|
clangFile.absolutePath,
|
||||||
|
"-isysroot", testRunSettings.configurables.absoluteTargetSysRoot,
|
||||||
|
"-target", testRunSettings.configurables.targetTriple.toString(),
|
||||||
|
"-g", "-fmodules",
|
||||||
|
"-F", buildDir.absolutePath,
|
||||||
|
"-o", executableFile.absolutePath
|
||||||
|
).redirectErrorStream(true).start()
|
||||||
|
val clangOutput = process.inputStream.readBytes()
|
||||||
|
|
||||||
|
check(
|
||||||
|
process.waitFor() == 0
|
||||||
|
) { clangOutput.decodeToString() }
|
||||||
|
|
||||||
|
// 4. Generate the test case
|
||||||
|
val testExecutable = TestExecutable(
|
||||||
|
TestCompilationArtifact.Executable(executableFile),
|
||||||
|
loggedCompilationToolCall = LoggedData.NoopCompilerCall(buildDir),
|
||||||
|
testNames = listOf(TestName(testName)),
|
||||||
|
)
|
||||||
|
val spec = LLDBSessionSpec.parse(lldbSpec)
|
||||||
|
val moduleForTestCase = TestModule.Exclusive(testName, emptySet(), emptySet(), emptySet())
|
||||||
|
val testCase = TestCase(
|
||||||
|
id = TestCaseId.Named(testName),
|
||||||
|
kind = TestKind.STANDALONE_LLDB,
|
||||||
|
modules = setOf(moduleForTestCase),
|
||||||
|
freeCompilerArgs = freeCompilerArgs,
|
||||||
|
nominalPackageName = PackageName.EMPTY,
|
||||||
|
checks = TestRunChecks(
|
||||||
|
executionTimeoutCheck = ExecutionTimeout.ShouldNotExceed(testRunSettings.get<Timeouts>().executionTimeout),
|
||||||
|
exitCodeCheck = TestRunCheck.ExitCode.Expected(0),
|
||||||
|
expectedFailureCheck = null,
|
||||||
|
outputDataFile = null,
|
||||||
|
outputMatcher = spec.let { TestRunCheck.OutputMatcher { output -> spec.checkLLDBOutput(output, testRunSettings.get()) } },
|
||||||
|
fileCheckMatcher = null,
|
||||||
|
),
|
||||||
|
extras = TestCase.NoTestRunnerExtras(
|
||||||
|
"main",
|
||||||
|
arguments = spec.generateCLIArguments(testRunSettings.get<LLDB>().prettyPrinters)
|
||||||
|
)
|
||||||
|
).apply { initialize(null, null) }
|
||||||
|
|
||||||
|
// 5. Run the test
|
||||||
|
runExecutableAndVerify(testCase, testExecutable)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CLANG_FILE_NAME = "main.m"
|
||||||
|
val KOTLIN_FILE_NAME = "lib.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user