public void Step(DkmStepper stepper, DkmStepArbitrationReason reason) { var thread = stepper.Thread; var process = thread.Process; if (stepper.StepKind == DkmStepKind.StepIntoSpecific) { throw new NotSupportedException(); } else if (_stepper != null) { _stepper.CancelStepper(process.GetPythonRuntimeInstance()); _stepper = null; } // Check if this was a step out (or step over/in that fell through) from native to Python. // If so, we consider the step done, since we can report the correct callstack at this point. if (reason == DkmStepArbitrationReason.TransitionModule) { var beginState = stepper.GetDataItem <StepBeginState>(); if (beginState != null) { thread.GetCurrentFrameInfo(out global::System.UInt64 retAddr, out global::System.UInt64 frameBase, out global::System.UInt64 vframe); if (frameBase >= beginState.FrameBase) { stepper.OnStepComplete(thread, false); return; } } } if (stepper.StepKind == DkmStepKind.Into) { new LocalComponent.BeginStepInNotification { ThreadId = thread.UniqueId }.SendHigher(process); } _stepper = stepper; _stepKind.Write((int)stepper.StepKind + 1); _stepThreadId.Write((uint)thread.SystemPart.Id); _steppingStackDepth.Write(0); }
void IDkmRuntimeStepper.Step(DkmRuntimeInstance runtimeInstance, DkmStepper stepper, DkmStepArbitrationReason reason) { if (!(this as IDkmRuntimeStepper).OwnsCurrentExecutionLocation(runtimeInstance, stepper, reason)) { runtimeInstance.Step(stepper, reason); return; } var processData = DebugHelpers.GetOrCreateDataItem <NullcRemoteProcessDataItem>(runtimeInstance.Process); if (stepper.StepKind == DkmStepKind.StepIntoSpecific) { throw new NotSupportedException(); } if (stepper.StepKind == DkmStepKind.Over || stepper.StepKind == DkmStepKind.Into) { ClearStepBreakpoints(processData); int nullcInstruction = processData.bytecode.ConvertNativeAddressToInstruction(stepper.StartingAddress.CPUInstructionPart.InstructionPointer); int line = processData.bytecode.GetInstructionSourceLocationLine(nullcInstruction, out int moduleIndex); // Instruction based stepping int nextNullcInstruction = nullcInstruction; int secondaryNullcInstruction = 0; while (nextNullcInstruction < processData.bytecode.instructions.Count) { // Walk forward until an instruction with a different line is hit if (processData.bytecode.GetInstructionSourceLocationLine(nextNullcInstruction, out _) != line) { break; } var nextInstruction = processData.bytecode.instructions[nextNullcInstruction]; // If there is a jump, place a breakpoint at the target (even if it's on the same line) if (nextInstruction.code == NullcInstructionCode.rviJmp) { nextNullcInstruction = (int)nextInstruction.argument; break; } // If there is a conditional jump, place two breakpoints - one after it and one at the target (even if either of them is on the same line) if (nextInstruction.code == NullcInstructionCode.rviJmpz || nextInstruction.code == NullcInstructionCode.rviJmpnz) { secondaryNullcInstruction = nextNullcInstruction + 1; nextNullcInstruction = (int)nextInstruction.argument; break; } // If there is a return, I wish I could just fallback to parent runtime to handle it, may be required to place a breakpoint at the call site if (nextInstruction.code == NullcInstructionCode.rviReturn) { ulong address = GetReturnAddress(stepper.Thread); // Address must be within nullc module or we will have to cancel the step if (address >= processData.nativeModuleInstance.BaseAddress && address < processData.nativeModuleInstance.BaseAddress + processData.nativeModuleInstance.Size) { processData.stepBreakpoint = PlaceBreakpointAtAddress(processData, stepper.Thread, address); } else { stepper.OnStepComplete(stepper.Thread, false); } return; } if (stepper.StepKind == DkmStepKind.Into) { if (nextInstruction.code == NullcInstructionCode.rviCall && stepper.StepKind == DkmStepKind.Into) { NullcFuncInfo function = processData.bytecode.functions[(int)nextInstruction.argument]; if (function.regVmAddress != ~0u) { secondaryNullcInstruction = nextNullcInstruction + 1; nextNullcInstruction = (int)function.regVmAddress; break; } else if (function.funcPtrWrap != 0 || function.funcPtrRaw != 0) { // TODO: Step into an external function // I've tryied placing a breakpoint at the external call target but it's not getting handled correctly // Maybe the solution is to single step the process until the stack frame changes } } else if (nextInstruction.code == NullcInstructionCode.rviCallPtr) { // TODO: Step into a function pointer call } } nextNullcInstruction++; } if (nextNullcInstruction != 0) { ulong instructionAddress = processData.bytecode.ConvertInstructionToNativeAddress(nextNullcInstruction); processData.stepBreakpoint = PlaceBreakpointAtAddress(processData, stepper.Thread, instructionAddress); } if (secondaryNullcInstruction != 0) { ulong instructionAddress = processData.bytecode.ConvertInstructionToNativeAddress(secondaryNullcInstruction); processData.secondaryStepBreakpoint = PlaceBreakpointAtAddress(processData, stepper.Thread, instructionAddress); } } else if (stepper.StepKind == DkmStepKind.Out) { ulong address = GetReturnAddress(stepper.Thread); processData.stepBreakpoint = PlaceBreakpointAtAddress(processData, stepper.Thread, address); } else { throw new NotImplementedException(); } }