Step through the <compiler-generated> functions via a stop hook in konan_lldb.py

^KT-63598
This commit is contained in:
Timofey Solonin
2023-11-22 13:25:02 +01:00
committed by Space Team
parent b83d8595e5
commit 04dec2769b
2 changed files with 310 additions and 0 deletions
@@ -627,6 +627,39 @@ class KonanStepOut(KonanStep):
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, _):
log(lambda: "init start")
__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__))
# Avoid Kotlin/Native runtime
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")
@@ -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"
}
}