1 /**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "step_hooks.h"
17 #include "inspector.h"
18 #include "error.h"
19 #include "json_property.h"
20 #include "server.h"
21
22 #include "bytecode_instruction-inl.h"
23 #include "optimizer/ir_builder/inst_builder.h"
24 #include "tooling/debug_interface.h"
25 #include "tooling/pt_location.h"
26 #include "utils/json_parser.h"
27 #include "utils/logger.h"
28 #include "utils/string_helpers.h"
29
30 #include <algorithm>
31 #include <cstddef>
32 #include <cstdint>
33 #include <functional>
34 #include <string>
35 #include <utility>
36
37 using namespace std::placeholders; // NOLINT(google-build-using-namespace)
38 using panda::helpers::string::ParseInt;
39
40 namespace panda::tooling::inspector {
StepHooks(Inspector & inspector)41 StepHooks::StepHooks(Inspector &inspector) : inspector_(inspector)
42 {
43 inspector.GetServer().OnCall("Debugger.continueToLocation", std::bind(&StepHooks::ContinueToLocation, this, _2));
44 inspector.GetServer().OnCall("Debugger.stepInto", std::bind(&StepHooks::SetStep, this, STEP_INTO));
45 inspector.GetServer().OnCall("Debugger.stepOut", std::bind(&StepHooks::SetStep, this, STEP_OUT));
46 inspector.GetServer().OnCall("Debugger.stepOver", std::bind(&StepHooks::SetStep, this, STEP_OVER));
47 inspector.GetServer().OnCall("Runtime.runIfWaitingForDebugger", std::bind(&StepHooks::SetBreakOnStart, this));
48
49 inspector.OnPause(std::bind(&StepHooks::OnPause, this));
50 }
51
AddLocation(const PtLocation & location)52 void StepHooks::AddLocation(const PtLocation &location)
53 {
54 if (state_->locations.count(location) != 0) {
55 return;
56 }
57
58 if (auto error = inspector_.GetDebugger().SetBreakpoint(location)) {
59 if (error->GetType() == Error::Type::BREAKPOINT_ALREADY_EXISTS) {
60 state_->locations[location] = true;
61 } else {
62 HandleError(std::move(error));
63 }
64 } else {
65 state_->locations[location] = false;
66 }
67 }
68
Breakpoint(PtThread thread,Method *,const PtLocation & location)69 void StepHooks::Breakpoint(PtThread thread, Method * /* method */, const PtLocation &location)
70 {
71 if (state_ && state_->thread == thread && state_->locations.count(location) != 0) {
72 inspector_.SetPause(thread, "Step");
73 }
74 }
75
ContinueToLocation(const JsonObject & params)76 void StepHooks::ContinueToLocation(const JsonObject ¶ms)
77 {
78 const std::string *scriptIdString;
79 if (!GetPropertyOrLog<JsonObject::StringT>(scriptIdString, params, "location", "scriptId")) {
80 return;
81 }
82
83 int scriptId;
84 if (!ParseInt(*scriptIdString, &scriptId, 0)) {
85 LOG(INFO, DEBUGGER) << "Invalid script id: " << *scriptIdString;
86 return;
87 }
88
89 size_t lineNumber;
90 if (!GetPropertyOrLog<JsonObject::NumT>(lineNumber, params, "location", "lineNumber")) {
91 return;
92 }
93
94 auto sourceFile = inspector_.GetSourceManager().GetSourceFile(scriptId);
95 if (sourceFile == nullptr) {
96 return;
97 }
98
99 state_ = State {};
100 state_->stepKind = CONTINUE_TO;
101 state_->thread = inspector_.Resume();
102
103 sourceFile->EnumerateLocations(lineNumber, [&](auto location) {
104 AddLocation(location);
105 return true;
106 });
107 }
108
FramePop(PtThread thread,Method *,bool)109 void StepHooks::FramePop(PtThread thread, Method * /* method */, bool /* wasPoppedByException */)
110 {
111 if (state_ && state_->thread == thread) {
112 state_->pauseOnStep = true;
113 }
114 }
115
MethodEntry(PtThread thread,Method *)116 void StepHooks::MethodEntry(PtThread thread, Method * /* method */)
117 {
118 if (!state_ || state_->thread != thread || std::exchange(state_->methodEntered, true)) {
119 return;
120 }
121
122 switch (state_->stepKind) {
123 case BREAK_ON_START:
124 case CONTINUE_TO:
125 case STEP_OUT:
126 break;
127
128 case STEP_INTO:
129 state_->pauseOnStep = true;
130 break;
131
132 case STEP_OVER:
133 HandleError(inspector_.GetDebugger().NotifyFramePop(thread, 0));
134 break;
135 }
136 }
137
OnPause()138 void StepHooks::OnPause()
139 {
140 if (!state_) {
141 return;
142 }
143
144 for (auto &[location, existingBreakpoint] : state_->locations) {
145 if (!existingBreakpoint) {
146 HandleError(inspector_.GetDebugger().RemoveBreakpoint(location));
147 }
148 }
149
150 state_.reset();
151 }
152
SetBreakOnStart()153 void StepHooks::SetBreakOnStart()
154 {
155 inspector_.Resume();
156
157 state_ = State {};
158 state_->stepKind = BREAK_ON_START;
159 }
160
SetStep(StepKind stepKind)161 void StepHooks::SetStep(StepKind stepKind)
162 {
163 auto thread = inspector_.Resume();
164
165 state_ = State {};
166 state_->stepKind = stepKind;
167 state_->thread = thread;
168
169 if (stepKind == STEP_OUT) {
170 HandleError(inspector_.GetDebugger().NotifyFramePop(thread, 0));
171 return;
172 }
173
174 auto frame = inspector_.GetDebugger().GetCurrentFrame(thread);
175 if (!frame) {
176 HandleError(frame.Error());
177 return;
178 }
179
180 auto method = frame.Value()->GetMethod();
181 uint32_t bytecodeOffset = frame.Value()->GetBytecodeOffset();
182
183 auto &table =
184 inspector_.GetSourceManager().GetDebugInfo(method->GetPandaFile()).GetLineNumberTable(method->GetFileId());
185 auto nextLineIter = std::upper_bound(table.begin(), table.end(), bytecodeOffset,
186 [](auto offset, auto &entry) { return offset < entry.offset; });
187 uint32_t nextLineBytecodeOffset = nextLineIter != table.end() ? nextLineIter->offset : ~0U;
188
189 while (bytecodeOffset < std::min(nextLineBytecodeOffset, method->GetCodeSize())) {
190 BytecodeInstruction inst(method->GetInstructions() + bytecodeOffset);
191
192 if (inst.HasFlag(BytecodeInstruction::Flags::JUMP)) {
193 AddLocation(PtLocation(method->GetPandaFile()->GetFilename().c_str(), method->GetFileId(),
194 bytecodeOffset + compiler::InstBuilder::GetInstructionJumpOffset(&inst)));
195
196 if (!inst.HasFlag(BytecodeInstruction::Flags::CONDITIONAL)) {
197 break;
198 }
199 }
200
201 bytecodeOffset += inst.GetSize();
202 }
203
204 if (bytecodeOffset == nextLineBytecodeOffset) {
205 AddLocation(
206 PtLocation(method->GetPandaFile()->GetFilename().c_str(), method->GetFileId(), nextLineBytecodeOffset));
207 } else {
208 HandleError(inspector_.GetDebugger().NotifyFramePop(thread, 0));
209 }
210 }
211
SingleStep(PtThread thread,Method *,const PtLocation &)212 void StepHooks::SingleStep(PtThread thread, Method * /* method */, const PtLocation & /* location */)
213 {
214 if (!state_) {
215 return;
216 }
217
218 if (state_->stepKind == BREAK_ON_START) {
219 inspector_.SetPause(thread, "Break on start");
220 } else if (state_->thread == thread && state_->pauseOnStep) {
221 inspector_.SetPause(thread, "Step");
222 }
223 }
224
VmInitialization(PtThread thread)225 void StepHooks::VmInitialization(PtThread thread)
226 {
227 // Wait for a debugger to attach.
228 inspector_.SetPause(thread, {});
229 }
230 } // namespace panda::tooling::inspector
231