]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * Duktape debugger | |
3 | */ | |
4 | ||
5 | #include "duk_internal.h" | |
6 | ||
7 | #if defined(DUK_USE_DEBUGGER_SUPPORT) | |
8 | ||
9 | /* | |
10 | * Helper structs | |
11 | */ | |
12 | ||
13 | typedef union { | |
14 | void *p; | |
15 | duk_uint_t b[1]; | |
16 | /* Use b[] to access the size of the union, which is strictly not | |
17 | * correct. Can't use fixed size unless there's feature detection | |
18 | * for pointer byte size. | |
19 | */ | |
20 | } duk__ptr_union; | |
21 | ||
22 | /* | |
23 | * Detach handling | |
24 | */ | |
25 | ||
26 | #define DUK__SET_CONN_BROKEN(thr,reason) do { \ | |
27 | /* For now shared handler is fine. */ \ | |
28 | duk__debug_do_detach1((thr)->heap, (reason)); \ | |
29 | } while (0) | |
30 | ||
31 | DUK_LOCAL void duk__debug_do_detach1(duk_heap *heap, duk_int_t reason) { | |
32 | /* Can be called multiple times with no harm. Mark the transport | |
33 | * bad (dbg_read_cb == NULL) and clear state except for the detached | |
34 | * callback and the udata field. The detached callback is delayed | |
35 | * to the message loop so that it can be called between messages; | |
36 | * this avoids corner cases related to immediate debugger reattach | |
37 | * inside the detached callback. | |
38 | */ | |
39 | ||
40 | if (heap->dbg_detaching) { | |
41 | DUK_D(DUK_DPRINT("debugger already detaching, ignore detach1")); | |
42 | return; | |
43 | } | |
44 | ||
45 | DUK_D(DUK_DPRINT("debugger transport detaching, marking transport broken")); | |
46 | ||
47 | heap->dbg_detaching = 1; /* prevent multiple in-progress detaches */ | |
48 | ||
49 | if (heap->dbg_write_cb != NULL) { | |
50 | duk_hthread *thr; | |
51 | ||
52 | thr = heap->heap_thread; | |
53 | DUK_ASSERT(thr != NULL); | |
54 | ||
55 | duk_debug_write_notify(thr, DUK_DBG_CMD_DETACHING); | |
56 | duk_debug_write_int(thr, reason); | |
57 | duk_debug_write_eom(thr); | |
58 | } | |
59 | ||
60 | heap->dbg_read_cb = NULL; | |
61 | heap->dbg_write_cb = NULL; | |
62 | heap->dbg_peek_cb = NULL; | |
63 | heap->dbg_read_flush_cb = NULL; | |
64 | heap->dbg_write_flush_cb = NULL; | |
65 | heap->dbg_request_cb = NULL; | |
66 | /* heap->dbg_detached_cb: keep */ | |
67 | /* heap->dbg_udata: keep */ | |
68 | /* heap->dbg_processing: keep on purpose to avoid debugger re-entry in detaching state */ | |
69 | heap->dbg_paused = 0; | |
70 | heap->dbg_state_dirty = 0; | |
71 | heap->dbg_force_restart = 0; | |
72 | heap->dbg_step_type = 0; | |
73 | heap->dbg_step_thread = NULL; | |
74 | heap->dbg_step_csindex = 0; | |
75 | heap->dbg_step_startline = 0; | |
76 | heap->dbg_have_next_byte = 0; | |
77 | ||
78 | /* Ensure there are no stale active breakpoint pointers. | |
79 | * Breakpoint list is currently kept - we could empty it | |
80 | * here but we'd need to handle refcounts correctly, and | |
81 | * we'd need a 'thr' reference for that. | |
82 | * | |
83 | * XXX: clear breakpoint on either attach or detach? | |
84 | */ | |
85 | heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; | |
86 | } | |
87 | ||
88 | DUK_LOCAL void duk__debug_do_detach2(duk_heap *heap) { | |
89 | duk_debug_detached_function detached_cb; | |
90 | void *detached_udata; | |
91 | ||
92 | /* Safe to call multiple times. */ | |
93 | ||
94 | detached_cb = heap->dbg_detached_cb; | |
95 | detached_udata = heap->dbg_udata; | |
96 | heap->dbg_detached_cb = NULL; | |
97 | heap->dbg_udata = NULL; | |
98 | ||
99 | if (detached_cb) { | |
100 | /* Careful here: state must be wiped before the call | |
101 | * so that we can cleanly handle a re-attach from | |
102 | * inside the callback. | |
103 | */ | |
104 | DUK_D(DUK_DPRINT("detached during message loop, delayed call to detached_cb")); | |
105 | detached_cb(detached_udata); | |
106 | } | |
107 | ||
108 | heap->dbg_detaching = 0; | |
109 | } | |
110 | ||
111 | DUK_INTERNAL void duk_debug_do_detach(duk_heap *heap) { | |
112 | duk__debug_do_detach1(heap, 0); | |
113 | duk__debug_do_detach2(heap); | |
114 | } | |
115 | ||
116 | /* Called on a read/write error: NULL all callbacks except the detached | |
117 | * callback so that we never accidentally call them after a read/write | |
118 | * error has been indicated. This is especially important for the transport | |
119 | * I/O callbacks to fulfill guaranteed callback semantics. | |
120 | */ | |
121 | DUK_LOCAL void duk__debug_null_most_callbacks(duk_hthread *thr) { | |
122 | duk_heap *heap; | |
123 | heap = thr->heap; | |
124 | DUK_D(DUK_DPRINT("transport read/write error, NULL all callbacks expected detached")); | |
125 | heap->dbg_read_cb = NULL; | |
126 | heap->dbg_write_cb = NULL; /* this is especially critical to avoid another write call in detach1() */ | |
127 | heap->dbg_peek_cb = NULL; | |
128 | heap->dbg_read_flush_cb = NULL; | |
129 | heap->dbg_write_flush_cb = NULL; | |
130 | heap->dbg_request_cb = NULL; | |
131 | /* keep heap->dbg_detached_cb */ | |
132 | } | |
133 | ||
134 | /* | |
135 | * Debug connection peek and flush primitives | |
136 | */ | |
137 | ||
138 | DUK_INTERNAL duk_bool_t duk_debug_read_peek(duk_hthread *thr) { | |
139 | duk_heap *heap; | |
140 | ||
141 | DUK_ASSERT(thr != NULL); | |
142 | heap = thr->heap; | |
143 | DUK_ASSERT(heap != NULL); | |
144 | ||
145 | if (heap->dbg_read_cb == NULL) { | |
146 | DUK_D(DUK_DPRINT("attempt to peek in detached state, return zero (= no data)")); | |
147 | return 0; | |
148 | } | |
149 | if (heap->dbg_peek_cb == NULL) { | |
150 | DUK_DD(DUK_DDPRINT("no peek callback, return zero (= no data)")); | |
151 | return 0; | |
152 | } | |
153 | ||
154 | return (duk_bool_t) (heap->dbg_peek_cb(heap->dbg_udata) > 0); | |
155 | } | |
156 | ||
157 | DUK_INTERNAL void duk_debug_read_flush(duk_hthread *thr) { | |
158 | duk_heap *heap; | |
159 | ||
160 | DUK_ASSERT(thr != NULL); | |
161 | heap = thr->heap; | |
162 | DUK_ASSERT(heap != NULL); | |
163 | ||
164 | if (heap->dbg_read_cb == NULL) { | |
165 | DUK_D(DUK_DPRINT("attempt to read flush in detached state, ignore")); | |
166 | return; | |
167 | } | |
168 | if (heap->dbg_read_flush_cb == NULL) { | |
169 | DUK_DD(DUK_DDPRINT("no read flush callback, ignore")); | |
170 | return; | |
171 | } | |
172 | ||
173 | heap->dbg_read_flush_cb(heap->dbg_udata); | |
174 | } | |
175 | ||
176 | DUK_INTERNAL void duk_debug_write_flush(duk_hthread *thr) { | |
177 | duk_heap *heap; | |
178 | ||
179 | DUK_ASSERT(thr != NULL); | |
180 | heap = thr->heap; | |
181 | DUK_ASSERT(heap != NULL); | |
182 | ||
183 | if (heap->dbg_read_cb == NULL) { | |
184 | DUK_D(DUK_DPRINT("attempt to write flush in detached state, ignore")); | |
185 | return; | |
186 | } | |
187 | if (heap->dbg_write_flush_cb == NULL) { | |
188 | DUK_DD(DUK_DDPRINT("no write flush callback, ignore")); | |
189 | return; | |
190 | } | |
191 | ||
192 | heap->dbg_write_flush_cb(heap->dbg_udata); | |
193 | } | |
194 | ||
195 | /* | |
196 | * Debug connection skip primitives | |
197 | */ | |
198 | ||
199 | /* Skip fully. */ | |
200 | DUK_INTERNAL void duk_debug_skip_bytes(duk_hthread *thr, duk_size_t length) { | |
201 | duk_uint8_t dummy[64]; | |
202 | duk_size_t now; | |
203 | ||
204 | DUK_ASSERT(thr != NULL); | |
205 | ||
206 | while (length > 0) { | |
207 | now = (length > sizeof(dummy) ? sizeof(dummy) : length); | |
208 | duk_debug_read_bytes(thr, dummy, now); | |
209 | length -= now; | |
210 | } | |
211 | } | |
212 | ||
213 | DUK_INTERNAL void duk_debug_skip_byte(duk_hthread *thr) { | |
214 | DUK_ASSERT(thr != NULL); | |
215 | ||
216 | (void) duk_debug_read_byte(thr); | |
217 | } | |
218 | ||
219 | /* | |
220 | * Debug connection read primitives | |
221 | */ | |
222 | ||
223 | /* Peek ahead in the stream one byte. */ | |
224 | DUK_INTERNAL uint8_t duk_debug_peek_byte(duk_hthread *thr) { | |
225 | /* It is important not to call this if the last byte read was an EOM. | |
226 | * Reading ahead in this scenario would cause unnecessary blocking if | |
227 | * another message is not available. | |
228 | */ | |
229 | ||
230 | duk_uint8_t x; | |
231 | ||
232 | x = duk_debug_read_byte(thr); | |
233 | thr->heap->dbg_have_next_byte = 1; | |
234 | thr->heap->dbg_next_byte = x; | |
235 | return x; | |
236 | } | |
237 | ||
238 | /* Read fully. */ | |
239 | DUK_INTERNAL void duk_debug_read_bytes(duk_hthread *thr, duk_uint8_t *data, duk_size_t length) { | |
240 | duk_heap *heap; | |
241 | duk_uint8_t *p; | |
242 | duk_size_t left; | |
243 | duk_size_t got; | |
244 | ||
245 | DUK_ASSERT(thr != NULL); | |
246 | heap = thr->heap; | |
247 | DUK_ASSERT(heap != NULL); | |
248 | ||
249 | if (heap->dbg_read_cb == NULL) { | |
250 | DUK_D(DUK_DPRINT("attempt to read %ld bytes in detached state, return zero data", (long) length)); | |
251 | goto fail; | |
252 | } | |
253 | ||
254 | /* NOTE: length may be zero */ | |
255 | p = data; | |
256 | if (length >= 1 && heap->dbg_have_next_byte) { | |
257 | heap->dbg_have_next_byte = 0; | |
258 | *p++ = heap->dbg_next_byte; | |
259 | } | |
260 | for (;;) { | |
261 | left = (duk_size_t) ((data + length) - p); | |
262 | if (left == 0) { | |
263 | break; | |
264 | } | |
265 | DUK_ASSERT(heap->dbg_read_cb != NULL); | |
266 | DUK_ASSERT(left >= 1); | |
267 | #if defined(DUK_USE_DEBUGGER_TRANSPORT_TORTURE) | |
268 | left = 1; | |
269 | #endif | |
270 | got = heap->dbg_read_cb(heap->dbg_udata, (char *) p, left); | |
271 | if (got == 0 || got > left) { | |
272 | DUK_D(DUK_DPRINT("connection error during read, return zero data")); | |
273 | duk__debug_null_most_callbacks(thr); /* avoid calling write callback in detach1() */ | |
274 | DUK__SET_CONN_BROKEN(thr, 1); | |
275 | goto fail; | |
276 | } | |
277 | p += got; | |
278 | } | |
279 | return; | |
280 | ||
281 | fail: | |
282 | DUK_MEMZERO((void *) data, (size_t) length); | |
283 | } | |
284 | ||
285 | DUK_INTERNAL duk_uint8_t duk_debug_read_byte(duk_hthread *thr) { | |
286 | duk_uint8_t x; | |
287 | ||
288 | x = 0; /* just in case callback is broken and won't write 'x' */ | |
289 | duk_debug_read_bytes(thr, &x, 1); | |
290 | return x; | |
291 | } | |
292 | ||
293 | DUK_LOCAL duk_uint32_t duk__debug_read_uint32_raw(duk_hthread *thr) { | |
294 | duk_uint8_t buf[4]; | |
295 | ||
296 | DUK_ASSERT(thr != NULL); | |
297 | ||
298 | duk_debug_read_bytes(thr, buf, 4); | |
299 | return ((duk_uint32_t) buf[0] << 24) | | |
300 | ((duk_uint32_t) buf[1] << 16) | | |
301 | ((duk_uint32_t) buf[2] << 8) | | |
302 | (duk_uint32_t) buf[3]; | |
303 | } | |
304 | ||
305 | DUK_LOCAL duk_uint32_t duk__debug_read_int32_raw(duk_hthread *thr) { | |
306 | return (duk_int32_t) duk__debug_read_uint32_raw(thr); | |
307 | } | |
308 | ||
309 | DUK_LOCAL duk_uint16_t duk__debug_read_uint16_raw(duk_hthread *thr) { | |
310 | duk_uint8_t buf[2]; | |
311 | ||
312 | DUK_ASSERT(thr != NULL); | |
313 | ||
314 | duk_debug_read_bytes(thr, buf, 2); | |
315 | return ((duk_uint16_t) buf[0] << 8) | | |
316 | (duk_uint16_t) buf[1]; | |
317 | } | |
318 | ||
319 | DUK_INTERNAL duk_int32_t duk_debug_read_int(duk_hthread *thr) { | |
320 | duk_small_uint_t x; | |
321 | duk_small_uint_t t; | |
322 | ||
323 | DUK_ASSERT(thr != NULL); | |
324 | ||
325 | x = duk_debug_read_byte(thr); | |
326 | if (x >= 0xc0) { | |
327 | t = duk_debug_read_byte(thr); | |
328 | return (duk_int32_t) (((x - 0xc0) << 8) + t); | |
329 | } else if (x >= 0x80) { | |
330 | return (duk_int32_t) (x - 0x80); | |
331 | } else if (x == DUK_DBG_IB_INT4) { | |
332 | return (duk_int32_t) duk__debug_read_uint32_raw(thr); | |
333 | } | |
334 | ||
335 | DUK_D(DUK_DPRINT("debug connection error: failed to decode int")); | |
336 | DUK__SET_CONN_BROKEN(thr, 1); | |
337 | return 0; | |
338 | } | |
339 | ||
340 | DUK_LOCAL duk_hstring *duk__debug_read_hstring_raw(duk_hthread *thr, duk_uint32_t len) { | |
341 | duk_context *ctx = (duk_context *) thr; | |
342 | duk_uint8_t buf[31]; | |
343 | duk_uint8_t *p; | |
344 | ||
345 | if (len <= sizeof(buf)) { | |
346 | duk_debug_read_bytes(thr, buf, (duk_size_t) len); | |
347 | duk_push_lstring(ctx, (const char *) buf, (duk_size_t) len); | |
348 | } else { | |
349 | p = (duk_uint8_t *) duk_push_fixed_buffer(ctx, (duk_size_t) len); | |
350 | DUK_ASSERT(p != NULL); | |
351 | duk_debug_read_bytes(thr, p, (duk_size_t) len); | |
352 | duk_to_string(ctx, -1); | |
353 | } | |
354 | ||
355 | return duk_require_hstring(ctx, -1); | |
356 | } | |
357 | ||
358 | DUK_INTERNAL duk_hstring *duk_debug_read_hstring(duk_hthread *thr) { | |
359 | duk_context *ctx = (duk_context *) thr; | |
360 | duk_small_uint_t x; | |
361 | duk_uint32_t len; | |
362 | ||
363 | DUK_ASSERT(thr != NULL); | |
364 | ||
365 | x = duk_debug_read_byte(thr); | |
366 | if (x >= 0x60 && x <= 0x7f) { | |
367 | /* For short strings, use a fixed temp buffer. */ | |
368 | len = (duk_uint32_t) (x - 0x60); | |
369 | } else if (x == DUK_DBG_IB_STR2) { | |
370 | len = (duk_uint32_t) duk__debug_read_uint16_raw(thr); | |
371 | } else if (x == DUK_DBG_IB_STR4) { | |
372 | len = (duk_uint32_t) duk__debug_read_uint32_raw(thr); | |
373 | } else { | |
374 | goto fail; | |
375 | } | |
376 | ||
377 | return duk__debug_read_hstring_raw(thr, len); | |
378 | ||
379 | fail: | |
380 | DUK_D(DUK_DPRINT("debug connection error: failed to decode int")); | |
381 | DUK__SET_CONN_BROKEN(thr, 1); | |
382 | duk_push_hstring_stridx(thr, DUK_STRIDX_EMPTY_STRING); /* always push some string */ | |
383 | return duk_require_hstring(ctx, -1); | |
384 | } | |
385 | ||
386 | DUK_LOCAL duk_hbuffer *duk__debug_read_hbuffer_raw(duk_hthread *thr, duk_uint32_t len) { | |
387 | duk_context *ctx = (duk_context *) thr; | |
388 | duk_uint8_t *p; | |
389 | ||
390 | p = (duk_uint8_t *) duk_push_fixed_buffer(ctx, (duk_size_t) len); | |
391 | DUK_ASSERT(p != NULL); | |
392 | duk_debug_read_bytes(thr, p, (duk_size_t) len); | |
393 | ||
394 | return duk_require_hbuffer(ctx, -1); | |
395 | } | |
396 | ||
397 | DUK_LOCAL void *duk__debug_read_pointer_raw(duk_hthread *thr) { | |
398 | duk_small_uint_t x; | |
399 | duk__ptr_union pu; | |
400 | ||
401 | DUK_ASSERT(thr != NULL); | |
402 | ||
403 | x = duk_debug_read_byte(thr); | |
404 | if (x != sizeof(pu)) { | |
405 | goto fail; | |
406 | } | |
407 | duk_debug_read_bytes(thr, (duk_uint8_t *) &pu.p, sizeof(pu)); | |
408 | #if defined(DUK_USE_INTEGER_LE) | |
409 | duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); | |
410 | #endif | |
411 | return (void *) pu.p; | |
412 | ||
413 | fail: | |
414 | DUK_D(DUK_DPRINT("debug connection error: failed to decode pointer")); | |
415 | DUK__SET_CONN_BROKEN(thr, 1); | |
416 | return (void *) NULL; | |
417 | } | |
418 | ||
419 | DUK_LOCAL duk_double_t duk__debug_read_double_raw(duk_hthread *thr) { | |
420 | duk_double_union du; | |
421 | ||
422 | DUK_ASSERT(sizeof(du.uc) == 8); | |
423 | duk_debug_read_bytes(thr, (duk_uint8_t *) du.uc, sizeof(du.uc)); | |
424 | DUK_DBLUNION_DOUBLE_NTOH(&du); | |
425 | return du.d; | |
426 | } | |
427 | ||
428 | #if 0 | |
429 | DUK_INTERNAL duk_heaphdr *duk_debug_read_heapptr(duk_hthread *thr) { | |
430 | duk_small_uint_t x; | |
431 | ||
432 | DUK_ASSERT(thr != NULL); | |
433 | ||
434 | x = duk_debug_read_byte(thr); | |
435 | if (x != DUK_DBG_IB_HEAPPTR) { | |
436 | goto fail; | |
437 | } | |
438 | ||
439 | return (duk_heaphdr *) duk__debug_read_pointer_raw(thr); | |
440 | ||
441 | fail: | |
442 | DUK_D(DUK_DPRINT("debug connection error: failed to decode heapptr")); | |
443 | DUK__SET_CONN_BROKEN(thr, 1); | |
444 | return NULL; | |
445 | } | |
446 | #endif | |
447 | ||
448 | DUK_INTERNAL duk_heaphdr *duk_debug_read_any_ptr(duk_hthread *thr) { | |
449 | duk_small_uint_t x; | |
450 | ||
451 | DUK_ASSERT(thr != NULL); | |
452 | ||
453 | x = duk_debug_read_byte(thr); | |
454 | switch (x) { | |
455 | case DUK_DBG_IB_OBJECT: | |
456 | case DUK_DBG_IB_POINTER: | |
457 | case DUK_DBG_IB_HEAPPTR: | |
458 | /* Accept any pointer-like value; for 'object' dvalue, read | |
459 | * and ignore the class number. | |
460 | */ | |
461 | if (x == DUK_DBG_IB_OBJECT) { | |
462 | duk_debug_skip_byte(thr); | |
463 | } | |
464 | break; | |
465 | default: | |
466 | goto fail; | |
467 | } | |
468 | ||
469 | return (duk_heaphdr *) duk__debug_read_pointer_raw(thr); | |
470 | ||
471 | fail: | |
472 | DUK_D(DUK_DPRINT("debug connection error: failed to decode any pointer (object, pointer, heapptr)")); | |
473 | DUK__SET_CONN_BROKEN(thr, 1); | |
474 | return NULL; | |
475 | } | |
476 | ||
477 | DUK_INTERNAL duk_tval *duk_debug_read_tval(duk_hthread *thr) { | |
478 | duk_context *ctx = (duk_context *) thr; | |
479 | duk_uint8_t x; | |
480 | duk_uint_t t; | |
481 | duk_uint32_t len; | |
482 | ||
483 | DUK_ASSERT(thr != NULL); | |
484 | ||
485 | x = duk_debug_read_byte(thr); | |
486 | ||
487 | if (x >= 0xc0) { | |
488 | t = (duk_uint_t) (x - 0xc0); | |
489 | t = (t << 8) + duk_debug_read_byte(thr); | |
490 | duk_push_uint(ctx, (duk_uint_t) t); | |
491 | goto return_ptr; | |
492 | } | |
493 | if (x >= 0x80) { | |
494 | duk_push_uint(ctx, (duk_uint_t) (x - 0x80)); | |
495 | goto return_ptr; | |
496 | } | |
497 | if (x >= 0x60) { | |
498 | len = (duk_uint32_t) (x - 0x60); | |
499 | duk__debug_read_hstring_raw(thr, len); | |
500 | goto return_ptr; | |
501 | } | |
502 | ||
503 | switch (x) { | |
504 | case DUK_DBG_IB_INT4: { | |
505 | duk_int32_t i = duk__debug_read_int32_raw(thr); | |
506 | duk_push_i32(ctx, i); | |
507 | break; | |
508 | } | |
509 | case DUK_DBG_IB_STR4: { | |
510 | len = duk__debug_read_uint32_raw(thr); | |
511 | duk__debug_read_hstring_raw(thr, len); | |
512 | break; | |
513 | } | |
514 | case DUK_DBG_IB_STR2: { | |
515 | len = duk__debug_read_uint16_raw(thr); | |
516 | duk__debug_read_hstring_raw(thr, len); | |
517 | break; | |
518 | } | |
519 | case DUK_DBG_IB_BUF4: { | |
520 | len = duk__debug_read_uint32_raw(thr); | |
521 | duk__debug_read_hbuffer_raw(thr, len); | |
522 | break; | |
523 | } | |
524 | case DUK_DBG_IB_BUF2: { | |
525 | len = duk__debug_read_uint16_raw(thr); | |
526 | duk__debug_read_hbuffer_raw(thr, len); | |
527 | break; | |
528 | } | |
529 | case DUK_DBG_IB_UNDEFINED: { | |
530 | duk_push_undefined(ctx); | |
531 | break; | |
532 | } | |
533 | case DUK_DBG_IB_NULL: { | |
534 | duk_push_null(ctx); | |
535 | break; | |
536 | } | |
537 | case DUK_DBG_IB_TRUE: { | |
538 | duk_push_true(ctx); | |
539 | break; | |
540 | } | |
541 | case DUK_DBG_IB_FALSE: { | |
542 | duk_push_false(ctx); | |
543 | break; | |
544 | } | |
545 | case DUK_DBG_IB_NUMBER: { | |
546 | duk_double_t d; | |
547 | d = duk__debug_read_double_raw(thr); | |
548 | duk_push_number(ctx, d); | |
549 | break; | |
550 | } | |
551 | case DUK_DBG_IB_OBJECT: { | |
552 | duk_heaphdr *h; | |
553 | duk_debug_skip_byte(thr); | |
554 | h = (duk_heaphdr *) duk__debug_read_pointer_raw(thr); | |
555 | duk_push_heapptr(thr, (void *) h); | |
556 | break; | |
557 | } | |
558 | case DUK_DBG_IB_POINTER: { | |
559 | void *ptr; | |
560 | ptr = duk__debug_read_pointer_raw(thr); | |
561 | duk_push_pointer(thr, ptr); | |
562 | break; | |
563 | } | |
564 | case DUK_DBG_IB_LIGHTFUNC: { | |
565 | /* XXX: Not needed for now, so not implemented. Note that | |
566 | * function pointers may have different size/layout than | |
567 | * a void pointer. | |
568 | */ | |
569 | DUK_D(DUK_DPRINT("reading lightfunc values unimplemented")); | |
570 | goto fail; | |
571 | } | |
572 | case DUK_DBG_IB_HEAPPTR: { | |
573 | duk_heaphdr *h; | |
574 | h = (duk_heaphdr *) duk__debug_read_pointer_raw(thr); | |
575 | duk_push_heapptr(thr, (void *) h); | |
576 | break; | |
577 | } | |
578 | case DUK_DBG_IB_UNUSED: /* unused: not accepted in inbound messages */ | |
579 | default: | |
580 | goto fail; | |
581 | } | |
582 | ||
583 | return_ptr: | |
584 | return DUK_GET_TVAL_NEGIDX(thr, -1); | |
585 | ||
586 | fail: | |
587 | DUK_D(DUK_DPRINT("debug connection error: failed to decode tval")); | |
588 | DUK__SET_CONN_BROKEN(thr, 1); | |
589 | return NULL; | |
590 | } | |
591 | ||
592 | /* | |
593 | * Debug connection write primitives | |
594 | */ | |
595 | ||
596 | /* Write fully. */ | |
597 | DUK_INTERNAL void duk_debug_write_bytes(duk_hthread *thr, const duk_uint8_t *data, duk_size_t length) { | |
598 | duk_heap *heap; | |
599 | const duk_uint8_t *p; | |
600 | duk_size_t left; | |
601 | duk_size_t got; | |
602 | ||
603 | DUK_ASSERT(thr != NULL); | |
604 | DUK_ASSERT(length == 0 || data != NULL); | |
605 | heap = thr->heap; | |
606 | DUK_ASSERT(heap != NULL); | |
607 | ||
608 | if (heap->dbg_write_cb == NULL) { | |
609 | DUK_D(DUK_DPRINT("attempt to write %ld bytes in detached state, ignore", (long) length)); | |
610 | return; | |
611 | } | |
612 | if (length == 0) { | |
613 | /* Avoid doing an actual write callback with length == 0, | |
614 | * because that's reserved for a write flush. | |
615 | */ | |
616 | return; | |
617 | } | |
618 | DUK_ASSERT(data != NULL); | |
619 | ||
620 | p = data; | |
621 | for (;;) { | |
622 | left = (duk_size_t) ((data + length) - p); | |
623 | if (left == 0) { | |
624 | break; | |
625 | } | |
626 | DUK_ASSERT(heap->dbg_write_cb != NULL); | |
627 | DUK_ASSERT(left >= 1); | |
628 | #if defined(DUK_USE_DEBUGGER_TRANSPORT_TORTURE) | |
629 | left = 1; | |
630 | #endif | |
631 | got = heap->dbg_write_cb(heap->dbg_udata, (const char *) p, left); | |
632 | if (got == 0 || got > left) { | |
633 | duk__debug_null_most_callbacks(thr); /* avoid calling write callback in detach1() */ | |
634 | DUK_D(DUK_DPRINT("connection error during write")); | |
635 | DUK__SET_CONN_BROKEN(thr, 1); | |
636 | return; | |
637 | } | |
638 | p += got; | |
639 | } | |
640 | } | |
641 | ||
642 | DUK_INTERNAL void duk_debug_write_byte(duk_hthread *thr, duk_uint8_t x) { | |
643 | duk_debug_write_bytes(thr, (const duk_uint8_t *) &x, 1); | |
644 | } | |
645 | ||
646 | DUK_INTERNAL void duk_debug_write_unused(duk_hthread *thr) { | |
647 | duk_debug_write_byte(thr, DUK_DBG_IB_UNUSED); | |
648 | } | |
649 | ||
650 | DUK_INTERNAL void duk_debug_write_undefined(duk_hthread *thr) { | |
651 | duk_debug_write_byte(thr, DUK_DBG_IB_UNDEFINED); | |
652 | } | |
653 | ||
654 | #if defined(DUK_USE_DEBUGGER_INSPECT) | |
655 | DUK_INTERNAL void duk_debug_write_null(duk_hthread *thr) { | |
656 | duk_debug_write_byte(thr, DUK_DBG_IB_NULL); | |
657 | } | |
658 | #endif | |
659 | ||
660 | DUK_INTERNAL void duk_debug_write_boolean(duk_hthread *thr, duk_uint_t val) { | |
661 | duk_debug_write_byte(thr, val ? DUK_DBG_IB_TRUE : DUK_DBG_IB_FALSE); | |
662 | } | |
663 | ||
664 | /* Write signed 32-bit integer. */ | |
665 | DUK_INTERNAL void duk_debug_write_int(duk_hthread *thr, duk_int32_t x) { | |
666 | duk_uint8_t buf[5]; | |
667 | duk_size_t len; | |
668 | ||
669 | DUK_ASSERT(thr != NULL); | |
670 | ||
671 | if (x >= 0 && x <= 0x3fL) { | |
672 | buf[0] = (duk_uint8_t) (0x80 + x); | |
673 | len = 1; | |
674 | } else if (x >= 0 && x <= 0x3fffL) { | |
675 | buf[0] = (duk_uint8_t) (0xc0 + (x >> 8)); | |
676 | buf[1] = (duk_uint8_t) (x & 0xff); | |
677 | len = 2; | |
678 | } else { | |
679 | /* Signed integers always map to 4 bytes now. */ | |
680 | buf[0] = (duk_uint8_t) DUK_DBG_IB_INT4; | |
681 | buf[1] = (duk_uint8_t) ((x >> 24) & 0xff); | |
682 | buf[2] = (duk_uint8_t) ((x >> 16) & 0xff); | |
683 | buf[3] = (duk_uint8_t) ((x >> 8) & 0xff); | |
684 | buf[4] = (duk_uint8_t) (x & 0xff); | |
685 | len = 5; | |
686 | } | |
687 | duk_debug_write_bytes(thr, buf, len); | |
688 | } | |
689 | ||
690 | /* Write unsigned 32-bit integer. */ | |
691 | DUK_INTERNAL void duk_debug_write_uint(duk_hthread *thr, duk_uint32_t x) { | |
692 | /* The debugger protocol doesn't support a plain integer encoding for | |
693 | * the full 32-bit unsigned range (only 32-bit signed). For now, | |
694 | * unsigned 32-bit values simply written as signed ones. This is not | |
695 | * a concrete issue except for 32-bit heaphdr fields. Proper solutions | |
696 | * would be to (a) write such integers as IEEE doubles or (b) add an | |
697 | * unsigned 32-bit dvalue. | |
698 | */ | |
699 | if (x >= 0x80000000UL) { | |
700 | DUK_D(DUK_DPRINT("writing unsigned integer 0x%08lx as signed integer", | |
701 | (long) x)); | |
702 | } | |
703 | duk_debug_write_int(thr, (duk_int32_t) x); | |
704 | } | |
705 | ||
706 | DUK_INTERNAL void duk_debug_write_strbuf(duk_hthread *thr, const char *data, duk_size_t length, duk_uint8_t marker_base) { | |
707 | duk_uint8_t buf[5]; | |
708 | duk_size_t buflen; | |
709 | ||
710 | DUK_ASSERT(thr != NULL); | |
711 | DUK_ASSERT(length == 0 || data != NULL); | |
712 | ||
713 | if (length <= 0x1fUL && marker_base == DUK_DBG_IB_STR4) { | |
714 | /* For strings, special form for short lengths. */ | |
715 | buf[0] = (duk_uint8_t) (0x60 + length); | |
716 | buflen = 1; | |
717 | } else if (length <= 0xffffUL) { | |
718 | buf[0] = (duk_uint8_t) (marker_base + 1); | |
719 | buf[1] = (duk_uint8_t) (length >> 8); | |
720 | buf[2] = (duk_uint8_t) (length & 0xff); | |
721 | buflen = 3; | |
722 | } else { | |
723 | buf[0] = (duk_uint8_t) marker_base; | |
724 | buf[1] = (duk_uint8_t) (length >> 24); | |
725 | buf[2] = (duk_uint8_t) ((length >> 16) & 0xff); | |
726 | buf[3] = (duk_uint8_t) ((length >> 8) & 0xff); | |
727 | buf[4] = (duk_uint8_t) (length & 0xff); | |
728 | buflen = 5; | |
729 | } | |
730 | ||
731 | duk_debug_write_bytes(thr, (const duk_uint8_t *) buf, buflen); | |
732 | duk_debug_write_bytes(thr, (const duk_uint8_t *) data, length); | |
733 | } | |
734 | ||
735 | DUK_INTERNAL void duk_debug_write_string(duk_hthread *thr, const char *data, duk_size_t length) { | |
736 | duk_debug_write_strbuf(thr, data, length, DUK_DBG_IB_STR4); | |
737 | } | |
738 | ||
739 | DUK_INTERNAL void duk_debug_write_cstring(duk_hthread *thr, const char *data) { | |
740 | DUK_ASSERT(thr != NULL); | |
741 | ||
742 | duk_debug_write_string(thr, | |
743 | data, | |
744 | data ? DUK_STRLEN(data) : 0); | |
745 | } | |
746 | ||
747 | DUK_INTERNAL void duk_debug_write_hstring(duk_hthread *thr, duk_hstring *h) { | |
748 | DUK_ASSERT(thr != NULL); | |
749 | ||
750 | /* XXX: differentiate null pointer from empty string? */ | |
751 | duk_debug_write_string(thr, | |
752 | (h != NULL ? (const char *) DUK_HSTRING_GET_DATA(h) : NULL), | |
753 | (h != NULL ? (duk_size_t) DUK_HSTRING_GET_BYTELEN(h) : 0)); | |
754 | } | |
755 | ||
756 | DUK_LOCAL void duk__debug_write_hstring_safe_top(duk_hthread *thr) { | |
757 | duk_context *ctx = (duk_context *) thr; | |
758 | duk_debug_write_hstring(thr, duk_safe_to_hstring(ctx, -1)); | |
759 | } | |
760 | ||
761 | DUK_INTERNAL void duk_debug_write_buffer(duk_hthread *thr, const char *data, duk_size_t length) { | |
762 | duk_debug_write_strbuf(thr, data, length, DUK_DBG_IB_BUF4); | |
763 | } | |
764 | ||
765 | DUK_INTERNAL void duk_debug_write_hbuffer(duk_hthread *thr, duk_hbuffer *h) { | |
766 | DUK_ASSERT(thr != NULL); | |
767 | ||
768 | duk_debug_write_buffer(thr, | |
769 | (h != NULL ? (const char *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h) : NULL), | |
770 | (h != NULL ? (duk_size_t) DUK_HBUFFER_GET_SIZE(h) : 0)); | |
771 | } | |
772 | ||
773 | DUK_LOCAL void duk__debug_write_pointer_raw(duk_hthread *thr, void *ptr, duk_uint8_t ibyte) { | |
774 | duk_uint8_t buf[2]; | |
775 | duk__ptr_union pu; | |
776 | ||
777 | DUK_ASSERT(thr != NULL); | |
778 | DUK_ASSERT(sizeof(ptr) >= 1 && sizeof(ptr) <= 16); | |
779 | /* ptr may be NULL */ | |
780 | ||
781 | buf[0] = ibyte; | |
782 | buf[1] = sizeof(pu); | |
783 | duk_debug_write_bytes(thr, buf, 2); | |
784 | pu.p = (void *) ptr; | |
785 | #if defined(DUK_USE_INTEGER_LE) | |
786 | duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); | |
787 | #endif | |
788 | duk_debug_write_bytes(thr, (const duk_uint8_t *) &pu.p, (duk_size_t) sizeof(pu)); | |
789 | } | |
790 | ||
791 | DUK_INTERNAL void duk_debug_write_pointer(duk_hthread *thr, void *ptr) { | |
792 | duk__debug_write_pointer_raw(thr, ptr, DUK_DBG_IB_POINTER); | |
793 | } | |
794 | ||
795 | #if defined(DUK_USE_DEBUGGER_DUMPHEAP) || defined(DUK_USE_DEBUGGER_INSPECT) | |
796 | DUK_INTERNAL void duk_debug_write_heapptr(duk_hthread *thr, duk_heaphdr *h) { | |
797 | duk__debug_write_pointer_raw(thr, (void *) h, DUK_DBG_IB_HEAPPTR); | |
798 | } | |
799 | #endif /* DUK_USE_DEBUGGER_DUMPHEAP || DUK_USE_DEBUGGER_INSPECT */ | |
800 | ||
801 | DUK_INTERNAL void duk_debug_write_hobject(duk_hthread *thr, duk_hobject *obj) { | |
802 | duk_uint8_t buf[3]; | |
803 | duk__ptr_union pu; | |
804 | ||
805 | DUK_ASSERT(thr != NULL); | |
806 | DUK_ASSERT(sizeof(obj) >= 1 && sizeof(obj) <= 16); | |
807 | DUK_ASSERT(obj != NULL); | |
808 | ||
809 | buf[0] = DUK_DBG_IB_OBJECT; | |
810 | buf[1] = (duk_uint8_t) DUK_HOBJECT_GET_CLASS_NUMBER(obj); | |
811 | buf[2] = sizeof(pu); | |
812 | duk_debug_write_bytes(thr, buf, 3); | |
813 | pu.p = (void *) obj; | |
814 | #if defined(DUK_USE_INTEGER_LE) | |
815 | duk_byteswap_bytes((duk_uint8_t *) pu.b, sizeof(pu)); | |
816 | #endif | |
817 | duk_debug_write_bytes(thr, (const duk_uint8_t *) &pu.p, (duk_size_t) sizeof(pu)); | |
818 | } | |
819 | ||
820 | DUK_INTERNAL void duk_debug_write_tval(duk_hthread *thr, duk_tval *tv) { | |
821 | duk_c_function lf_func; | |
822 | duk_small_uint_t lf_flags; | |
823 | duk_uint8_t buf[4]; | |
824 | duk_double_union du1; | |
825 | duk_double_union du2; | |
826 | duk_int32_t i32; | |
827 | ||
828 | DUK_ASSERT(thr != NULL); | |
829 | DUK_ASSERT(tv != NULL); | |
830 | ||
831 | switch (DUK_TVAL_GET_TAG(tv)) { | |
832 | case DUK_TAG_UNDEFINED: | |
833 | duk_debug_write_byte(thr, DUK_DBG_IB_UNDEFINED); | |
834 | break; | |
835 | case DUK_TAG_UNUSED: | |
836 | duk_debug_write_byte(thr, DUK_DBG_IB_UNUSED); | |
837 | break; | |
838 | case DUK_TAG_NULL: | |
839 | duk_debug_write_byte(thr, DUK_DBG_IB_NULL); | |
840 | break; | |
841 | case DUK_TAG_BOOLEAN: | |
842 | DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv) == 0 || | |
843 | DUK_TVAL_GET_BOOLEAN(tv) == 1); | |
844 | duk_debug_write_boolean(thr, DUK_TVAL_GET_BOOLEAN(tv)); | |
845 | break; | |
846 | case DUK_TAG_POINTER: | |
847 | duk_debug_write_pointer(thr, (void *) DUK_TVAL_GET_POINTER(tv)); | |
848 | break; | |
849 | case DUK_TAG_LIGHTFUNC: | |
850 | DUK_TVAL_GET_LIGHTFUNC(tv, lf_func, lf_flags); | |
851 | buf[0] = DUK_DBG_IB_LIGHTFUNC; | |
852 | buf[1] = (duk_uint8_t) (lf_flags >> 8); | |
853 | buf[2] = (duk_uint8_t) (lf_flags & 0xff); | |
854 | buf[3] = sizeof(lf_func); | |
855 | duk_debug_write_bytes(thr, buf, 4); | |
856 | duk_debug_write_bytes(thr, (const duk_uint8_t *) &lf_func, sizeof(lf_func)); | |
857 | break; | |
858 | case DUK_TAG_STRING: | |
859 | duk_debug_write_hstring(thr, DUK_TVAL_GET_STRING(tv)); | |
860 | break; | |
861 | case DUK_TAG_OBJECT: | |
862 | duk_debug_write_hobject(thr, DUK_TVAL_GET_OBJECT(tv)); | |
863 | break; | |
864 | case DUK_TAG_BUFFER: | |
865 | duk_debug_write_hbuffer(thr, DUK_TVAL_GET_BUFFER(tv)); | |
866 | break; | |
867 | #if defined(DUK_USE_FASTINT) | |
868 | case DUK_TAG_FASTINT: | |
869 | #endif | |
870 | default: | |
871 | /* Numbers are normalized to big (network) endian. We can | |
872 | * (but are not required) to use integer dvalues when there's | |
873 | * no loss of precision. | |
874 | * | |
875 | * XXX: share check with other code; this check is slow but | |
876 | * reliable and doesn't require careful exponent/mantissa | |
877 | * mask tricks as in the fastint downgrade code. | |
878 | */ | |
879 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); | |
880 | DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); | |
881 | du1.d = DUK_TVAL_GET_NUMBER(tv); | |
882 | i32 = (duk_int32_t) du1.d; | |
883 | du2.d = (duk_double_t) i32; | |
884 | ||
885 | DUK_DD(DUK_DDPRINT("i32=%ld du1=%02x%02x%02x%02x%02x%02x%02x%02x " | |
886 | "du2=%02x%02x%02x%02x%02x%02x%02x%02x", | |
887 | (long) i32, | |
888 | (unsigned int) du1.uc[0], (unsigned int) du1.uc[1], | |
889 | (unsigned int) du1.uc[2], (unsigned int) du1.uc[3], | |
890 | (unsigned int) du1.uc[4], (unsigned int) du1.uc[5], | |
891 | (unsigned int) du1.uc[6], (unsigned int) du1.uc[7], | |
892 | (unsigned int) du2.uc[0], (unsigned int) du2.uc[1], | |
893 | (unsigned int) du2.uc[2], (unsigned int) du2.uc[3], | |
894 | (unsigned int) du2.uc[4], (unsigned int) du2.uc[5], | |
895 | (unsigned int) du2.uc[6], (unsigned int) du2.uc[7])); | |
896 | ||
897 | if (DUK_MEMCMP((const void *) du1.uc, (const void *) du2.uc, sizeof(du1.uc)) == 0) { | |
898 | duk_debug_write_int(thr, i32); | |
899 | } else { | |
900 | DUK_DBLUNION_DOUBLE_HTON(&du1); | |
901 | duk_debug_write_byte(thr, DUK_DBG_IB_NUMBER); | |
902 | duk_debug_write_bytes(thr, (const duk_uint8_t *) du1.uc, sizeof(du1.uc)); | |
903 | } | |
904 | } | |
905 | } | |
906 | ||
907 | #if defined(DUK_USE_DEBUGGER_DUMPHEAP) | |
908 | /* Variant for writing duk_tvals so that any heap allocated values are | |
909 | * written out as tagged heap pointers. | |
910 | */ | |
911 | DUK_LOCAL void duk__debug_write_tval_heapptr(duk_hthread *thr, duk_tval *tv) { | |
912 | if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { | |
913 | duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); | |
914 | duk_debug_write_heapptr(thr, h); | |
915 | } else { | |
916 | duk_debug_write_tval(thr, tv); | |
917 | } | |
918 | } | |
919 | #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ | |
920 | ||
921 | /* | |
922 | * Debug connection message write helpers | |
923 | */ | |
924 | ||
925 | #if 0 /* unused */ | |
926 | DUK_INTERNAL void duk_debug_write_request(duk_hthread *thr, duk_small_uint_t command) { | |
927 | duk_debug_write_byte(thr, DUK_DBG_IB_REQUEST); | |
928 | duk_debug_write_int(thr, command); | |
929 | } | |
930 | #endif | |
931 | ||
932 | DUK_INTERNAL void duk_debug_write_reply(duk_hthread *thr) { | |
933 | duk_debug_write_byte(thr, DUK_DBG_IB_REPLY); | |
934 | } | |
935 | ||
936 | DUK_INTERNAL void duk_debug_write_error_eom(duk_hthread *thr, duk_small_uint_t err_code, const char *msg) { | |
937 | /* Allow NULL 'msg' */ | |
938 | duk_debug_write_byte(thr, DUK_DBG_IB_ERROR); | |
939 | duk_debug_write_int(thr, (duk_int32_t) err_code); | |
940 | duk_debug_write_cstring(thr, msg); | |
941 | duk_debug_write_eom(thr); | |
942 | } | |
943 | ||
944 | DUK_INTERNAL void duk_debug_write_notify(duk_hthread *thr, duk_small_uint_t command) { | |
945 | duk_debug_write_byte(thr, DUK_DBG_IB_NOTIFY); | |
946 | duk_debug_write_int(thr, command); | |
947 | } | |
948 | ||
949 | DUK_INTERNAL void duk_debug_write_eom(duk_hthread *thr) { | |
950 | duk_debug_write_byte(thr, DUK_DBG_IB_EOM); | |
951 | ||
952 | /* As an initial implementation, write flush after every EOM (and the | |
953 | * version identifier). A better implementation would flush only when | |
954 | * Duktape is finished processing messages so that a flush only happens | |
955 | * after all outbound messages are finished on that occasion. | |
956 | */ | |
957 | duk_debug_write_flush(thr); | |
958 | } | |
959 | ||
960 | /* | |
961 | * Status message and helpers | |
962 | */ | |
963 | ||
964 | DUK_INTERNAL duk_uint_fast32_t duk_debug_curr_line(duk_hthread *thr) { | |
965 | duk_context *ctx = (duk_context *) thr; | |
966 | duk_activation *act; | |
967 | duk_uint_fast32_t line; | |
968 | duk_uint_fast32_t pc; | |
969 | ||
970 | act = duk_hthread_get_current_activation(thr); /* may be NULL */ | |
971 | if (act == NULL) { | |
972 | return 0; | |
973 | } | |
974 | ||
975 | /* We're conceptually between two opcodes; act->pc indicates the next | |
976 | * instruction to be executed. This is usually the correct pc/line to | |
977 | * indicate in Status. (For the 'debugger' statement this now reports | |
978 | * the pc/line after the debugger statement because the debugger opcode | |
979 | * has already been executed.) | |
980 | */ | |
981 | ||
982 | pc = duk_hthread_get_act_curr_pc(thr, act); | |
983 | ||
984 | /* XXX: this should be optimized to be a raw query and avoid valstack | |
985 | * operations if possible. | |
986 | */ | |
987 | duk_push_tval(ctx, &act->tv_func); | |
988 | line = duk_hobject_pc2line_query(ctx, -1, pc); | |
989 | duk_pop(ctx); | |
990 | return line; | |
991 | } | |
992 | ||
993 | DUK_INTERNAL void duk_debug_send_status(duk_hthread *thr) { | |
994 | duk_context *ctx = (duk_context *) thr; | |
995 | duk_activation *act; | |
996 | ||
997 | duk_debug_write_notify(thr, DUK_DBG_CMD_STATUS); | |
998 | duk_debug_write_int(thr, thr->heap->dbg_paused); | |
999 | ||
1000 | DUK_ASSERT_DISABLE(thr->callstack_top >= 0); /* unsigned */ | |
1001 | if (thr->callstack_top == 0) { | |
1002 | duk_debug_write_undefined(thr); | |
1003 | duk_debug_write_undefined(thr); | |
1004 | duk_debug_write_int(thr, 0); | |
1005 | duk_debug_write_int(thr, 0); | |
1006 | } else { | |
1007 | act = thr->callstack + thr->callstack_top - 1; | |
1008 | duk_push_tval(ctx, &act->tv_func); | |
1009 | duk_get_prop_string(ctx, -1, "fileName"); | |
1010 | duk__debug_write_hstring_safe_top(thr); | |
1011 | duk_get_prop_string(ctx, -2, "name"); | |
1012 | duk__debug_write_hstring_safe_top(thr); | |
1013 | duk_pop_3(ctx); | |
1014 | /* Report next pc/line to be executed. */ | |
1015 | duk_debug_write_uint(thr, (duk_uint32_t) duk_debug_curr_line(thr)); | |
1016 | duk_debug_write_uint(thr, (duk_uint32_t) duk_hthread_get_act_curr_pc(thr, act)); | |
1017 | } | |
1018 | ||
1019 | duk_debug_write_eom(thr); | |
1020 | } | |
1021 | ||
1022 | #if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) | |
1023 | DUK_INTERNAL void duk_debug_send_throw(duk_hthread *thr, duk_bool_t fatal) { | |
1024 | /* | |
1025 | * NFY <int: 5> <int: fatal> <str: msg> <str: filename> <int: linenumber> EOM | |
1026 | */ | |
1027 | ||
1028 | duk_context *ctx = (duk_context *) thr; | |
1029 | duk_activation *act; | |
1030 | duk_uint32_t pc; | |
1031 | ||
1032 | DUK_ASSERT(thr->valstack_top > thr->valstack); /* At least: ... [err] */ | |
1033 | ||
1034 | duk_debug_write_notify(thr, DUK_DBG_CMD_THROW); | |
1035 | duk_debug_write_int(thr, fatal); | |
1036 | ||
1037 | /* Report thrown value to client coerced to string */ | |
1038 | duk_dup(ctx, -1); | |
1039 | duk__debug_write_hstring_safe_top(thr); | |
1040 | duk_pop(ctx); | |
1041 | ||
1042 | if (duk_is_error(ctx, -1)) { | |
1043 | /* Error instance, use augmented error data directly */ | |
1044 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); | |
1045 | duk__debug_write_hstring_safe_top(thr); | |
1046 | duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER); | |
1047 | duk_debug_write_uint(thr, duk_get_uint(ctx, -1)); | |
1048 | } else { | |
1049 | /* For anything other than an Error instance, we calculate the error | |
1050 | * location directly from the current activation. | |
1051 | */ | |
1052 | act = thr->callstack + thr->callstack_top - 1; | |
1053 | duk_push_tval(ctx, &act->tv_func); | |
1054 | duk_get_prop_string(ctx, -1, "fileName"); | |
1055 | duk__debug_write_hstring_safe_top(thr); | |
1056 | pc = duk_hthread_get_act_prev_pc(thr, act); | |
1057 | duk_debug_write_uint(thr, (duk_uint32_t) duk_hobject_pc2line_query(ctx, -2, pc)); | |
1058 | } | |
1059 | duk_pop_2(ctx); /* shared pop */ | |
1060 | ||
1061 | duk_debug_write_eom(thr); | |
1062 | } | |
1063 | #endif /* DUK_USE_DEBUGGER_THROW_NOTIFY */ | |
1064 | ||
1065 | /* | |
1066 | * Debug message processing | |
1067 | */ | |
1068 | ||
1069 | /* Skip dvalue. */ | |
1070 | DUK_LOCAL duk_bool_t duk__debug_skip_dvalue(duk_hthread *thr) { | |
1071 | duk_uint8_t x; | |
1072 | duk_uint32_t len; | |
1073 | ||
1074 | x = duk_debug_read_byte(thr); | |
1075 | ||
1076 | if (x >= 0xc0) { | |
1077 | duk_debug_skip_byte(thr); | |
1078 | return 0; | |
1079 | } | |
1080 | if (x >= 0x80) { | |
1081 | return 0; | |
1082 | } | |
1083 | if (x >= 0x60) { | |
1084 | duk_debug_skip_bytes(thr, x - 0x60); | |
1085 | return 0; | |
1086 | } | |
1087 | switch(x) { | |
1088 | case DUK_DBG_IB_EOM: | |
1089 | return 1; /* Return 1: got EOM */ | |
1090 | case DUK_DBG_IB_REQUEST: | |
1091 | case DUK_DBG_IB_REPLY: | |
1092 | case DUK_DBG_IB_ERROR: | |
1093 | case DUK_DBG_IB_NOTIFY: | |
1094 | break; | |
1095 | case DUK_DBG_IB_INT4: | |
1096 | (void) duk__debug_read_uint32_raw(thr); | |
1097 | break; | |
1098 | case DUK_DBG_IB_STR4: | |
1099 | case DUK_DBG_IB_BUF4: | |
1100 | len = duk__debug_read_uint32_raw(thr); | |
1101 | duk_debug_skip_bytes(thr, len); | |
1102 | break; | |
1103 | case DUK_DBG_IB_STR2: | |
1104 | case DUK_DBG_IB_BUF2: | |
1105 | len = duk__debug_read_uint16_raw(thr); | |
1106 | duk_debug_skip_bytes(thr, len); | |
1107 | break; | |
1108 | case DUK_DBG_IB_UNUSED: | |
1109 | case DUK_DBG_IB_UNDEFINED: | |
1110 | case DUK_DBG_IB_NULL: | |
1111 | case DUK_DBG_IB_TRUE: | |
1112 | case DUK_DBG_IB_FALSE: | |
1113 | break; | |
1114 | case DUK_DBG_IB_NUMBER: | |
1115 | duk_debug_skip_bytes(thr, 8); | |
1116 | break; | |
1117 | case DUK_DBG_IB_OBJECT: | |
1118 | duk_debug_skip_byte(thr); | |
1119 | len = duk_debug_read_byte(thr); | |
1120 | duk_debug_skip_bytes(thr, len); | |
1121 | break; | |
1122 | case DUK_DBG_IB_POINTER: | |
1123 | case DUK_DBG_IB_HEAPPTR: | |
1124 | len = duk_debug_read_byte(thr); | |
1125 | duk_debug_skip_bytes(thr, len); | |
1126 | break; | |
1127 | case DUK_DBG_IB_LIGHTFUNC: | |
1128 | duk_debug_skip_bytes(thr, 2); | |
1129 | len = duk_debug_read_byte(thr); | |
1130 | duk_debug_skip_bytes(thr, len); | |
1131 | break; | |
1132 | default: | |
1133 | goto fail; | |
1134 | } | |
1135 | ||
1136 | return 0; | |
1137 | ||
1138 | fail: | |
1139 | DUK__SET_CONN_BROKEN(thr, 1); | |
1140 | return 1; /* Pretend like we got EOM */ | |
1141 | } | |
1142 | ||
1143 | /* Skip dvalues to EOM. */ | |
1144 | DUK_LOCAL void duk__debug_skip_to_eom(duk_hthread *thr) { | |
1145 | for (;;) { | |
1146 | if (duk__debug_skip_dvalue(thr)) { | |
1147 | break; | |
1148 | } | |
1149 | } | |
1150 | } | |
1151 | ||
1152 | /* | |
1153 | * Simple commands | |
1154 | */ | |
1155 | ||
1156 | DUK_LOCAL void duk__debug_handle_basic_info(duk_hthread *thr, duk_heap *heap) { | |
1157 | DUK_UNREF(heap); | |
1158 | DUK_D(DUK_DPRINT("debug command Version")); | |
1159 | ||
1160 | duk_debug_write_reply(thr); | |
1161 | duk_debug_write_int(thr, DUK_VERSION); | |
1162 | duk_debug_write_cstring(thr, DUK_GIT_DESCRIBE); | |
1163 | duk_debug_write_cstring(thr, DUK_USE_TARGET_INFO); | |
1164 | #if defined(DUK_USE_DOUBLE_LE) | |
1165 | duk_debug_write_int(thr, 1); | |
1166 | #elif defined(DUK_USE_DOUBLE_ME) | |
1167 | duk_debug_write_int(thr, 2); | |
1168 | #elif defined(DUK_USE_DOUBLE_BE) | |
1169 | duk_debug_write_int(thr, 3); | |
1170 | #else | |
1171 | duk_debug_write_int(thr, 0); | |
1172 | #endif | |
1173 | duk_debug_write_int(thr, (duk_int_t) sizeof(void *)); | |
1174 | duk_debug_write_eom(thr); | |
1175 | } | |
1176 | ||
1177 | DUK_LOCAL void duk__debug_handle_trigger_status(duk_hthread *thr, duk_heap *heap) { | |
1178 | DUK_UNREF(heap); | |
1179 | DUK_D(DUK_DPRINT("debug command TriggerStatus")); | |
1180 | ||
1181 | duk_debug_write_reply(thr); | |
1182 | duk_debug_write_eom(thr); | |
1183 | heap->dbg_state_dirty = 1; | |
1184 | } | |
1185 | ||
1186 | DUK_LOCAL void duk__debug_handle_pause(duk_hthread *thr, duk_heap *heap) { | |
1187 | DUK_D(DUK_DPRINT("debug command Pause")); | |
1188 | ||
1189 | DUK_HEAP_SET_PAUSED(heap); | |
1190 | duk_debug_write_reply(thr); | |
1191 | duk_debug_write_eom(thr); | |
1192 | } | |
1193 | ||
1194 | DUK_LOCAL void duk__debug_handle_resume(duk_hthread *thr, duk_heap *heap) { | |
1195 | DUK_D(DUK_DPRINT("debug command Resume")); | |
1196 | ||
1197 | DUK_HEAP_CLEAR_PAUSED(heap); | |
1198 | duk_debug_write_reply(thr); | |
1199 | duk_debug_write_eom(thr); | |
1200 | } | |
1201 | ||
1202 | DUK_LOCAL void duk__debug_handle_step(duk_hthread *thr, duk_heap *heap, duk_int32_t cmd) { | |
1203 | duk_small_uint_t step_type; | |
1204 | duk_uint_fast32_t line; | |
1205 | ||
1206 | DUK_D(DUK_DPRINT("debug command StepInto/StepOver/StepOut: %d", (int) cmd)); | |
1207 | ||
1208 | if (cmd == DUK_DBG_CMD_STEPINTO) { | |
1209 | step_type = DUK_STEP_TYPE_INTO; | |
1210 | } else if (cmd == DUK_DBG_CMD_STEPOVER) { | |
1211 | step_type = DUK_STEP_TYPE_OVER; | |
1212 | } else { | |
1213 | DUK_ASSERT(cmd == DUK_DBG_CMD_STEPOUT); | |
1214 | step_type = DUK_STEP_TYPE_OUT; | |
1215 | } | |
1216 | ||
1217 | line = duk_debug_curr_line(thr); | |
1218 | if (line > 0) { | |
1219 | heap->dbg_paused = 0; | |
1220 | heap->dbg_step_type = step_type; | |
1221 | heap->dbg_step_thread = thr; | |
1222 | heap->dbg_step_csindex = thr->callstack_top - 1; | |
1223 | heap->dbg_step_startline = line; | |
1224 | heap->dbg_state_dirty = 1; | |
1225 | } else { | |
1226 | DUK_D(DUK_DPRINT("cannot determine current line, stepinto/stepover/stepout ignored")); | |
1227 | } | |
1228 | duk_debug_write_reply(thr); | |
1229 | duk_debug_write_eom(thr); | |
1230 | } | |
1231 | ||
1232 | DUK_LOCAL void duk__debug_handle_list_break(duk_hthread *thr, duk_heap *heap) { | |
1233 | duk_small_int_t i; | |
1234 | ||
1235 | DUK_D(DUK_DPRINT("debug command ListBreak")); | |
1236 | duk_debug_write_reply(thr); | |
1237 | for (i = 0; i < (duk_small_int_t) heap->dbg_breakpoint_count; i++) { | |
1238 | duk_debug_write_hstring(thr, heap->dbg_breakpoints[i].filename); | |
1239 | duk_debug_write_uint(thr, (duk_uint32_t) heap->dbg_breakpoints[i].line); | |
1240 | } | |
1241 | duk_debug_write_eom(thr); | |
1242 | } | |
1243 | ||
1244 | DUK_LOCAL void duk__debug_handle_add_break(duk_hthread *thr, duk_heap *heap) { | |
1245 | duk_hstring *filename; | |
1246 | duk_uint32_t linenumber; | |
1247 | duk_small_int_t idx; | |
1248 | ||
1249 | DUK_UNREF(heap); | |
1250 | ||
1251 | filename = duk_debug_read_hstring(thr); | |
1252 | linenumber = (duk_uint32_t) duk_debug_read_int(thr); | |
1253 | DUK_D(DUK_DPRINT("debug command AddBreak: %!O:%ld", (duk_hobject *) filename, (long) linenumber)); | |
1254 | idx = duk_debug_add_breakpoint(thr, filename, linenumber); | |
1255 | if (idx >= 0) { | |
1256 | duk_debug_write_reply(thr); | |
1257 | duk_debug_write_int(thr, (duk_int32_t) idx); | |
1258 | duk_debug_write_eom(thr); | |
1259 | } else { | |
1260 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_TOOMANY, "no space for breakpoint"); | |
1261 | } | |
1262 | } | |
1263 | ||
1264 | DUK_LOCAL void duk__debug_handle_del_break(duk_hthread *thr, duk_heap *heap) { | |
1265 | duk_small_uint_t idx; | |
1266 | ||
1267 | DUK_UNREF(heap); | |
1268 | ||
1269 | DUK_D(DUK_DPRINT("debug command DelBreak")); | |
1270 | idx = (duk_small_uint_t) duk_debug_read_int(thr); | |
1271 | if (duk_debug_remove_breakpoint(thr, idx)) { | |
1272 | duk_debug_write_reply(thr); | |
1273 | duk_debug_write_eom(thr); | |
1274 | } else { | |
1275 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid breakpoint index"); | |
1276 | } | |
1277 | } | |
1278 | ||
1279 | DUK_LOCAL void duk__debug_handle_get_var(duk_hthread *thr, duk_heap *heap) { | |
1280 | duk_context *ctx = (duk_context *) thr; | |
1281 | duk_hstring *str; | |
1282 | duk_bool_t rc; | |
1283 | duk_int32_t level; | |
1284 | ||
1285 | DUK_UNREF(heap); | |
1286 | DUK_D(DUK_DPRINT("debug command GetVar")); | |
1287 | ||
1288 | str = duk_debug_read_hstring(thr); /* push to stack */ | |
1289 | DUK_ASSERT(str != NULL); | |
1290 | if (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { | |
1291 | level = duk_debug_read_int(thr); /* optional callstack level */ | |
1292 | if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { | |
1293 | DUK_D(DUK_DPRINT("invalid callstack level for GetVar")); | |
1294 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level"); | |
1295 | return; | |
1296 | } | |
1297 | } else { | |
1298 | level = -1; | |
1299 | } | |
1300 | ||
1301 | if (thr->callstack_top > 0) { | |
1302 | rc = duk_js_getvar_activation(thr, | |
1303 | thr->callstack + thr->callstack_top + level, | |
1304 | str, | |
1305 | 0); | |
1306 | } else { | |
1307 | /* No activation, no variable access. Could also pretend | |
1308 | * we're in the global program context and read stuff off | |
1309 | * the global object. | |
1310 | */ | |
1311 | DUK_D(DUK_DPRINT("callstack empty, no activation -> ignore getvar")); | |
1312 | rc = 0; | |
1313 | } | |
1314 | ||
1315 | duk_debug_write_reply(thr); | |
1316 | if (rc) { | |
1317 | duk_debug_write_int(thr, 1); | |
1318 | DUK_ASSERT(duk_get_tval(ctx, -2) != NULL); | |
1319 | duk_debug_write_tval(thr, duk_get_tval(ctx, -2)); | |
1320 | } else { | |
1321 | duk_debug_write_int(thr, 0); | |
1322 | duk_debug_write_unused(thr); | |
1323 | } | |
1324 | duk_debug_write_eom(thr); | |
1325 | } | |
1326 | ||
1327 | DUK_LOCAL void duk__debug_handle_put_var(duk_hthread *thr, duk_heap *heap) { | |
1328 | duk_hstring *str; | |
1329 | duk_tval *tv; | |
1330 | duk_int32_t level; | |
1331 | ||
1332 | DUK_UNREF(heap); | |
1333 | DUK_D(DUK_DPRINT("debug command PutVar")); | |
1334 | ||
1335 | str = duk_debug_read_hstring(thr); /* push to stack */ | |
1336 | DUK_ASSERT(str != NULL); | |
1337 | tv = duk_debug_read_tval(thr); | |
1338 | if (tv == NULL) { | |
1339 | /* detached */ | |
1340 | return; | |
1341 | } | |
1342 | if (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { | |
1343 | level = duk_debug_read_int(thr); /* optional callstack level */ | |
1344 | if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { | |
1345 | DUK_D(DUK_DPRINT("invalid callstack level for PutVar")); | |
1346 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level"); | |
1347 | return; | |
1348 | } | |
1349 | } else { | |
1350 | level = -1; | |
1351 | } | |
1352 | ||
1353 | if (thr->callstack_top > 0) { | |
1354 | duk_js_putvar_activation(thr, | |
1355 | thr->callstack + thr->callstack_top + level, | |
1356 | str, | |
1357 | tv, | |
1358 | 0); | |
1359 | } else { | |
1360 | DUK_D(DUK_DPRINT("callstack empty, no activation -> ignore putvar")); | |
1361 | } | |
1362 | ||
1363 | /* XXX: Current putvar implementation doesn't have a success flag, | |
1364 | * add one and send to debug client? | |
1365 | */ | |
1366 | duk_debug_write_reply(thr); | |
1367 | duk_debug_write_eom(thr); | |
1368 | } | |
1369 | ||
1370 | DUK_LOCAL void duk__debug_handle_get_call_stack(duk_hthread *thr, duk_heap *heap) { | |
1371 | duk_context *ctx = (duk_context *) thr; | |
1372 | duk_hthread *curr_thr = thr; | |
1373 | duk_activation *curr_act; | |
1374 | duk_uint_fast32_t pc; | |
1375 | duk_uint_fast32_t line; | |
1376 | duk_size_t i; | |
1377 | ||
1378 | DUK_ASSERT(thr != NULL); | |
1379 | DUK_UNREF(heap); | |
1380 | ||
1381 | duk_debug_write_reply(thr); | |
1382 | while (curr_thr != NULL) { | |
1383 | i = curr_thr->callstack_top; | |
1384 | while (i > 0) { | |
1385 | i--; | |
1386 | curr_act = curr_thr->callstack + i; | |
1387 | ||
1388 | /* PC/line semantics here are: | |
1389 | * - For callstack top we're conceptually between two | |
1390 | * opcodes and current PC indicates next line to | |
1391 | * execute, so report that (matches Status). | |
1392 | * - For other activations we're conceptually still | |
1393 | * executing the instruction at PC-1, so report that | |
1394 | * (matches error stacktrace behavior). | |
1395 | * - See: https://github.com/svaarala/duktape/issues/281 | |
1396 | */ | |
1397 | ||
1398 | /* XXX: optimize to use direct reads, i.e. avoid | |
1399 | * value stack operations. | |
1400 | */ | |
1401 | duk_push_tval(ctx, &curr_act->tv_func); | |
1402 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); | |
1403 | duk__debug_write_hstring_safe_top(thr); | |
1404 | duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); | |
1405 | duk__debug_write_hstring_safe_top(thr); | |
1406 | pc = duk_hthread_get_act_curr_pc(thr, curr_act); | |
1407 | if (i != curr_thr->callstack_top - 1 && pc > 0) { | |
1408 | pc--; | |
1409 | } | |
1410 | line = duk_hobject_pc2line_query(ctx, -3, pc); | |
1411 | duk_debug_write_uint(thr, (duk_uint32_t) line); | |
1412 | duk_debug_write_uint(thr, (duk_uint32_t) pc); | |
1413 | duk_pop_3(ctx); | |
1414 | } | |
1415 | curr_thr = curr_thr->resumer; | |
1416 | } | |
1417 | /* SCANBUILD: warning about 'thr' potentially being NULL here, | |
1418 | * warning is incorrect because thr != NULL always here. | |
1419 | */ | |
1420 | duk_debug_write_eom(thr); | |
1421 | } | |
1422 | ||
1423 | DUK_LOCAL void duk__debug_handle_get_locals(duk_hthread *thr, duk_heap *heap) { | |
1424 | duk_context *ctx = (duk_context *) thr; | |
1425 | duk_activation *curr_act; | |
1426 | duk_int32_t level; | |
1427 | duk_hstring *varname; | |
1428 | ||
1429 | DUK_UNREF(heap); | |
1430 | ||
1431 | if (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { | |
1432 | level = duk_debug_read_int(thr); /* optional callstack level */ | |
1433 | if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { | |
1434 | DUK_D(DUK_DPRINT("invalid callstack level for GetLocals")); | |
1435 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level"); | |
1436 | return; | |
1437 | } | |
1438 | duk_debug_write_reply(thr); | |
1439 | } else { | |
1440 | duk_debug_write_reply(thr); | |
1441 | if (thr->callstack_top == 0) { | |
1442 | goto callstack_empty; | |
1443 | } | |
1444 | level = -1; | |
1445 | } | |
1446 | ||
1447 | curr_act = thr->callstack + thr->callstack_top + level; | |
1448 | ||
1449 | /* XXX: several nice-to-have improvements here: | |
1450 | * - Use direct reads avoiding value stack operations | |
1451 | * - Avoid triggering getters, indicate getter values to debug client | |
1452 | * - If side effects are possible, add error catching | |
1453 | */ | |
1454 | ||
1455 | duk_push_tval(ctx, &curr_act->tv_func); | |
1456 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VARMAP); | |
1457 | if (duk_is_object(ctx, -1)) { | |
1458 | duk_enum(ctx, -1, 0 /*enum_flags*/); | |
1459 | while (duk_next(ctx, -1 /*enum_index*/, 0 /*get_value*/)) { | |
1460 | varname = duk_get_hstring(ctx, -1); | |
1461 | DUK_ASSERT(varname != NULL); | |
1462 | ||
1463 | duk_js_getvar_activation(thr, curr_act, varname, 0 /*throw_flag*/); | |
1464 | /* [ ... func varmap enum key value this ] */ | |
1465 | duk_debug_write_hstring(thr, duk_get_hstring(ctx, -3)); | |
1466 | duk_debug_write_tval(thr, duk_get_tval(ctx, -2)); | |
1467 | duk_pop_3(ctx); /* -> [ ... func varmap enum ] */ | |
1468 | } | |
1469 | } else { | |
1470 | DUK_D(DUK_DPRINT("varmap is not an object in GetLocals, ignore")); | |
1471 | } | |
1472 | ||
1473 | callstack_empty: | |
1474 | duk_debug_write_eom(thr); | |
1475 | } | |
1476 | ||
1477 | DUK_LOCAL void duk__debug_handle_eval(duk_hthread *thr, duk_heap *heap) { | |
1478 | duk_context *ctx = (duk_context *) thr; | |
1479 | duk_small_uint_t call_flags; | |
1480 | duk_int_t call_ret; | |
1481 | duk_small_int_t eval_err; | |
1482 | duk_int32_t level; | |
1483 | ||
1484 | DUK_UNREF(heap); | |
1485 | ||
1486 | DUK_D(DUK_DPRINT("debug command Eval")); | |
1487 | ||
1488 | /* The eval code is executed within the lexical environment of a specified | |
1489 | * activation. For now, use global object eval() function, with the eval | |
1490 | * considered a 'direct call to eval'. | |
1491 | * | |
1492 | * Callstack level for debug commands only affects scope -- the callstack | |
1493 | * as seen by, e.g. Duktape.act() will be the same regardless. | |
1494 | */ | |
1495 | ||
1496 | /* nargs == 2 so we can pass a callstack level to eval(). */ | |
1497 | duk_push_c_function(ctx, duk_bi_global_object_eval, 2 /*nargs*/); | |
1498 | duk_push_undefined(ctx); /* 'this' binding shouldn't matter here */ | |
1499 | ||
1500 | (void) duk_debug_read_hstring(thr); | |
1501 | if (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { | |
1502 | level = duk_debug_read_int(thr); /* optional callstack level */ | |
1503 | if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { | |
1504 | DUK_D(DUK_DPRINT("invalid callstack level for Eval")); | |
1505 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level"); | |
1506 | return; | |
1507 | } | |
1508 | } | |
1509 | else { | |
1510 | level = -1; | |
1511 | } | |
1512 | DUK_ASSERT(level < 0 && -level <= (duk_int32_t) thr->callstack_top); | |
1513 | duk_push_int(ctx, level - 1); /* compensate for eval() call */ | |
1514 | ||
1515 | /* [ ... eval "eval" eval_input level ] */ | |
1516 | ||
1517 | call_flags = 0; | |
1518 | if (thr->callstack_top >= (duk_size_t) -level) { | |
1519 | duk_activation *act; | |
1520 | duk_hobject *fun; | |
1521 | ||
1522 | act = thr->callstack + thr->callstack_top + level; | |
1523 | fun = DUK_ACT_GET_FUNC(act); | |
1524 | if (fun != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(fun)) { | |
1525 | /* Direct eval requires that there's a current | |
1526 | * activation and it is an Ecmascript function. | |
1527 | * When Eval is executed from e.g. cooperate API | |
1528 | * call we'll need to do an indirect eval instead. | |
1529 | */ | |
1530 | call_flags |= DUK_CALL_FLAG_DIRECT_EVAL; | |
1531 | } | |
1532 | } | |
1533 | ||
1534 | call_ret = duk_handle_call_protected(thr, 2 /*num_stack_args*/, call_flags); | |
1535 | ||
1536 | if (call_ret == DUK_EXEC_SUCCESS) { | |
1537 | eval_err = 0; | |
1538 | /* Use result value as is. */ | |
1539 | } else { | |
1540 | /* For errors a string coerced result is most informative | |
1541 | * right now, as the debug client doesn't have the capability | |
1542 | * to traverse the error object. | |
1543 | */ | |
1544 | eval_err = 1; | |
1545 | duk_safe_to_string(ctx, -1); | |
1546 | } | |
1547 | ||
1548 | /* [ ... result ] */ | |
1549 | ||
1550 | duk_debug_write_reply(thr); | |
1551 | duk_debug_write_int(thr, (duk_int32_t) eval_err); | |
1552 | DUK_ASSERT(duk_get_tval(ctx, -1) != NULL); | |
1553 | duk_debug_write_tval(thr, duk_get_tval(ctx, -1)); | |
1554 | duk_debug_write_eom(thr); | |
1555 | } | |
1556 | ||
1557 | DUK_LOCAL void duk__debug_handle_detach(duk_hthread *thr, duk_heap *heap) { | |
1558 | DUK_UNREF(heap); | |
1559 | DUK_D(DUK_DPRINT("debug command Detach")); | |
1560 | ||
1561 | duk_debug_write_reply(thr); | |
1562 | duk_debug_write_eom(thr); | |
1563 | ||
1564 | DUK_D(DUK_DPRINT("debug connection detached, mark broken")); | |
1565 | DUK__SET_CONN_BROKEN(thr, 0); /* not an error */ | |
1566 | } | |
1567 | ||
1568 | DUK_LOCAL void duk__debug_handle_apprequest(duk_hthread *thr, duk_heap *heap) { | |
1569 | duk_context *ctx = (duk_context *) thr; | |
1570 | duk_idx_t old_top; | |
1571 | ||
1572 | DUK_D(DUK_DPRINT("debug command AppRequest")); | |
1573 | ||
1574 | old_top = duk_get_top(ctx); /* save stack top */ | |
1575 | ||
1576 | if (heap->dbg_request_cb != NULL) { | |
1577 | duk_idx_t nrets; | |
1578 | duk_idx_t nvalues = 0; | |
1579 | duk_idx_t top, idx; | |
1580 | ||
1581 | /* Read tvals from the message and push them onto the valstack, | |
1582 | * then call the request callback to process the request. | |
1583 | */ | |
1584 | while (duk_debug_peek_byte(thr) != DUK_DBG_IB_EOM) { | |
1585 | duk_tval *tv; | |
1586 | if (!duk_check_stack(ctx, 1)) { | |
1587 | DUK_D(DUK_DPRINT("failed to allocate space for request dvalue(s)")); | |
1588 | goto fail; | |
1589 | } | |
1590 | tv = duk_debug_read_tval(thr); /* push to stack */ | |
1591 | if (tv == NULL) { | |
1592 | /* detached */ | |
1593 | return; | |
1594 | } | |
1595 | nvalues++; | |
1596 | } | |
1597 | DUK_ASSERT(duk_get_top(ctx) == old_top + nvalues); | |
1598 | ||
1599 | /* Request callback should push values for reply to client onto valstack */ | |
1600 | DUK_D(DUK_DPRINT("calling into AppRequest request_cb with nvalues=%ld, old_top=%ld, top=%ld", | |
1601 | (long) nvalues, (long) old_top, (long) duk_get_top(ctx))); | |
1602 | nrets = heap->dbg_request_cb(ctx, heap->dbg_udata, nvalues); | |
1603 | DUK_D(DUK_DPRINT("returned from AppRequest request_cb; nvalues=%ld -> nrets=%ld, old_top=%ld, top=%ld", | |
1604 | (long) nvalues, (long) nrets, (long) old_top, (long) duk_get_top(ctx))); | |
1605 | if (nrets >= 0) { | |
1606 | DUK_ASSERT(duk_get_top(ctx) >= old_top + nrets); | |
1607 | if (duk_get_top(ctx) < old_top + nrets) { | |
1608 | DUK_D(DUK_DPRINT("AppRequest callback doesn't match value stack configuration, " | |
1609 | "top=%ld < old_top=%ld + nrets=%ld; " | |
1610 | "this might mean it's unsafe to continue!", | |
1611 | (long) duk_get_top(ctx), (long) old_top, (long) nrets)); | |
1612 | goto fail; | |
1613 | } | |
1614 | ||
1615 | /* Reply with tvals pushed by request callback */ | |
1616 | duk_debug_write_byte(thr, DUK_DBG_IB_REPLY); | |
1617 | top = duk_get_top(ctx); | |
1618 | for (idx = top - nrets; idx < top; idx++) { | |
1619 | duk_debug_write_tval(thr, DUK_GET_TVAL_POSIDX(ctx, idx)); | |
1620 | } | |
1621 | duk_debug_write_eom(thr); | |
1622 | } else { | |
1623 | DUK_ASSERT(duk_get_top(ctx) >= old_top + 1); | |
1624 | if (duk_get_top(ctx) < old_top + 1) { | |
1625 | DUK_D(DUK_DPRINT("request callback return value doesn't match value stack configuration")); | |
1626 | goto fail; | |
1627 | } | |
1628 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_APPLICATION, duk_get_string(ctx, -1)); | |
1629 | } | |
1630 | ||
1631 | duk_set_top(ctx, old_top); /* restore stack top */ | |
1632 | } else { | |
1633 | DUK_D(DUK_DPRINT("no request callback, treat AppRequest as unsupported")); | |
1634 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNSUPPORTED, "AppRequest unsupported by target"); | |
1635 | } | |
1636 | ||
1637 | return; | |
1638 | ||
1639 | fail: | |
1640 | duk_set_top(ctx, old_top); /* restore stack top */ | |
1641 | DUK__SET_CONN_BROKEN(thr, 1); | |
1642 | } | |
1643 | ||
1644 | /* | |
1645 | * DumpHeap command | |
1646 | */ | |
1647 | ||
1648 | #if defined(DUK_USE_DEBUGGER_DUMPHEAP) | |
1649 | /* XXX: this has some overlap with object inspection; remove this and make | |
1650 | * DumpHeap return lists of heapptrs instead? | |
1651 | */ | |
1652 | DUK_LOCAL void duk__debug_dump_heaphdr(duk_hthread *thr, duk_heap *heap, duk_heaphdr *hdr) { | |
1653 | DUK_UNREF(heap); | |
1654 | ||
1655 | duk_debug_write_heapptr(thr, hdr); | |
1656 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_TYPE(hdr)); | |
1657 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_FLAGS_RAW(hdr)); | |
1658 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
1659 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HEAPHDR_GET_REFCOUNT(hdr)); | |
1660 | #else | |
1661 | duk_debug_write_int(thr, (duk_int32_t) -1); | |
1662 | #endif | |
1663 | ||
1664 | switch (DUK_HEAPHDR_GET_TYPE(hdr)) { | |
1665 | case DUK_HTYPE_STRING: { | |
1666 | duk_hstring *h = (duk_hstring *) hdr; | |
1667 | ||
1668 | duk_debug_write_uint(thr, (duk_int32_t) DUK_HSTRING_GET_BYTELEN(h)); | |
1669 | duk_debug_write_uint(thr, (duk_int32_t) DUK_HSTRING_GET_CHARLEN(h)); | |
1670 | duk_debug_write_uint(thr, (duk_int32_t) DUK_HSTRING_GET_HASH(h)); | |
1671 | duk_debug_write_hstring(thr, h); | |
1672 | break; | |
1673 | } | |
1674 | case DUK_HTYPE_OBJECT: { | |
1675 | duk_hobject *h = (duk_hobject *) hdr; | |
1676 | duk_hstring *k; | |
1677 | duk_uint_fast32_t i; | |
1678 | ||
1679 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_CLASS_NUMBER(h)); | |
1680 | duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h)); | |
1681 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ESIZE(h)); | |
1682 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ENEXT(h)); | |
1683 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_ASIZE(h)); | |
1684 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_GET_HSIZE(h)); | |
1685 | ||
1686 | for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { | |
1687 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HOBJECT_E_GET_FLAGS(heap, h, i)); | |
1688 | k = DUK_HOBJECT_E_GET_KEY(heap, h, i); | |
1689 | duk_debug_write_heapptr(thr, (duk_heaphdr *) k); | |
1690 | if (k == NULL) { | |
1691 | duk_debug_write_int(thr, 0); /* isAccessor */ | |
1692 | duk_debug_write_unused(thr); | |
1693 | continue; | |
1694 | } | |
1695 | if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { | |
1696 | duk_debug_write_int(thr, 1); /* isAccessor */ | |
1697 | duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); | |
1698 | duk_debug_write_heapptr(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); | |
1699 | } else { | |
1700 | duk_debug_write_int(thr, 0); /* isAccessor */ | |
1701 | ||
1702 | duk__debug_write_tval_heapptr(thr, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); | |
1703 | } | |
1704 | } | |
1705 | ||
1706 | for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { | |
1707 | /* Note: array dump will include elements beyond | |
1708 | * 'length'. | |
1709 | */ | |
1710 | duk__debug_write_tval_heapptr(thr, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); | |
1711 | } | |
1712 | break; | |
1713 | } | |
1714 | case DUK_HTYPE_BUFFER: { | |
1715 | duk_hbuffer *h = (duk_hbuffer *) hdr; | |
1716 | ||
1717 | duk_debug_write_uint(thr, (duk_uint32_t) DUK_HBUFFER_GET_SIZE(h)); | |
1718 | duk_debug_write_buffer(thr, (const char *) DUK_HBUFFER_GET_DATA_PTR(heap, h), (duk_size_t) DUK_HBUFFER_GET_SIZE(h)); | |
1719 | break; | |
1720 | } | |
1721 | default: { | |
1722 | DUK_D(DUK_DPRINT("invalid htype: %d", (int) DUK_HEAPHDR_GET_TYPE(hdr))); | |
1723 | } | |
1724 | } | |
1725 | } | |
1726 | ||
1727 | DUK_LOCAL void duk__debug_dump_heap_allocated(duk_hthread *thr, duk_heap *heap) { | |
1728 | duk_heaphdr *hdr; | |
1729 | ||
1730 | hdr = heap->heap_allocated; | |
1731 | while (hdr != NULL) { | |
1732 | duk__debug_dump_heaphdr(thr, heap, hdr); | |
1733 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
1734 | } | |
1735 | } | |
1736 | ||
1737 | #if defined(DUK_USE_STRTAB_CHAIN) | |
1738 | DUK_LOCAL void duk__debug_dump_strtab_chain(duk_hthread *thr, duk_heap *heap) { | |
1739 | duk_uint_fast32_t i, j; | |
1740 | duk_strtab_entry *e; | |
1741 | #if defined(DUK_USE_HEAPPTR16) | |
1742 | duk_uint16_t *lst; | |
1743 | #else | |
1744 | duk_hstring **lst; | |
1745 | #endif | |
1746 | duk_hstring *h; | |
1747 | ||
1748 | for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) { | |
1749 | e = heap->strtable + i; | |
1750 | if (e->listlen > 0) { | |
1751 | #if defined(DUK_USE_HEAPPTR16) | |
1752 | lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16); | |
1753 | #else | |
1754 | lst = e->u.strlist; | |
1755 | #endif | |
1756 | DUK_ASSERT(lst != NULL); | |
1757 | ||
1758 | for (j = 0; j < e->listlen; j++) { | |
1759 | #if defined(DUK_USE_HEAPPTR16) | |
1760 | h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[j]); | |
1761 | #else | |
1762 | h = lst[j]; | |
1763 | #endif | |
1764 | if (h != NULL) { | |
1765 | duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h); | |
1766 | } | |
1767 | } | |
1768 | } else { | |
1769 | #if defined(DUK_USE_HEAPPTR16) | |
1770 | h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16); | |
1771 | #else | |
1772 | h = e->u.str; | |
1773 | #endif | |
1774 | if (h != NULL) { | |
1775 | duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h); | |
1776 | } | |
1777 | } | |
1778 | } | |
1779 | } | |
1780 | #endif /* DUK_USE_STRTAB_CHAIN */ | |
1781 | ||
1782 | #if defined(DUK_USE_STRTAB_PROBE) | |
1783 | DUK_LOCAL void duk__debug_dump_strtab_probe(duk_hthread *thr, duk_heap *heap) { | |
1784 | duk_uint32_t i; | |
1785 | duk_hstring *h; | |
1786 | ||
1787 | for (i = 0; i < heap->st_size; i++) { | |
1788 | #if defined(DUK_USE_HEAPPTR16) | |
1789 | h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); | |
1790 | #else | |
1791 | h = heap->strtable[i]; | |
1792 | #endif | |
1793 | if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) { | |
1794 | continue; | |
1795 | } | |
1796 | ||
1797 | duk__debug_dump_heaphdr(thr, heap, (duk_heaphdr *) h); | |
1798 | } | |
1799 | } | |
1800 | #endif /* DUK_USE_STRTAB_PROBE */ | |
1801 | ||
1802 | DUK_LOCAL void duk__debug_handle_dump_heap(duk_hthread *thr, duk_heap *heap) { | |
1803 | DUK_D(DUK_DPRINT("debug command DumpHeap")); | |
1804 | ||
1805 | duk_debug_write_reply(thr); | |
1806 | duk__debug_dump_heap_allocated(thr, heap); | |
1807 | #if defined(DUK_USE_STRTAB_CHAIN) | |
1808 | duk__debug_dump_strtab_chain(thr, heap); | |
1809 | #endif | |
1810 | #if defined(DUK_USE_STRTAB_PROBE) | |
1811 | duk__debug_dump_strtab_probe(thr, heap); | |
1812 | #endif | |
1813 | duk_debug_write_eom(thr); | |
1814 | } | |
1815 | #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ | |
1816 | ||
1817 | DUK_LOCAL void duk__debug_handle_get_bytecode(duk_hthread *thr, duk_heap *heap) { | |
1818 | duk_activation *act; | |
1819 | duk_hcompiledfunction *fun = NULL; | |
1820 | duk_size_t i, n; | |
1821 | duk_tval *tv; | |
1822 | duk_hobject **fn; | |
1823 | duk_int32_t level = -1; | |
1824 | duk_uint8_t ibyte; | |
1825 | ||
1826 | DUK_UNREF(heap); | |
1827 | ||
1828 | DUK_D(DUK_DPRINT("debug command GetBytecode")); | |
1829 | ||
1830 | ibyte = duk_debug_peek_byte(thr); | |
1831 | if (ibyte != DUK_DBG_IB_EOM) { | |
1832 | tv = duk_debug_read_tval(thr); | |
1833 | if (tv == NULL) { | |
1834 | /* detached */ | |
1835 | return; | |
1836 | } | |
1837 | if (DUK_TVAL_IS_OBJECT(tv)) { | |
1838 | /* tentative, checked later */ | |
1839 | fun = (duk_hcompiledfunction *) DUK_TVAL_GET_OBJECT(tv); | |
1840 | DUK_ASSERT(fun != NULL); | |
1841 | } else if (DUK_TVAL_IS_NUMBER(tv)) { | |
1842 | level = (duk_int32_t) DUK_TVAL_GET_NUMBER(tv); | |
1843 | } else { | |
1844 | DUK_D(DUK_DPRINT("invalid argument to GetBytecode: %!T", tv)); | |
1845 | goto fail_args; | |
1846 | } | |
1847 | } | |
1848 | ||
1849 | if (fun == NULL) { | |
1850 | if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) { | |
1851 | DUK_D(DUK_DPRINT("invalid callstack level for GetBytecode")); | |
1852 | goto fail_level; | |
1853 | } | |
1854 | act = thr->callstack + thr->callstack_top + level; | |
1855 | fun = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act); | |
1856 | } | |
1857 | ||
1858 | if (fun == NULL || !DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) fun)) { | |
1859 | DUK_D(DUK_DPRINT("invalid argument to GetBytecode: %!O", fun)); | |
1860 | goto fail_args; | |
1861 | } | |
1862 | DUK_ASSERT(fun != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) fun)); | |
1863 | ||
1864 | duk_debug_write_reply(thr); | |
1865 | n = DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(heap, fun); | |
1866 | duk_debug_write_int(thr, (duk_int32_t) n); | |
1867 | tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(heap, fun); | |
1868 | for (i = 0; i < n; i++) { | |
1869 | duk_debug_write_tval(thr, tv); | |
1870 | tv++; | |
1871 | } | |
1872 | n = DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(heap, fun); | |
1873 | duk_debug_write_int(thr, (duk_int32_t) n); | |
1874 | fn = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(heap, fun); | |
1875 | for (i = 0; i < n; i++) { | |
1876 | duk_debug_write_hobject(thr, *fn); | |
1877 | fn++; | |
1878 | } | |
1879 | duk_debug_write_string(thr, | |
1880 | (const char *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(heap, fun), | |
1881 | (duk_size_t) DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(heap, fun)); | |
1882 | duk_debug_write_eom(thr); | |
1883 | return; | |
1884 | ||
1885 | fail_args: | |
1886 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid argument"); | |
1887 | return; | |
1888 | ||
1889 | fail_level: | |
1890 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level"); | |
1891 | return; | |
1892 | } | |
1893 | ||
1894 | /* | |
1895 | * Object inspection commands: GetHeapObjInfo, GetObjPropDesc, | |
1896 | * GetObjPropDescRange | |
1897 | */ | |
1898 | ||
1899 | #if defined(DUK_USE_DEBUGGER_INSPECT) | |
1900 | ||
1901 | #if 0 /* pruned */ | |
1902 | DUK_LOCAL const char * const duk__debug_getinfo_heaphdr_keys[] = { | |
1903 | "reachable", | |
1904 | "temproot", | |
1905 | "finalizable", | |
1906 | "finalized", | |
1907 | "readonly" | |
1908 | /* NULL not needed here */ | |
1909 | }; | |
1910 | DUK_LOCAL duk_uint_t duk__debug_getinfo_heaphdr_masks[] = { | |
1911 | DUK_HEAPHDR_FLAG_REACHABLE, | |
1912 | DUK_HEAPHDR_FLAG_TEMPROOT, | |
1913 | DUK_HEAPHDR_FLAG_FINALIZABLE, | |
1914 | DUK_HEAPHDR_FLAG_FINALIZED, | |
1915 | DUK_HEAPHDR_FLAG_READONLY, | |
1916 | 0 /* terminator */ | |
1917 | }; | |
1918 | #endif | |
1919 | DUK_LOCAL const char * const duk__debug_getinfo_hstring_keys[] = { | |
1920 | #if 0 | |
1921 | "arridx", | |
1922 | "internal", | |
1923 | "reserved_word", | |
1924 | "strict_reserved_word", | |
1925 | "eval_or_arguments", | |
1926 | #endif | |
1927 | "extdata" | |
1928 | /* NULL not needed here */ | |
1929 | }; | |
1930 | DUK_LOCAL duk_uint_t duk__debug_getinfo_hstring_masks[] = { | |
1931 | #if 0 | |
1932 | DUK_HSTRING_FLAG_ARRIDX, | |
1933 | DUK_HSTRING_FLAG_INTERNAL, | |
1934 | DUK_HSTRING_FLAG_RESERVED_WORD, | |
1935 | DUK_HSTRING_FLAG_STRICT_RESERVED_WORD, | |
1936 | DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS, | |
1937 | #endif | |
1938 | DUK_HSTRING_FLAG_EXTDATA, | |
1939 | 0 /* terminator */ | |
1940 | }; | |
1941 | DUK_LOCAL const char * const duk__debug_getinfo_hobject_keys[] = { | |
1942 | "extensible", | |
1943 | "constructable", | |
1944 | "bound", | |
1945 | "compiledfunction", | |
1946 | "nativefunction", | |
1947 | "bufferobject", | |
1948 | "thread", | |
1949 | "array_part", | |
1950 | "strict", | |
1951 | "notail", | |
1952 | "newenv", | |
1953 | "namebinding", | |
1954 | "createargs", | |
1955 | "envrecclosed", | |
1956 | "exotic_array", | |
1957 | "exotic_stringobj", | |
1958 | "exotic_arguments", | |
1959 | "exotic_dukfunc", | |
1960 | "exotic_proxyobj" | |
1961 | /* NULL not needed here */ | |
1962 | }; | |
1963 | DUK_LOCAL duk_uint_t duk__debug_getinfo_hobject_masks[] = { | |
1964 | DUK_HOBJECT_FLAG_EXTENSIBLE, | |
1965 | DUK_HOBJECT_FLAG_CONSTRUCTABLE, | |
1966 | DUK_HOBJECT_FLAG_BOUND, | |
1967 | DUK_HOBJECT_FLAG_COMPILEDFUNCTION, | |
1968 | DUK_HOBJECT_FLAG_NATIVEFUNCTION, | |
1969 | DUK_HOBJECT_FLAG_BUFFEROBJECT, | |
1970 | DUK_HOBJECT_FLAG_THREAD, | |
1971 | DUK_HOBJECT_FLAG_ARRAY_PART, | |
1972 | DUK_HOBJECT_FLAG_STRICT, | |
1973 | DUK_HOBJECT_FLAG_NOTAIL, | |
1974 | DUK_HOBJECT_FLAG_NEWENV, | |
1975 | DUK_HOBJECT_FLAG_NAMEBINDING, | |
1976 | DUK_HOBJECT_FLAG_CREATEARGS, | |
1977 | DUK_HOBJECT_FLAG_ENVRECCLOSED, | |
1978 | DUK_HOBJECT_FLAG_EXOTIC_ARRAY, | |
1979 | DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ, | |
1980 | DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS, | |
1981 | DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC, | |
1982 | DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ, | |
1983 | 0 /* terminator */ | |
1984 | }; | |
1985 | DUK_LOCAL const char * const duk__debug_getinfo_hbuffer_keys[] = { | |
1986 | "dynamic", | |
1987 | "external" | |
1988 | /* NULL not needed here */ | |
1989 | }; | |
1990 | DUK_LOCAL duk_uint_t duk__debug_getinfo_hbuffer_masks[] = { | |
1991 | DUK_HBUFFER_FLAG_DYNAMIC, | |
1992 | DUK_HBUFFER_FLAG_EXTERNAL, | |
1993 | 0 /* terminator */ | |
1994 | }; | |
1995 | ||
1996 | DUK_LOCAL void duk__debug_getinfo_flags_key(duk_hthread *thr, const char *key) { | |
1997 | duk_debug_write_uint(thr, 0); | |
1998 | duk_debug_write_cstring(thr, key); | |
1999 | } | |
2000 | ||
2001 | DUK_LOCAL void duk__debug_getinfo_prop_uint(duk_hthread *thr, const char *key, duk_uint_t val) { | |
2002 | duk_debug_write_uint(thr, 0); | |
2003 | duk_debug_write_cstring(thr, key); | |
2004 | duk_debug_write_uint(thr, val); | |
2005 | } | |
2006 | ||
2007 | DUK_LOCAL void duk__debug_getinfo_prop_int(duk_hthread *thr, const char *key, duk_int_t val) { | |
2008 | duk_debug_write_uint(thr, 0); | |
2009 | duk_debug_write_cstring(thr, key); | |
2010 | duk_debug_write_int(thr, val); | |
2011 | } | |
2012 | ||
2013 | DUK_LOCAL void duk__debug_getinfo_prop_bool(duk_hthread *thr, const char *key, duk_bool_t val) { | |
2014 | duk_debug_write_uint(thr, 0); | |
2015 | duk_debug_write_cstring(thr, key); | |
2016 | duk_debug_write_boolean(thr, val); | |
2017 | } | |
2018 | ||
2019 | DUK_LOCAL void duk__debug_getinfo_bitmask(duk_hthread *thr, const char * const * keys, duk_uint_t *masks, duk_uint_t flags) { | |
2020 | const char *key; | |
2021 | duk_uint_t mask; | |
2022 | ||
2023 | for (;;) { | |
2024 | mask = *masks++; | |
2025 | if (!mask) { | |
2026 | break; | |
2027 | } | |
2028 | key = *keys++; | |
2029 | DUK_ASSERT(key != NULL); | |
2030 | ||
2031 | DUK_DD(DUK_DDPRINT("inspect bitmask: key=%s, mask=0x%08lx, flags=0x%08lx", key, (unsigned long) mask, (unsigned long) flags)); | |
2032 | duk__debug_getinfo_prop_bool(thr, key, flags & mask); | |
2033 | } | |
2034 | } | |
2035 | ||
2036 | /* Inspect a property using a virtual index into a conceptual property list | |
2037 | * consisting of (1) all array part items from [0,a_size[ (even when above | |
2038 | * .length) and (2) all entry part items from [0,e_next[. Unused slots are | |
2039 | * indicated using dvalue 'unused'. | |
2040 | */ | |
2041 | DUK_LOCAL duk_bool_t duk__debug_getprop_index(duk_hthread *thr, duk_heap *heap, duk_hobject *h_obj, duk_uint_t idx) { | |
2042 | duk_uint_t a_size; | |
2043 | duk_tval *tv; | |
2044 | duk_hstring *h_key; | |
2045 | duk_hobject *h_getset; | |
2046 | duk_uint_t flags; | |
2047 | ||
2048 | DUK_UNREF(heap); | |
2049 | ||
2050 | a_size = DUK_HOBJECT_GET_ASIZE(h_obj); | |
2051 | if (idx < a_size) { | |
2052 | duk_debug_write_uint(thr, DUK_PROPDESC_FLAGS_WEC); | |
2053 | duk_debug_write_uint(thr, idx); | |
2054 | tv = DUK_HOBJECT_A_GET_VALUE_PTR(heap, h_obj, idx); | |
2055 | duk_debug_write_tval(thr, tv); | |
2056 | return 1; | |
2057 | } | |
2058 | ||
2059 | idx -= a_size; | |
2060 | if (idx >= DUK_HOBJECT_GET_ENEXT(h_obj)) { | |
2061 | return 0; | |
2062 | } | |
2063 | ||
2064 | h_key = DUK_HOBJECT_E_GET_KEY(heap, h_obj, idx); | |
2065 | if (h_key == NULL) { | |
2066 | duk_debug_write_uint(thr, 0); | |
2067 | duk_debug_write_null(thr); | |
2068 | duk_debug_write_unused(thr); | |
2069 | return 1; | |
2070 | } | |
2071 | ||
2072 | flags = DUK_HOBJECT_E_GET_FLAGS(heap, h_obj, idx); | |
2073 | if (DUK_HSTRING_HAS_INTERNAL(h_key)) { | |
2074 | flags |= DUK_DBG_PROPFLAG_INTERNAL; | |
2075 | } | |
2076 | duk_debug_write_uint(thr, flags); | |
2077 | duk_debug_write_hstring(thr, h_key); | |
2078 | if (flags & DUK_PROPDESC_FLAG_ACCESSOR) { | |
2079 | h_getset = DUK_HOBJECT_E_GET_VALUE_GETTER(heap, h_obj, idx); | |
2080 | if (h_getset) { | |
2081 | duk_debug_write_hobject(thr, h_getset); | |
2082 | } else { | |
2083 | duk_debug_write_null(thr); | |
2084 | } | |
2085 | h_getset = DUK_HOBJECT_E_GET_VALUE_SETTER(heap, h_obj, idx); | |
2086 | if (h_getset) { | |
2087 | duk_debug_write_hobject(thr, h_getset); | |
2088 | } else { | |
2089 | duk_debug_write_null(thr); | |
2090 | } | |
2091 | } else { | |
2092 | tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, h_obj, idx); | |
2093 | duk_debug_write_tval(thr, tv); | |
2094 | } | |
2095 | return 1; | |
2096 | } | |
2097 | ||
2098 | DUK_LOCAL void duk__debug_handle_get_heap_obj_info(duk_hthread *thr, duk_heap *heap) { | |
2099 | duk_heaphdr *h; | |
2100 | ||
2101 | DUK_D(DUK_DPRINT("debug command GetHeapObjInfo")); | |
2102 | DUK_UNREF(heap); | |
2103 | ||
2104 | h = duk_debug_read_any_ptr(thr); | |
2105 | if (!h) { | |
2106 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid target"); | |
2107 | return; | |
2108 | } | |
2109 | ||
2110 | duk_debug_write_reply(thr); | |
2111 | ||
2112 | /* As with all inspection code, we rely on the debug client providing | |
2113 | * a valid, non-stale pointer: there's no portable way to safely | |
2114 | * validate the pointer here. | |
2115 | */ | |
2116 | ||
2117 | duk__debug_getinfo_flags_key(thr, "heapptr"); | |
2118 | duk_debug_write_heapptr(thr, h); | |
2119 | ||
2120 | /* XXX: comes out as signed now */ | |
2121 | duk__debug_getinfo_prop_uint(thr, "heaphdr_flags", (duk_uint_t) DUK_HEAPHDR_GET_FLAGS(h)); | |
2122 | duk__debug_getinfo_prop_uint(thr, "heaphdr_type", (duk_uint_t) DUK_HEAPHDR_GET_TYPE(h)); | |
2123 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
2124 | duk__debug_getinfo_prop_uint(thr, "refcount", (duk_uint_t) DUK_HEAPHDR_GET_REFCOUNT(h)); | |
2125 | #endif | |
2126 | #if 0 /* pruned */ | |
2127 | duk__debug_getinfo_bitmask(thr, | |
2128 | duk__debug_getinfo_heaphdr_keys, | |
2129 | duk__debug_getinfo_heaphdr_masks, | |
2130 | DUK_HEAPHDR_GET_FLAGS_RAW(h)); | |
2131 | #endif | |
2132 | ||
2133 | switch (DUK_HEAPHDR_GET_TYPE(h)) { | |
2134 | case DUK_HTYPE_STRING: { | |
2135 | duk_hstring *h_str; | |
2136 | ||
2137 | h_str = (duk_hstring *) h; | |
2138 | duk__debug_getinfo_bitmask(thr, | |
2139 | duk__debug_getinfo_hstring_keys, | |
2140 | duk__debug_getinfo_hstring_masks, | |
2141 | DUK_HEAPHDR_GET_FLAGS_RAW(h)); | |
2142 | duk__debug_getinfo_prop_uint(thr, "bytelen", DUK_HSTRING_GET_BYTELEN(h_str)); | |
2143 | duk__debug_getinfo_prop_uint(thr, "charlen", DUK_HSTRING_GET_CHARLEN(h_str)); | |
2144 | duk__debug_getinfo_prop_uint(thr, "hash", DUK_HSTRING_GET_HASH(h_str)); | |
2145 | duk__debug_getinfo_flags_key(thr, "data"); | |
2146 | duk_debug_write_hstring(thr, h_str); | |
2147 | break; | |
2148 | } | |
2149 | case DUK_HTYPE_OBJECT: { | |
2150 | duk_hobject *h_obj; | |
2151 | duk_hobject *h_proto; | |
2152 | ||
2153 | h_obj = (duk_hobject *) h; | |
2154 | h_proto = DUK_HOBJECT_GET_PROTOTYPE(heap, h_obj); | |
2155 | ||
2156 | /* duk_hobject specific fields. */ | |
2157 | duk__debug_getinfo_bitmask(thr, | |
2158 | duk__debug_getinfo_hobject_keys, | |
2159 | duk__debug_getinfo_hobject_masks, | |
2160 | DUK_HEAPHDR_GET_FLAGS_RAW(h)); | |
2161 | duk__debug_getinfo_prop_uint(thr, "class_number", DUK_HOBJECT_GET_CLASS_NUMBER(h_obj)); | |
2162 | duk__debug_getinfo_flags_key(thr, "class_name"); | |
2163 | duk_debug_write_hstring(thr, DUK_HOBJECT_GET_CLASS_STRING(heap, h_obj)); | |
2164 | duk__debug_getinfo_flags_key(thr, "prototype"); | |
2165 | if (h_proto != NULL) { | |
2166 | duk_debug_write_hobject(thr, h_proto); | |
2167 | } else { | |
2168 | duk_debug_write_null(thr); | |
2169 | } | |
2170 | duk__debug_getinfo_flags_key(thr, "props"); | |
2171 | duk_debug_write_pointer(thr, (void *) DUK_HOBJECT_GET_PROPS(heap, h_obj)); | |
2172 | duk__debug_getinfo_prop_uint(thr, "e_size", (duk_uint_t) DUK_HOBJECT_GET_ESIZE(h_obj)); | |
2173 | duk__debug_getinfo_prop_uint(thr, "e_next", (duk_uint_t) DUK_HOBJECT_GET_ENEXT(h_obj)); | |
2174 | duk__debug_getinfo_prop_uint(thr, "a_size", (duk_uint_t) DUK_HOBJECT_GET_ASIZE(h_obj)); | |
2175 | duk__debug_getinfo_prop_uint(thr, "h_size", (duk_uint_t) DUK_HOBJECT_GET_HSIZE(h_obj)); | |
2176 | ||
2177 | /* duk_hnativefunction specific fields. */ | |
2178 | if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) { | |
2179 | duk_hnativefunction *h_fun; | |
2180 | h_fun = (duk_hnativefunction *) h_obj; | |
2181 | ||
2182 | duk__debug_getinfo_prop_int(thr, "nargs", h_fun->nargs); | |
2183 | duk__debug_getinfo_prop_int(thr, "magic", h_fun->magic); | |
2184 | duk__debug_getinfo_prop_bool(thr, "varargs", h_fun->magic == DUK_HNATIVEFUNCTION_NARGS_VARARGS); | |
2185 | /* Native function pointer may be different from a void pointer, | |
2186 | * and we serialize it from memory directly now (no byte swapping etc). | |
2187 | */ | |
2188 | duk__debug_getinfo_flags_key(thr, "funcptr"); | |
2189 | duk_debug_write_buffer(thr, (const char *) &h_fun->func, sizeof(h_fun->func)); | |
2190 | } | |
2191 | ||
2192 | if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { | |
2193 | duk_hcompiledfunction *h_fun; | |
2194 | duk_hbuffer *h_buf; | |
2195 | h_fun = (duk_hcompiledfunction *) h_obj; | |
2196 | ||
2197 | duk__debug_getinfo_prop_int(thr, "nregs", h_fun->nregs); | |
2198 | duk__debug_getinfo_prop_int(thr, "nargs", h_fun->nargs); | |
2199 | duk__debug_getinfo_prop_uint(thr, "start_line", h_fun->start_line); | |
2200 | duk__debug_getinfo_prop_uint(thr, "end_line", h_fun->end_line); | |
2201 | h_buf = (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, h_fun); | |
2202 | if (h_buf != NULL) { | |
2203 | duk__debug_getinfo_flags_key(thr, "data"); | |
2204 | duk_debug_write_heapptr(thr, (duk_heaphdr *) h_buf); | |
2205 | } | |
2206 | } | |
2207 | ||
2208 | if (DUK_HOBJECT_IS_THREAD(h_obj)) { | |
2209 | /* XXX: Currently no inspection of threads, e.g. value stack, call | |
2210 | * stack, catch stack, etc. | |
2211 | */ | |
2212 | duk_hthread *h_thr; | |
2213 | h_thr = (duk_hthread *) h_obj; | |
2214 | DUK_UNREF(h_thr); | |
2215 | } | |
2216 | ||
2217 | if (DUK_HOBJECT_IS_BUFFEROBJECT(h_obj)) { | |
2218 | duk_hbufferobject *h_bufobj; | |
2219 | h_bufobj = (duk_hbufferobject *) h_obj; | |
2220 | ||
2221 | duk__debug_getinfo_prop_uint(thr, "slice_offset", h_bufobj->offset); | |
2222 | duk__debug_getinfo_prop_uint(thr, "slice_length", h_bufobj->length); | |
2223 | duk__debug_getinfo_prop_uint(thr, "elem_shift", (duk_uint_t) h_bufobj->shift); | |
2224 | duk__debug_getinfo_prop_uint(thr, "elem_type", (duk_uint_t) h_bufobj->elem_type); | |
2225 | duk__debug_getinfo_prop_bool(thr, "is_view", (duk_uint_t) h_bufobj->is_view); | |
2226 | if (h_bufobj->buf != NULL) { | |
2227 | duk__debug_getinfo_flags_key(thr, "buffer"); | |
2228 | duk_debug_write_heapptr(thr, (duk_heaphdr *) h_bufobj->buf); | |
2229 | } | |
2230 | } | |
2231 | break; | |
2232 | } | |
2233 | case DUK_HTYPE_BUFFER: { | |
2234 | duk_hbuffer *h_buf; | |
2235 | ||
2236 | h_buf = (duk_hbuffer *) h; | |
2237 | duk__debug_getinfo_bitmask(thr, | |
2238 | duk__debug_getinfo_hbuffer_keys, | |
2239 | duk__debug_getinfo_hbuffer_masks, | |
2240 | DUK_HEAPHDR_GET_FLAGS_RAW(h)); | |
2241 | duk__debug_getinfo_prop_uint(thr, "size", (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_buf)); | |
2242 | duk__debug_getinfo_flags_key(thr, "dataptr"); | |
2243 | duk_debug_write_pointer(thr, (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_buf)); | |
2244 | duk__debug_getinfo_flags_key(thr, "data"); | |
2245 | duk_debug_write_hbuffer(thr, h_buf); /* tolerates NULL h_buf */ | |
2246 | break; | |
2247 | } | |
2248 | default: { | |
2249 | /* Since we already started writing the reply, just emit nothing. */ | |
2250 | DUK_D(DUK_DPRINT("inspect target pointer has invalid heaphdr type")); | |
2251 | } | |
2252 | } | |
2253 | ||
2254 | duk_debug_write_eom(thr); | |
2255 | } | |
2256 | ||
2257 | DUK_LOCAL void duk__debug_handle_get_obj_prop_desc(duk_hthread *thr, duk_heap *heap) { | |
2258 | duk_heaphdr *h; | |
2259 | duk_hobject *h_obj; | |
2260 | duk_hstring *h_key; | |
2261 | duk_propdesc desc; | |
2262 | ||
2263 | DUK_D(DUK_DPRINT("debug command GetObjPropDesc")); | |
2264 | DUK_UNREF(heap); | |
2265 | ||
2266 | h = duk_debug_read_any_ptr(thr); | |
2267 | if (!h) { | |
2268 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid target"); | |
2269 | return; | |
2270 | } | |
2271 | h_key = duk_debug_read_hstring(thr); | |
2272 | if (h == NULL || DUK_HEAPHDR_GET_TYPE(h) != DUK_HTYPE_OBJECT || h_key == NULL) { | |
2273 | goto fail_args; | |
2274 | } | |
2275 | h_obj = (duk_hobject *) h; | |
2276 | ||
2277 | if (duk_hobject_get_own_propdesc(thr, h_obj, h_key, &desc, 0 /*flags*/)) { | |
2278 | duk_int_t virtual_idx; | |
2279 | duk_bool_t rc; | |
2280 | ||
2281 | /* To use the shared helper need the virtual index. */ | |
2282 | DUK_ASSERT(desc.e_idx >= 0 || desc.a_idx >= 0); | |
2283 | virtual_idx = (desc.a_idx >= 0 ? desc.a_idx : | |
2284 | (duk_int_t) DUK_HOBJECT_GET_ASIZE(h_obj) + desc.e_idx); | |
2285 | ||
2286 | duk_debug_write_reply(thr); | |
2287 | rc = duk__debug_getprop_index(thr, heap, h_obj, (duk_uint_t) virtual_idx); | |
2288 | DUK_ASSERT(rc == 1); | |
2289 | DUK_UNREF(rc); | |
2290 | duk_debug_write_eom(thr); | |
2291 | } else { | |
2292 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "not found"); | |
2293 | } | |
2294 | return; | |
2295 | ||
2296 | fail_args: | |
2297 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid args"); | |
2298 | } | |
2299 | ||
2300 | DUK_LOCAL void duk__debug_handle_get_obj_prop_desc_range(duk_hthread *thr, duk_heap *heap) { | |
2301 | duk_heaphdr *h; | |
2302 | duk_hobject *h_obj; | |
2303 | duk_uint_t idx, idx_start, idx_end; | |
2304 | ||
2305 | DUK_D(DUK_DPRINT("debug command GetObjPropDescRange")); | |
2306 | DUK_UNREF(heap); | |
2307 | ||
2308 | h = duk_debug_read_any_ptr(thr); | |
2309 | idx_start = duk_debug_read_int(thr); | |
2310 | idx_end = duk_debug_read_int(thr); | |
2311 | if (h == NULL || DUK_HEAPHDR_GET_TYPE(h) != DUK_HTYPE_OBJECT) { | |
2312 | goto fail_args; | |
2313 | } | |
2314 | h_obj = (duk_hobject *) h; | |
2315 | ||
2316 | /* The index range space is conceptually the array part followed by the | |
2317 | * entry part. Unlike normal enumeration all slots are exposed here as | |
2318 | * is and return 'unused' if the slots are not in active use. In particular | |
2319 | * the array part is included for the full a_size regardless of what the | |
2320 | * array .length is. | |
2321 | */ | |
2322 | ||
2323 | duk_debug_write_reply(thr); | |
2324 | for (idx = idx_start; idx < idx_end; idx++) { | |
2325 | if (!duk__debug_getprop_index(thr, heap, h_obj, idx)) { | |
2326 | break; | |
2327 | } | |
2328 | } | |
2329 | duk_debug_write_eom(thr); | |
2330 | return; | |
2331 | ||
2332 | fail_args: | |
2333 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNKNOWN, "invalid args"); | |
2334 | } | |
2335 | ||
2336 | #endif /* DUK_USE_DEBUGGER_INSPECT */ | |
2337 | ||
2338 | /* | |
2339 | * Process incoming debug requests | |
2340 | * | |
2341 | * Individual request handlers can push temporaries on the value stack and | |
2342 | * rely on duk__debug_process_message() to restore the value stack top | |
2343 | * automatically. | |
2344 | */ | |
2345 | ||
2346 | /* Process one debug message. Automatically restore value stack top to its | |
2347 | * entry value, so that individual message handlers don't need exact value | |
2348 | * stack handling which is convenient. | |
2349 | */ | |
2350 | DUK_LOCAL void duk__debug_process_message(duk_hthread *thr) { | |
2351 | duk_context *ctx = (duk_context *) thr; | |
2352 | duk_heap *heap; | |
2353 | duk_uint8_t x; | |
2354 | duk_int32_t cmd; | |
2355 | duk_idx_t entry_top; | |
2356 | ||
2357 | DUK_ASSERT(thr != NULL); | |
2358 | heap = thr->heap; | |
2359 | DUK_ASSERT(heap != NULL); | |
2360 | DUK_UNREF(ctx); | |
2361 | ||
2362 | entry_top = duk_get_top(ctx); | |
2363 | ||
2364 | x = duk_debug_read_byte(thr); | |
2365 | switch (x) { | |
2366 | case DUK_DBG_IB_REQUEST: { | |
2367 | cmd = duk_debug_read_int(thr); | |
2368 | switch (cmd) { | |
2369 | case DUK_DBG_CMD_BASICINFO: { | |
2370 | duk__debug_handle_basic_info(thr, heap); | |
2371 | break; | |
2372 | } | |
2373 | case DUK_DBG_CMD_TRIGGERSTATUS: { | |
2374 | duk__debug_handle_trigger_status(thr, heap); | |
2375 | break; | |
2376 | } | |
2377 | case DUK_DBG_CMD_PAUSE: { | |
2378 | duk__debug_handle_pause(thr, heap); | |
2379 | break; | |
2380 | } | |
2381 | case DUK_DBG_CMD_RESUME: { | |
2382 | duk__debug_handle_resume(thr, heap); | |
2383 | break; | |
2384 | } | |
2385 | case DUK_DBG_CMD_STEPINTO: | |
2386 | case DUK_DBG_CMD_STEPOVER: | |
2387 | case DUK_DBG_CMD_STEPOUT: { | |
2388 | duk__debug_handle_step(thr, heap, cmd); | |
2389 | break; | |
2390 | } | |
2391 | case DUK_DBG_CMD_LISTBREAK: { | |
2392 | duk__debug_handle_list_break(thr, heap); | |
2393 | break; | |
2394 | } | |
2395 | case DUK_DBG_CMD_ADDBREAK: { | |
2396 | duk__debug_handle_add_break(thr, heap); | |
2397 | break; | |
2398 | } | |
2399 | case DUK_DBG_CMD_DELBREAK: { | |
2400 | duk__debug_handle_del_break(thr, heap); | |
2401 | break; | |
2402 | } | |
2403 | case DUK_DBG_CMD_GETVAR: { | |
2404 | duk__debug_handle_get_var(thr, heap); | |
2405 | break; | |
2406 | } | |
2407 | case DUK_DBG_CMD_PUTVAR: { | |
2408 | duk__debug_handle_put_var(thr, heap); | |
2409 | break; | |
2410 | } | |
2411 | case DUK_DBG_CMD_GETCALLSTACK: { | |
2412 | duk__debug_handle_get_call_stack(thr, heap); | |
2413 | break; | |
2414 | } | |
2415 | case DUK_DBG_CMD_GETLOCALS: { | |
2416 | duk__debug_handle_get_locals(thr, heap); | |
2417 | break; | |
2418 | } | |
2419 | case DUK_DBG_CMD_EVAL: { | |
2420 | duk__debug_handle_eval(thr, heap); | |
2421 | break; | |
2422 | } | |
2423 | case DUK_DBG_CMD_DETACH: { | |
2424 | /* The actual detached_cb call is postponed to message loop so | |
2425 | * we don't need any special precautions here (just skip to EOM | |
2426 | * on the already closed connection). | |
2427 | */ | |
2428 | duk__debug_handle_detach(thr, heap); | |
2429 | break; | |
2430 | } | |
2431 | #if defined(DUK_USE_DEBUGGER_DUMPHEAP) | |
2432 | case DUK_DBG_CMD_DUMPHEAP: { | |
2433 | duk__debug_handle_dump_heap(thr, heap); | |
2434 | break; | |
2435 | } | |
2436 | #endif /* DUK_USE_DEBUGGER_DUMPHEAP */ | |
2437 | case DUK_DBG_CMD_GETBYTECODE: { | |
2438 | duk__debug_handle_get_bytecode(thr, heap); | |
2439 | break; | |
2440 | } | |
2441 | case DUK_DBG_CMD_APPREQUEST: { | |
2442 | duk__debug_handle_apprequest(thr, heap); | |
2443 | break; | |
2444 | } | |
2445 | #if defined(DUK_USE_DEBUGGER_INSPECT) | |
2446 | case DUK_DBG_CMD_GETHEAPOBJINFO: { | |
2447 | duk__debug_handle_get_heap_obj_info(thr, heap); | |
2448 | break; | |
2449 | } | |
2450 | case DUK_DBG_CMD_GETOBJPROPDESC: { | |
2451 | duk__debug_handle_get_obj_prop_desc(thr, heap); | |
2452 | break; | |
2453 | } | |
2454 | case DUK_DBG_CMD_GETOBJPROPDESCRANGE: { | |
2455 | duk__debug_handle_get_obj_prop_desc_range(thr, heap); | |
2456 | break; | |
2457 | } | |
2458 | #endif /* DUK_USE_DEBUGGER_INSPECT */ | |
2459 | default: { | |
2460 | DUK_D(DUK_DPRINT("debug command unsupported: %d", (int) cmd)); | |
2461 | duk_debug_write_error_eom(thr, DUK_DBG_ERR_UNSUPPORTED, "unsupported command"); | |
2462 | } | |
2463 | } /* switch cmd */ | |
2464 | break; | |
2465 | } | |
2466 | case DUK_DBG_IB_REPLY: { | |
2467 | DUK_D(DUK_DPRINT("debug reply, skipping")); | |
2468 | break; | |
2469 | } | |
2470 | case DUK_DBG_IB_ERROR: { | |
2471 | DUK_D(DUK_DPRINT("debug error, skipping")); | |
2472 | break; | |
2473 | } | |
2474 | case DUK_DBG_IB_NOTIFY: { | |
2475 | DUK_D(DUK_DPRINT("debug notify, skipping")); | |
2476 | break; | |
2477 | } | |
2478 | default: { | |
2479 | DUK_D(DUK_DPRINT("invalid initial byte, drop connection: %d", (int) x)); | |
2480 | goto fail; | |
2481 | } | |
2482 | } /* switch initial byte */ | |
2483 | ||
2484 | DUK_ASSERT(duk_get_top(ctx) >= entry_top); | |
2485 | duk_set_top(ctx, entry_top); | |
2486 | duk__debug_skip_to_eom(thr); | |
2487 | return; | |
2488 | ||
2489 | fail: | |
2490 | DUK_ASSERT(duk_get_top(ctx) >= entry_top); | |
2491 | duk_set_top(ctx, entry_top); | |
2492 | DUK__SET_CONN_BROKEN(thr, 1); | |
2493 | return; | |
2494 | } | |
2495 | ||
2496 | DUK_LOCAL void duk__check_resend_status(duk_hthread *thr) { | |
2497 | if (thr->heap->dbg_read_cb != NULL && thr->heap->dbg_state_dirty) { | |
2498 | duk_debug_send_status(thr); | |
2499 | thr->heap->dbg_state_dirty = 0; | |
2500 | } | |
2501 | } | |
2502 | ||
2503 | DUK_INTERNAL duk_bool_t duk_debug_process_messages(duk_hthread *thr, duk_bool_t no_block) { | |
2504 | duk_context *ctx = (duk_context *) thr; | |
2505 | #if defined(DUK_USE_ASSERTIONS) | |
2506 | duk_idx_t entry_top; | |
2507 | #endif | |
2508 | duk_bool_t retval = 0; | |
2509 | ||
2510 | DUK_ASSERT(thr != NULL); | |
2511 | DUK_UNREF(ctx); | |
2512 | DUK_ASSERT(thr->heap != NULL); | |
2513 | #if defined(DUK_USE_ASSERTIONS) | |
2514 | entry_top = duk_get_top(ctx); | |
2515 | #endif | |
2516 | ||
2517 | DUK_D(DUK_DPRINT("process debug messages: read_cb=%s, no_block=%ld, detaching=%ld, processing=%ld", | |
2518 | thr->heap->dbg_read_cb ? "not NULL" : "NULL", (long) no_block, | |
2519 | (long) thr->heap->dbg_detaching, (long) thr->heap->dbg_processing)); | |
2520 | DUK_DD(DUK_DDPRINT("top at entry: %ld", (long) duk_get_top(ctx))); | |
2521 | ||
2522 | /* thr->heap->dbg_detaching may be != 0 if a debugger write outside | |
2523 | * the message loop caused a transport error and detach1() to run. | |
2524 | */ | |
2525 | DUK_ASSERT(thr->heap->dbg_detaching == 0 || thr->heap->dbg_detaching == 1); | |
2526 | DUK_ASSERT(thr->heap->dbg_processing == 0); | |
2527 | thr->heap->dbg_processing = 1; | |
2528 | ||
2529 | /* Ensure dirty state causes a Status even if never process any | |
2530 | * messages. This is expected by the bytecode executor when in | |
2531 | * the running state. | |
2532 | */ | |
2533 | duk__check_resend_status(thr); | |
2534 | ||
2535 | for (;;) { | |
2536 | /* Process messages until we're no longer paused or we peek | |
2537 | * and see there's nothing to read right now. | |
2538 | */ | |
2539 | DUK_DD(DUK_DDPRINT("top at loop top: %ld", (long) duk_get_top(ctx))); | |
2540 | DUK_ASSERT(thr->heap->dbg_processing == 1); | |
2541 | ||
2542 | while (thr->heap->dbg_read_cb == NULL && thr->heap->dbg_detaching) { | |
2543 | /* Detach is pending; can be triggered from outside the | |
2544 | * debugger loop (e.g. Status notify write error) or by | |
2545 | * previous message handling. Call detached callback | |
2546 | * here, in a controlled state, to ensure a possible | |
2547 | * reattach inside the detached_cb is handled correctly. | |
2548 | * | |
2549 | * Recheck for detach in a while loop: an immediate | |
2550 | * reattach involves a call to duk_debugger_attach() | |
2551 | * which writes a debugger handshake line immediately | |
2552 | * inside the API call. If the transport write fails | |
2553 | * for that handshake, we can immediately end up in a | |
2554 | * "transport broken, detaching" case several times here. | |
2555 | * Loop back until we're either cleanly attached or | |
2556 | * fully detached. | |
2557 | * | |
2558 | * NOTE: Reset dbg_processing = 1 forcibly, in case we | |
2559 | * re-attached; duk_debugger_attach() sets dbg_processing | |
2560 | * to 0 at the moment. | |
2561 | */ | |
2562 | ||
2563 | DUK_D(DUK_DPRINT("detach pending (dbg_read_cb == NULL, dbg_detaching != 0), call detach2")); | |
2564 | ||
2565 | duk__debug_do_detach2(thr->heap); | |
2566 | thr->heap->dbg_processing = 1; /* may be set to 0 by duk_debugger_attach() inside callback */ | |
2567 | ||
2568 | DUK_D(DUK_DPRINT("after detach2 (and possible reattach): dbg_read_cb=%s, dbg_detaching=%ld", | |
2569 | thr->heap->dbg_read_cb ? "not NULL" : "NULL", (long) thr->heap->dbg_detaching)); | |
2570 | } | |
2571 | DUK_ASSERT(thr->heap->dbg_detaching == 0); /* true even with reattach */ | |
2572 | DUK_ASSERT(thr->heap->dbg_processing == 1); /* even after a detach and possible reattach */ | |
2573 | ||
2574 | if (thr->heap->dbg_read_cb == NULL) { | |
2575 | DUK_D(DUK_DPRINT("debug connection broken (and not detaching), stop processing messages")); | |
2576 | break; | |
2577 | } | |
2578 | ||
2579 | if (!thr->heap->dbg_paused || no_block) { | |
2580 | if (!duk_debug_read_peek(thr)) { | |
2581 | /* Note: peek cannot currently trigger a detach | |
2582 | * so the dbg_detaching == 0 assert outside the | |
2583 | * loop is correct. | |
2584 | */ | |
2585 | DUK_D(DUK_DPRINT("processing debug message, peek indicated no data, stop processing messages")); | |
2586 | break; | |
2587 | } | |
2588 | DUK_D(DUK_DPRINT("processing debug message, peek indicated there is data, handle it")); | |
2589 | } else { | |
2590 | DUK_D(DUK_DPRINT("paused, process debug message, blocking if necessary")); | |
2591 | } | |
2592 | ||
2593 | duk__check_resend_status(thr); | |
2594 | duk__debug_process_message(thr); | |
2595 | duk__check_resend_status(thr); | |
2596 | ||
2597 | retval = 1; /* processed one or more messages */ | |
2598 | } | |
2599 | ||
2600 | DUK_ASSERT(thr->heap->dbg_detaching == 0); | |
2601 | DUK_ASSERT(thr->heap->dbg_processing == 1); | |
2602 | thr->heap->dbg_processing = 0; | |
2603 | ||
2604 | /* As an initial implementation, read flush after exiting the message | |
2605 | * loop. If transport is broken, this is a no-op (with debug logs). | |
2606 | */ | |
2607 | duk_debug_read_flush(thr); /* this cannot initiate a detach */ | |
2608 | DUK_ASSERT(thr->heap->dbg_detaching == 0); | |
2609 | ||
2610 | DUK_DD(DUK_DDPRINT("top at exit: %ld", (long) duk_get_top(ctx))); | |
2611 | ||
2612 | #if defined(DUK_USE_ASSERTIONS) | |
2613 | /* Easy to get wrong, so assert for it. */ | |
2614 | DUK_ASSERT(entry_top == duk_get_top(ctx)); | |
2615 | #endif | |
2616 | ||
2617 | return retval; | |
2618 | } | |
2619 | ||
2620 | /* | |
2621 | * Halt execution helper | |
2622 | */ | |
2623 | ||
2624 | /* Halt execution and enter a debugger message loop until execution is resumed | |
2625 | * by the client. PC for the current activation may be temporarily decremented | |
2626 | * so that the "current" instruction will be shown by the client. This helper | |
2627 | * is callable from anywhere, also outside bytecode executor. | |
2628 | */ | |
2629 | ||
2630 | DUK_INTERNAL void duk_debug_halt_execution(duk_hthread *thr, duk_bool_t use_prev_pc) { | |
2631 | duk_activation *act; | |
2632 | duk_hcompiledfunction *fun; | |
2633 | duk_instr_t *old_pc = NULL; | |
2634 | ||
2635 | DUK_ASSERT(thr != NULL); | |
2636 | DUK_ASSERT(thr->heap != NULL); | |
2637 | DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)); | |
2638 | DUK_ASSERT(thr->heap->dbg_processing == 0); | |
2639 | ||
2640 | DUK_HEAP_SET_PAUSED(thr->heap); | |
2641 | ||
2642 | act = duk_hthread_get_current_activation(thr); | |
2643 | ||
2644 | /* NOTE: act may be NULL if an error is thrown outside of any activation, | |
2645 | * which may happen in the case of, e.g. syntax errors. | |
2646 | */ | |
2647 | ||
2648 | /* Decrement PC if that was requested, this requires a PC sync. */ | |
2649 | if (act != NULL) { | |
2650 | duk_hthread_sync_currpc(thr); | |
2651 | old_pc = act->curr_pc; | |
2652 | fun = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act); | |
2653 | ||
2654 | /* Short circuit if is safe: if act->curr_pc != NULL, 'fun' is | |
2655 | * guaranteed to be a non-NULL Ecmascript function. | |
2656 | */ | |
2657 | DUK_ASSERT(act->curr_pc == NULL || | |
2658 | (fun != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) fun))); | |
2659 | if (use_prev_pc && | |
2660 | act->curr_pc != NULL && | |
2661 | act->curr_pc > DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(thr->heap, fun)) { | |
2662 | act->curr_pc--; | |
2663 | } | |
2664 | } | |
2665 | ||
2666 | /* Process debug messages until we are no longer paused. */ | |
2667 | ||
2668 | /* NOTE: This is a bit fragile. It's important to ensure that | |
2669 | * duk_debug_process_messages() never throws an error or | |
2670 | * act->curr_pc will never be reset. | |
2671 | */ | |
2672 | ||
2673 | thr->heap->dbg_state_dirty = 1; | |
2674 | while (thr->heap->dbg_paused) { | |
2675 | DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)); | |
2676 | DUK_ASSERT(thr->heap->dbg_processing); | |
2677 | duk_debug_process_messages(thr, 0 /*no_block*/); | |
2678 | } | |
2679 | ||
2680 | /* XXX: Decrementing and restoring act->curr_pc works now, but if the | |
2681 | * debugger message loop gains the ability to adjust the current PC | |
2682 | * (e.g. a forced jump) restoring the PC here will break. Another | |
2683 | * approach would be to use a state flag for the "decrement 1 from | |
2684 | * topmost activation's PC" and take it into account whenever dealing | |
2685 | * with PC values. | |
2686 | */ | |
2687 | if (act != NULL) { | |
2688 | act->curr_pc = old_pc; /* restore PC */ | |
2689 | } | |
2690 | } | |
2691 | ||
2692 | /* | |
2693 | * Breakpoint management | |
2694 | */ | |
2695 | ||
2696 | DUK_INTERNAL duk_small_int_t duk_debug_add_breakpoint(duk_hthread *thr, duk_hstring *filename, duk_uint32_t line) { | |
2697 | duk_heap *heap; | |
2698 | duk_breakpoint *b; | |
2699 | ||
2700 | /* Caller must trigger recomputation of active breakpoint list. To | |
2701 | * ensure stale values are not used if that doesn't happen, clear the | |
2702 | * active breakpoint list here. | |
2703 | */ | |
2704 | ||
2705 | DUK_ASSERT(thr != NULL); | |
2706 | DUK_ASSERT(filename != NULL); | |
2707 | heap = thr->heap; | |
2708 | DUK_ASSERT(heap != NULL); | |
2709 | ||
2710 | if (heap->dbg_breakpoint_count >= DUK_HEAP_MAX_BREAKPOINTS) { | |
2711 | DUK_D(DUK_DPRINT("failed to add breakpoint for %O:%ld, all breakpoint slots used", | |
2712 | (duk_heaphdr *) filename, (long) line)); | |
2713 | return -1; | |
2714 | } | |
2715 | heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; | |
2716 | b = heap->dbg_breakpoints + (heap->dbg_breakpoint_count++); | |
2717 | b->filename = filename; | |
2718 | b->line = line; | |
2719 | DUK_HSTRING_INCREF(thr, filename); | |
2720 | ||
2721 | return heap->dbg_breakpoint_count - 1; /* index */ | |
2722 | } | |
2723 | ||
2724 | DUK_INTERNAL duk_bool_t duk_debug_remove_breakpoint(duk_hthread *thr, duk_small_uint_t breakpoint_index) { | |
2725 | duk_heap *heap; | |
2726 | duk_hstring *h; | |
2727 | duk_breakpoint *b; | |
2728 | duk_size_t move_size; | |
2729 | ||
2730 | /* Caller must trigger recomputation of active breakpoint list. To | |
2731 | * ensure stale values are not used if that doesn't happen, clear the | |
2732 | * active breakpoint list here. | |
2733 | */ | |
2734 | ||
2735 | DUK_ASSERT(thr != NULL); | |
2736 | heap = thr->heap; | |
2737 | DUK_ASSERT(heap != NULL); | |
2738 | DUK_ASSERT(DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)); | |
2739 | DUK_ASSERT_DISABLE(breakpoint_index >= 0); /* unsigned */ | |
2740 | ||
2741 | if (breakpoint_index >= heap->dbg_breakpoint_count) { | |
2742 | DUK_D(DUK_DPRINT("invalid breakpoint index: %ld", (long) breakpoint_index)); | |
2743 | return 0; | |
2744 | } | |
2745 | b = heap->dbg_breakpoints + breakpoint_index; | |
2746 | ||
2747 | h = b->filename; | |
2748 | DUK_ASSERT(h != NULL); | |
2749 | ||
2750 | move_size = sizeof(duk_breakpoint) * (heap->dbg_breakpoint_count - breakpoint_index - 1); | |
2751 | if (move_size > 0) { | |
2752 | DUK_MEMMOVE((void *) b, | |
2753 | (const void *) (b + 1), | |
2754 | (size_t) move_size); | |
2755 | } | |
2756 | heap->dbg_breakpoint_count--; | |
2757 | heap->dbg_breakpoints_active[0] = (duk_breakpoint *) NULL; | |
2758 | ||
2759 | DUK_HSTRING_DECREF(thr, h); /* side effects */ | |
2760 | DUK_UNREF(h); /* w/o refcounting */ | |
2761 | ||
2762 | /* Breakpoint entries above the used area are left as garbage. */ | |
2763 | ||
2764 | return 1; | |
2765 | } | |
2766 | ||
2767 | #undef DUK__SET_CONN_BROKEN | |
2768 | ||
2769 | #else /* DUK_USE_DEBUGGER_SUPPORT */ | |
2770 | ||
2771 | /* No debugger support. */ | |
2772 | ||
2773 | #endif /* DUK_USE_DEBUGGER_SUPPORT */ |