1 /*
2 * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 #include "util.h"
27 #include "transport.h"
28 #include "debugLoop.h"
29 #include "debugDispatch.h"
30 #include "standardHandlers.h"
31 #include "inStream.h"
32 #include "outStream.h"
33 #include "threadControl.h"
34
35 // ANDROID-CHANGED: Needed for DDM_onDisconnect
36 #include "DDMImpl.h"
37 // ANDROID-CHANGED: Needed for vmDebug_onDisconnect, vmDebug_notifyDebuggerActivityStart &
38 // vmDebug_notifyDebuggerActivityEnd.
39 #include "vmDebug.h"
40
41
42 static void JNICALL reader(jvmtiEnv* jvmti_env, JNIEnv* jni_env, void* arg);
43 static void enqueue(jdwpPacket *p);
44 static jboolean dequeue(jdwpPacket *p);
45 static void notifyTransportError(void);
46
47 struct PacketList {
48 jdwpPacket packet;
49 struct PacketList *next;
50 };
51
52 static volatile struct PacketList *cmdQueue;
53 static jrawMonitorID cmdQueueLock;
54 static jrawMonitorID vmDeathLock;
55 static jboolean transportError;
56
57 static jboolean
lastCommand(jdwpCmdPacket * cmd)58 lastCommand(jdwpCmdPacket *cmd)
59 {
60 if ((cmd->cmdSet == JDWP_COMMAND_SET(VirtualMachine)) &&
61 ((cmd->cmd == JDWP_COMMAND(VirtualMachine, Dispose)) ||
62 (cmd->cmd == JDWP_COMMAND(VirtualMachine, Exit)))) {
63 return JNI_TRUE;
64 } else {
65 return JNI_FALSE;
66 }
67 }
68
69 void
debugLoop_initialize(void)70 debugLoop_initialize(void)
71 {
72 vmDeathLock = debugMonitorCreate("JDWP VM_DEATH Lock");
73 }
74
75 void
debugLoop_sync(void)76 debugLoop_sync(void)
77 {
78 debugMonitorEnter(vmDeathLock);
79 debugMonitorExit(vmDeathLock);
80 }
81
82 /*
83 * This is where all the work gets done.
84 */
85
86 void
debugLoop_run(void)87 debugLoop_run(void)
88 {
89 jboolean shouldListen;
90 jdwpPacket p;
91 jvmtiStartFunction func;
92
93 /* Initialize all statics */
94 /* We may be starting a new connection after an error */
95 cmdQueue = NULL;
96 cmdQueueLock = debugMonitorCreate("JDWP Command Queue Lock");
97 transportError = JNI_FALSE;
98
99 shouldListen = JNI_TRUE;
100
101 func = &reader;
102 (void)spawnNewThread(func, NULL, "JDWP Command Reader");
103
104 standardHandlers_onConnect();
105 threadControl_onConnect();
106
107 /* Okay, start reading cmds! */
108 while (shouldListen) {
109 if (!dequeue(&p)) {
110 break;
111 }
112
113 if (p.type.cmd.flags & JDWPTRANSPORT_FLAGS_REPLY) {
114 /*
115 * Its a reply packet.
116 */
117 continue;
118 } else {
119 /*
120 * Its a cmd packet.
121 */
122 jdwpCmdPacket *cmd = &p.type.cmd;
123 PacketInputStream in;
124 PacketOutputStream out;
125 CommandHandler func;
126
127 /* Should reply be sent to sender.
128 * For error handling, assume yes, since
129 * only VM/exit does not reply
130 */
131 jboolean replyToSender = JNI_TRUE;
132
133 /*
134 * For all commands we hold the vmDeathLock
135 * while executing and replying to the command. This ensures
136 * that a command after VM_DEATH will be allowed to complete
137 * before the thread posting the VM_DEATH continues VM
138 * termination.
139 */
140 debugMonitorEnter(vmDeathLock);
141
142 // ANDROID-CHANGED: Tell vmDebug we have started doing some debugger activity. We only
143 // do this if the cmdSet is not DDMS for historical reasons.
144 jboolean is_ddms = (cmd->cmdSet == JDWP_COMMAND_SET(DDM));
145 if (!is_ddms) {
146 vmDebug_notifyDebuggerActivityStart();
147 }
148
149 /* Initialize the input and output streams */
150 inStream_init(&in, p);
151 outStream_initReply(&out, inStream_id(&in));
152
153 LOG_MISC(("Command set %d, command %d", cmd->cmdSet, cmd->cmd));
154
155 func = debugDispatch_getHandler(cmd->cmdSet,cmd->cmd);
156 if (func == NULL) {
157 /* we've never heard of this, so I guess we
158 * haven't implemented it.
159 * Handle gracefully for future expansion
160 * and platform / vendor expansion.
161 */
162 outStream_setError(&out, JDWP_ERROR(NOT_IMPLEMENTED));
163 } else if (gdata->vmDead &&
164 ((cmd->cmdSet) != JDWP_COMMAND_SET(VirtualMachine))) {
165 /* Protect the VM from calls while dead.
166 * VirtualMachine cmdSet quietly ignores some cmds
167 * after VM death, so, it sends it's own errors.
168 */
169 outStream_setError(&out, JDWP_ERROR(VM_DEAD));
170 } else {
171 /* Call the command handler */
172 replyToSender = func(&in, &out);
173 }
174
175 // ANDROID-CHANGED: Tell vmDebug we are done with the current debugger activity.
176 if (!is_ddms) {
177 vmDebug_notifyDebuggerActivityEnd();
178 }
179
180 /* Reply to the sender */
181 if (replyToSender) {
182 if (inStream_error(&in)) {
183 outStream_setError(&out, inStream_error(&in));
184 }
185 outStream_sendReply(&out);
186 }
187
188 /*
189 * Release the vmDeathLock as the reply has been posted.
190 */
191 debugMonitorExit(vmDeathLock);
192
193 inStream_destroy(&in);
194 outStream_destroy(&out);
195
196 shouldListen = !lastCommand(cmd);
197 }
198 }
199 threadControl_onDisconnect();
200 standardHandlers_onDisconnect();
201
202 /*
203 * Cut off the transport immediately. This has the effect of
204 * cutting off any events that the eventHelper thread might
205 * be trying to send.
206 */
207 transport_close();
208 debugMonitorDestroy(cmdQueueLock);
209
210 // ANDROID-CHANGED: Tell vmDebug we have disconnected.
211 vmDebug_onDisconnect();
212 // ANDROID-CHANGED: DDM needs to call some functions when we disconnect.
213 DDM_onDisconnect();
214
215 /* Reset for a new connection to this VM if it's still alive */
216 if ( ! gdata->vmDead ) {
217 debugInit_reset(getEnv());
218 }
219 }
220
221 /* Command reader */
222 static void JNICALL
reader(jvmtiEnv * jvmti_env,JNIEnv * jni_env,void * arg)223 reader(jvmtiEnv* jvmti_env, JNIEnv* jni_env, void* arg)
224 {
225 jdwpPacket packet;
226 jdwpCmdPacket *cmd;
227 jboolean shouldListen = JNI_TRUE;
228
229 LOG_MISC(("Begin reader thread"));
230
231 while (shouldListen) {
232 jint rc;
233
234 rc = transport_receivePacket(&packet);
235
236 /* I/O error or EOF */
237 if (rc != 0 || (rc == 0 && packet.type.cmd.len == 0)) {
238 shouldListen = JNI_FALSE;
239 notifyTransportError();
240 } else if (packet.type.cmd.flags != JDWPTRANSPORT_FLAGS_NONE) {
241 /*
242 * Close the connection when we get a jdwpCmdPacket with an
243 * invalid flags field value. This is a protocol violation
244 * so we drop the connection. Also this could be a web
245 * browser generating an HTTP request that passes the JDWP
246 * handshake. HTTP requests requires that everything be in
247 * the ASCII printable range so a flags value of
248 * JDWPTRANSPORT_FLAGS_NONE(0) cannot be generated via HTTP.
249 */
250 ERROR_MESSAGE(("Received jdwpPacket with flags != 0x%d (actual=0x%x) when a jdwpCmdPacket was expected.",
251 JDWPTRANSPORT_FLAGS_NONE, packet.type.cmd.flags));
252 shouldListen = JNI_FALSE;
253 notifyTransportError();
254 } else {
255 cmd = &packet.type.cmd;
256
257 LOG_MISC(("Command set %d, command %d", cmd->cmdSet, cmd->cmd));
258
259 /*
260 * FIXME! We need to deal with high priority
261 * packets and queue flushes!
262 */
263 enqueue(&packet);
264
265 shouldListen = !lastCommand(cmd);
266 }
267 }
268 LOG_MISC(("End reader thread"));
269 }
270
271 /*
272 * The current system for queueing packets is highly
273 * inefficient, and should be rewritten! It'd be nice
274 * to avoid any additional memory allocations.
275 */
276
277 static void
enqueue(jdwpPacket * packet)278 enqueue(jdwpPacket *packet)
279 {
280 struct PacketList *pL;
281 struct PacketList *walker;
282
283 pL = jvmtiAllocate((jint)sizeof(struct PacketList));
284 if (pL == NULL) {
285 EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"packet list");
286 }
287
288 pL->packet = *packet;
289 pL->next = NULL;
290
291 debugMonitorEnter(cmdQueueLock);
292
293 if (cmdQueue == NULL) {
294 cmdQueue = pL;
295 debugMonitorNotify(cmdQueueLock);
296 } else {
297 walker = (struct PacketList *)cmdQueue;
298 while (walker->next != NULL)
299 walker = walker->next;
300
301 walker->next = pL;
302 }
303
304 debugMonitorExit(cmdQueueLock);
305 }
306
307 static jboolean
dequeue(jdwpPacket * packet)308 dequeue(jdwpPacket *packet) {
309 struct PacketList *node = NULL;
310
311 debugMonitorEnter(cmdQueueLock);
312
313 while (!transportError && (cmdQueue == NULL)) {
314 debugMonitorWait(cmdQueueLock);
315 }
316
317 if (cmdQueue != NULL) {
318 node = (struct PacketList *)cmdQueue;
319 cmdQueue = node->next;
320 }
321 debugMonitorExit(cmdQueueLock);
322
323 if (node != NULL) {
324 *packet = node->packet;
325 jvmtiDeallocate(node);
326 }
327 return (node != NULL);
328 }
329
330 static void
notifyTransportError(void)331 notifyTransportError(void) {
332 debugMonitorEnter(cmdQueueLock);
333 transportError = JNI_TRUE;
334 debugMonitorNotify(cmdQueueLock);
335 debugMonitorExit(cmdQueueLock);
336 }
337