debug.c
1 #include <stdio.h>
2 #include <string.h>
3 
4 #include <kuroko/debug.h>
5 #include <kuroko/vm.h>
6 #include <kuroko/util.h>
7 #include <kuroko/compiler.h>
8 
9 #include "private.h"
10 #include "opcode_enum.h"
11 
12 #ifndef KRK_DISABLE_DEBUG
13 #define NOOP (void)0
14 
15 #if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER)
16 #pragma GCC optimize ("Os")
17 #endif
18 
19 #define STRING_DEBUG_TRUNCATE 50
20 
21 void krk_printValueSafe(FILE * f, KrkValue printable) {
22  if (!IS_OBJECT(printable)) {
23  switch (KRK_VAL_TYPE(printable)) {
24  case KRK_VAL_INTEGER: fprintf(f, PRIkrk_int, AS_INTEGER(printable)); break;
25  case KRK_VAL_BOOLEAN: fprintf(f, "%s", AS_BOOLEAN(printable) ? "True" : "False"); break;
26  case KRK_VAL_NONE: fprintf(f, "None"); break;
27  case KRK_VAL_HANDLER:
28  switch (AS_HANDLER_TYPE(printable)) {
29  case OP_PUSH_TRY: fprintf(f, "{try->%d}", (int)AS_HANDLER_TARGET(printable)); break;
30  case OP_PUSH_WITH: fprintf(f, "{with->%d}", (int)AS_HANDLER_TARGET(printable)); break;
31  case OP_RAISE: fprintf(f, "{raise<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
32  case OP_FILTER_EXCEPT: fprintf(f, "{except<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
33  case OP_BEGIN_FINALLY: fprintf(f, "{finally<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
34  case OP_RETURN: fprintf(f, "{return<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
35  case OP_END_FINALLY: fprintf(f, "{end<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
36  case OP_EXIT_LOOP: fprintf(f, "{exit<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
37  case OP_RAISE_FROM: fprintf(f, "{reraise<-%d}", (int)AS_HANDLER_TARGET(printable)); break;
38  }
39  break;
40  case KRK_VAL_KWARGS: {
41  if (AS_INTEGER(printable) == KWARGS_SINGLE) {
42  fprintf(f, "{unpack single}");
43  } else if (AS_INTEGER(printable) == KWARGS_LIST) {
44  fprintf(f, "{unpack list}");
45  } else if (AS_INTEGER(printable) == KWARGS_DICT) {
46  fprintf(f, "{unpack dict}");
47  } else if (AS_INTEGER(printable) == KWARGS_NIL) {
48  fprintf(f, "{unpack nil}");
49  } else if (AS_INTEGER(printable) == KWARGS_UNSET) {
50  fprintf(f, "{unset default}");
51  } else {
52  fprintf(f, "{sentinel=" PRIkrk_int "}",AS_INTEGER(printable));
53  }
54  break;
55  }
56  default:
57 #ifndef KRK_NO_FLOAT
58  if (IS_FLOATING(printable)) fprintf(f, "%.16g", AS_FLOATING(printable));
59 #endif
60  break;
61  }
62  } else if (IS_STRING(printable)) {
63  fprintf(f, "'");
64  /*
65  * Print at most STRING_DEBUG_TRUNCATE characters, as bytes, escaping anything not ASCII.
66  * See also str.__repr__ which does something similar with escape sequences, but this
67  * is a dumber, safer, and slightly faster approach.
68  */
69  for (size_t c = 0; c < AS_STRING(printable)->length && c < STRING_DEBUG_TRUNCATE; ++c) {
70  unsigned char byte = (unsigned char)AS_CSTRING(printable)[c];
71  switch (byte) {
72  case '\\': fprintf(f, "\\\\"); break;
73  case '\n': fprintf(f, "\\n"); break;
74  case '\r': fprintf(f, "\\r"); break;
75  case '\'': fprintf(f, "\\'"); break;
76  default: {
77  if (byte < ' ' || byte > '~') {
78  fprintf(f, "\\x%02x", byte);
79  } else {
80  fprintf(f, "%c", byte);
81  }
82  break;
83  }
84  }
85  }
86  if (AS_STRING(printable)->length > STRING_DEBUG_TRUNCATE) {
87  fprintf(f,"...");
88  }
89  fprintf(f,"'");
90  } else {
91  switch (AS_OBJECT(printable)->type) {
92  case KRK_OBJ_CODEOBJECT: fprintf(f, "<codeobject %s>", AS_codeobject(printable)->name ? AS_codeobject(printable)->name->chars : "?"); break;
93  case KRK_OBJ_CLASS: fprintf(f, "<class %s>", AS_CLASS(printable)->name ? AS_CLASS(printable)->name->chars : "?"); break;
94  case KRK_OBJ_INSTANCE: fprintf(f, "<instance of %s>", AS_INSTANCE(printable)->_class->name->chars); break;
95  case KRK_OBJ_NATIVE: fprintf(f, "<nativefn %s>", ((KrkNative*)AS_OBJECT(printable))->name); break;
96  case KRK_OBJ_CLOSURE: fprintf(f, "<function %s>", AS_CLOSURE(printable)->function->name->chars); break;
97  case KRK_OBJ_BYTES: fprintf(f, "<bytes of len %ld>", (long)AS_BYTES(printable)->length); break;
98  case KRK_OBJ_TUPLE: {
99  fprintf(f, "(");
100  for (size_t i = 0; i < AS_TUPLE(printable)->values.count; ++i) {
101  krk_printValueSafe(f, AS_TUPLE(printable)->values.values[i]);
102  if (i + 1 != AS_TUPLE(printable)->values.count) {
103  fprintf(f, ",");
104  }
105  }
106  fprintf(f, ")");
107  } break;
108  case KRK_OBJ_BOUND_METHOD: fprintf(f, "<method %s>",
109  AS_BOUND_METHOD(printable)->method ? (
110  AS_BOUND_METHOD(printable)->method->type == KRK_OBJ_CLOSURE ? ((KrkClosure*)AS_BOUND_METHOD(printable)->method)->function->name->chars :
111  (AS_BOUND_METHOD(printable)->method->type == KRK_OBJ_NATIVE ? ((KrkNative*)AS_BOUND_METHOD(printable)->method)->name : "(unknown)")) : "(corrupt bound method)"); break;
112  default: fprintf(f, "<%s>", krk_typeName(printable)); break;
113  }
114  }
115 }
116 
117 
125 void krk_debug_dumpStack(FILE * file, KrkCallFrame * frame) {
126  size_t i = 0;
127  if (!frame) frame = &krk_currentThread.frames[krk_currentThread.frameCount-1];
128  for (KrkValue * slot = krk_currentThread.stack; slot < krk_currentThread.stackTop; slot++) {
129  fprintf(file, "[%c", frame->slots == i ? '*' : ' ');
130 
131  for (size_t x = krk_currentThread.frameCount; x > 0; x--) {
132  if (krk_currentThread.frames[x-1].slots > i) continue;
134  size_t relative = i - f->slots;
135 
136  /* Figure out the name of this value */
137  int found = 0;
138  for (size_t j = 0; j < f->closure->function->localNameCount; ++j) {
139  if (relative == f->closure->function->localNames[j].id
140  /* Only display this name if it's currently valid */
141  && f->closure->function->localNames[j].birthday <= (size_t)(f->ip - f->closure->function->chunk.code)
142  && f->closure->function->localNames[j].deathday >= (size_t)(f->ip - f->closure->function->chunk.code)
143  ) {
144  fprintf(file, "%s=", f->closure->function->localNames[j].name->chars);
145  found = 1;
146  break;
147  }
148  }
149  if (found) break;
150  }
151 
152  krk_printValueSafe(file, *slot);
153  fprintf(file, " ]");
154  i++;
155  }
156  if (i == frame->slots) {
157  fprintf(file, " * ");
158  }
159  fprintf(file, "\n");
160 }
161 
162 
163 void krk_disassembleCodeObject(FILE * f, KrkCodeObject * func, const char * name) {
164  KrkChunk * chunk = &func->chunk;
165  /* Function header */
166  fprintf(f, "<%s(", name);
167  int j = 0;
168  for (int i = 0; i < func->potentialPositionals; ++i) {
169  fprintf(f,"%s",func->localNames[j].name->chars);
170  if (j + 1 < func->totalArguments) fprintf(f,",");
171  j++;
172  }
173  if (func->obj.flags & KRK_OBJ_FLAGS_CODEOBJECT_COLLECTS_ARGS) {
174  fprintf(f,"*%s",func->localNames[j].name->chars);
175  if (j + 1 < func->totalArguments) fprintf(f,",");
176  j++;
177  }
178  for (int i = 0; i < func->keywordArgs; ++i) {
179  fprintf(f,"%s=",func->localNames[j].name->chars);
180  if (j + 1 < func->totalArguments) fprintf(f,",");
181  j++;
182  }
183  if (func->obj.flags & KRK_OBJ_FLAGS_CODEOBJECT_COLLECTS_KWS) {
184  fprintf(f,"**%s",func->localNames[j].name->chars);
185  }
186  fprintf(f, ") from %s>\n", chunk->filename->chars);
187  for (size_t offset = 0; offset < chunk->count;) {
188  offset = krk_disassembleInstruction(f, func, offset);
189  }
190 }
191 
192 static inline const char * opcodeClean(const char * opc) {
193  return &opc[3];
194 }
195 
196 static void _overlong_jump(KrkCodeObject * func, size_t offset, size_t operand) {
197  for (size_t i = 0; i < func->overlongJumpsCount; ++i) {
198  if (func->overlongJumps[i].instructionOffset == offset) {
199  operand |= (int)func->overlongJumps[i].intendedTarget << 16;
200  size_t target = offset + 2 + operand;
201  if (func->overlongJumps[i].originalOpcode == OP_LOOP ||
202  func->overlongJumps[i].originalOpcode == OP_LOOP_ITER) {
203  target = offset + 2 - operand;
204  }
205  krk_tableSet(AS_DICT(func->jumpTargets), INTEGER_VAL(target), BOOLEAN_VAL(1));
206  return;
207  }
208  }
209 }
210 
211 static int isJumpTarget(KrkCodeObject * func, size_t startPoint) {
212  KrkChunk * chunk = &func->chunk;
213  size_t offset = 0;
214 
215  if (IS_NONE(func->jumpTargets)) {
216  func->jumpTargets = krk_dict_of(0,NULL,0);
217 #define SIMPLE(opc) case opc: size = 1; break;
218 #define CONSTANT(opc,more) case opc: { size_t constant _unused = chunk->code[offset + 1]; size = 2; more; break; } \
219  case opc ## _LONG: { size_t constant _unused = (chunk->code[offset + 1] << 16) | \
220  (chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); size = 4; more; break; }
221 #define OPERANDB(opc,more) case opc: { size = 2; more; break; }
222 #define OPERAND(opc,more) OPERANDB(opc,more) \
223  case opc ## _LONG: { size = 4; more; break; }
224 #define JUMP(opc,sign) case opc: { uint16_t jump = (chunk->code[offset + 1] << 8) | (chunk->code[offset + 2]); \
225  krk_tableSet(AS_DICT(func->jumpTargets), INTEGER_VAL((size_t)(offset + 3 sign jump)), BOOLEAN_VAL(1)); \
226  size = 3; break; }
227 #define COMPLICATED(opc,more) case opc: size = 1; more; break;
228 #define OVERLONG_JUMP_MORE size += 2; _overlong_jump(func, offset+1, (chunk->code[offset + 1] << 8) | (chunk->code[offset + 2]))
229 #define CLOSURE_MORE \
230  KrkCodeObject * function = AS_codeobject(chunk->constants.values[constant]); \
231  for (size_t j = 0; j < function->upvalueCount; ++j) { \
232  int isLocal = chunk->code[offset++ + size]; \
233  offset++; \
234  if (isLocal & 2) { \
235  offset += 2; \
236  } \
237  }
238 #define EXPAND_ARGS_MORE
239 #define LOCAL_MORE
240 #define FORMAT_VALUE_MORE
241 
242  while (offset < chunk->count) {
243  uint8_t opcode = chunk->code[offset];
244  size_t size = 0;
245  switch (opcode) {
246 #include "opcodes.h"
247  }
248  offset += size;
249  }
250 #undef SIMPLE
251 #undef OPERANDB
252 #undef OPERAND
253 #undef CONSTANT
254 #undef JUMP
255 #undef COMPLICATED
256 #undef OVERLONG_JUMP_MORE
257 #undef CLOSURE_MORE
258 #undef LOCAL_MORE
259 #undef EXPAND_ARGS_MORE
260 #undef FORMAT_VALUE_MORE
261  }
262 
263  if (!IS_dict(func->jumpTargets)) return 0;
264  KrkValue garbage;
265  if (krk_tableGet(AS_DICT(func->jumpTargets), INTEGER_VAL(startPoint), &garbage)) return 1;
266  return 0;
267 }
268 
269 #define OPARGS FILE * f, const char * fullName, size_t * size, size_t * offset, KrkCodeObject * func, KrkChunk * chunk
270 #define OPARG_VALS f,fullName,size,offset,func,chunk
271 
272 static void _print_opcode(OPARGS) {
273  fprintf(f, "%-16s ", opcodeClean(fullName));
274 }
275 
276 static void _simple(OPARGS) {
277  _print_opcode(OPARG_VALS);
278  fprintf(f, " ");
279  *size = 1;
280 }
281 
282 static void _constant(OPARGS, int isLong, void (*more)(OPARGS, size_t constant)) {
283  _print_opcode(OPARG_VALS);
284  size_t constant = isLong ? (chunk->code[*offset + 1] << 16) | (chunk->code[*offset + 2] << 8) | (chunk->code[*offset + 3]) : chunk->code[*offset + 1];
285  fprintf(f, "%4d ", (int)constant);
286  krk_printValueSafe(f, chunk->constants.values[constant]);
287  *size = isLong ? 4 : 2;
288  if (more) more(OPARG_VALS, constant);
289 }
290 
291 static void _operand(OPARGS, int isLong, void (*more)(OPARGS, size_t constant)) {
292  _print_opcode(OPARG_VALS);
293  uint32_t operand = isLong ? (chunk->code[*offset + 1] << 16) | (chunk->code[*offset + 2] << 8) | (chunk->code[*offset + 3]) : chunk->code[*offset + 1];
294  fprintf(f, "%4d", (int)operand);
295  *size = isLong ? 4 : 2;
296  if (more) more(OPARG_VALS, operand);
297 }
298 
299 static void _jump(OPARGS, int sign) {
300  _print_opcode(OPARG_VALS);
301  uint16_t jump = (chunk->code[*offset + 1] << 8) | (chunk->code[*offset + 2]);
302  fprintf(f, "%4d (to %d)", (int)jump, (int)(*offset + 3 + sign * jump));
303  *size = 3;
304 }
305 
306 static void _complicated(OPARGS, void (*more)(OPARGS)) {
307  _print_opcode(OPARG_VALS);
308  if (more) more(OPARG_VALS);
309  else *size = 1;
310 }
311 
312 #define SIMPLE(opc)
313 #define JUMP(opc,sign) case opc: fprintf(f, "(%s, to %zu)", opcodeClean(#opc), *offset + 3 sign current_jump); return;
314 #define OPERAND(opc,more)
315 #define CONSTANT(opc,more)
316 #define COMPLICATED(opc,more)
317 static void _overlong_jump_more(OPARGS) {
318  size_t current_jump = (chunk->code[*offset + 1] << 8) | (chunk->code[*offset + 2]);
319  *size = 3;
320 
321  /* Now look it up */
322  for (size_t i = 0; i < func->overlongJumpsCount; ++i) {
323  if (*offset + 1 == (size_t)func->overlongJumps[i].instructionOffset) {
324  current_jump |= ((size_t)func->overlongJumps[i].intendedTarget << 16);
325  switch (func->overlongJumps[i].originalOpcode) {
326 #include "opcodes.h"
327  default: break;
328  }
329  }
330  }
331 
332  fprintf(f,"(invalid destination)");
333 }
334 #undef SIMPLE
335 #undef OPERAND
336 #undef CONSTANT
337 #undef JUMP
338 #undef COMPLICATED
339 
340 
341 #undef NOOP
342 #define NOOP (NULL)
343 #define SIMPLE(opc) case opc: _simple(f,#opc,&size,&offset,func,chunk); break;
344 #define CONSTANT(opc,more) case opc: _constant(f,#opc,&size,&offset,func,chunk,0,more); break; \
345  case opc ## _LONG: _constant(f,#opc "_LONG",&size,&offset,func,chunk,1,more); break;
346 #define OPERAND(opc,more) case opc: _operand(f,#opc,&size,&offset,func,chunk,0,more); break; \
347  case opc ## _LONG: _operand(f,#opc "_LONG",&size,&offset,func,chunk,1,more); break;
348 #define JUMP(opc,sign) case opc: _jump(f,#opc,&size,&offset,func,chunk,sign 1); break;
349 #define COMPLICATED(opc,more) case opc: _complicated(f,#opc,&size,&offset,func,chunk,more); break;
350 
351 #define OVERLONG_JUMP_MORE _overlong_jump_more
352 
353 #define CLOSURE_MORE _closure_more
354 
355 static void _closure_more(OPARGS, size_t constant) {
356  KrkCodeObject * function = AS_codeobject(chunk->constants.values[constant]);
357  fprintf(f, " ");
358  for (size_t j = 0; j < function->upvalueCount; ++j) {
359  int isLocal = chunk->code[(*offset)++ + *size];
360  int index = chunk->code[(*offset)++ + *size];
361  if (isLocal & 2) {
362  index = (index << 16) | (chunk->code[*offset + *size] << 8) | chunk->code[*offset + 1 + *size];
363  offset += 2;
364  }
365  if (isLocal & 1) {
366  for (size_t i = 0; i < func->localNameCount; ++i) {
367  if (func->localNames[i].id == (size_t)index && func->localNames[i].birthday <= *offset && func->localNames[i].deathday >= *offset) {
368  fprintf(f, "%s", func->localNames[i].name->chars);
369  break;
370  }
371  }
372  } else if (isLocal & 4) {
373  fprintf(f, "classcell");
374  } else { fprintf(f, "upvalue<%d>", index); }
375  if (j + 1 != function->upvalueCount) fprintf(f, ", ");
376  }
377 }
378 
379 #define EXPAND_ARGS_MORE _expand_args_more
380 
381 static void _expand_args_more(OPARGS, size_t operand) {
382  fprintf(f, " (%s)", operand == 0 ? "singleton" : (operand == 1 ? "list" : "dict"));
383 }
384 
385 #define FORMAT_VALUE_MORE _format_value_more
386 
387 static void _format_value_more(OPARGS, size_t operand) {
388  if (operand != 0) {
389  int hasThing = 0;
390  fprintf(f, " (");
391  if (operand & FORMAT_OP_EQ) { fprintf(f, "eq"); hasThing = 1; }
392  if (operand & FORMAT_OP_STR) { fprintf(f, "%sstr", hasThing ? ", " : ""); hasThing = 1; }
393  if (operand & FORMAT_OP_REPR) { fprintf(f, "%srepr", hasThing ? ", " : ""); hasThing = 1; }
394  if (operand & FORMAT_OP_FORMAT) { fprintf(f, "%swith format", hasThing ? ", " : ""); }
395  fprintf(f, ")");
396  }
397 }
398 
399 #define LOCAL_MORE _local_more
400 static void _local_more(OPARGS, size_t operand) {
401  for (size_t i = 0; i < func->localNameCount; ++i) {
402  if (func->localNames[i].id == operand && func->localNames[i].birthday <= *offset && func->localNames[i].deathday >= *offset) {
403  fprintf(f, " (%s", func->localNames[i].name->chars);
404  if ((short int) operand < func->potentialPositionals) {
405  fprintf(f, ", arg");
406  } else if ((short int)operand < func->potentialPositionals + func->keywordArgs + !!(func->obj.flags & KRK_OBJ_FLAGS_CODEOBJECT_COLLECTS_ARGS)) {
407  fprintf(f, ", kwarg");
408  }
409  fprintf(f, ")");
410  break;
411  }
412  }
413 }
414 
415 size_t krk_disassembleInstruction(FILE * f, KrkCodeObject * func, size_t offset) {
416  KrkChunk * chunk = &func->chunk;
417  if (offset > 0 && krk_lineNumber(chunk, offset) == krk_lineNumber(chunk, offset - 1)) {
418  fprintf(f, " ");
419  } else {
420  if (offset > 0) fprintf(f,"\n");
421  fprintf(f, "%4d ", (int)krk_lineNumber(chunk, offset));
422  }
423  if (isJumpTarget(func,offset)) {
424  fprintf(f, " >> ");
425  } else {
426  fprintf(f, " ");
427  }
428  fprintf(f, "%4u ", (unsigned int)offset);
429  uint8_t opcode = chunk->code[offset];
430  size_t size = 1;
431 
432  switch (opcode) {
433 #include "opcodes.h"
434  default:
435  fprintf(f, "Unknown opcode: %02x", opcode);
436  }
437 
438  /* Birthdays - Local names that have become valid from this instruction */
439  for (size_t i = 0; i < func->localNameCount; ++i) {
440  if (func->localNames[i].birthday >= offset && func->localNames[i].birthday < offset + size) {
441  fprintf(f, " +%s", func->localNames[i].name->chars);
442  }
443  }
444 
445  /* Deathdays - Local names that are no longer valid as of this instruction */
446  for (size_t i = 0; i < func->localNameCount; ++i) {
447  if (func->localNames[i].deathday >= offset && func->localNames[i].deathday < offset + size) {
448  fprintf(f, " -%s", func->localNames[i].name->chars);
449  }
450  }
451 
452  fprintf(f,"\n");
453 
454  return offset + size;
455 }
456 #undef SIMPLE
457 #undef OPERANDB
458 #undef OPERAND
459 #undef CONSTANT
460 #undef JUMP
461 #undef COMPLICATED
462 #undef OVERLONG_JUMP_MORE
463 #undef CLOSURE_MORE
464 #undef LOCAL_MORE
465 #undef EXPAND_ARGS_MORE
466 #undef FORMAT_VALUE_MORE
467 #undef NOOP
468 
469 int krk_debug_addBreakpointCodeOffset(KrkCodeObject * target, size_t offset, int flags) {
470  int index = vm.dbgState->breakpointsCount;
471  if (vm.dbgState->breakpointsCount == MAX_BREAKPOINTS) {
472  /* See if any are available */
473  for (int i = 0; i < MAX_BREAKPOINTS; ++i) {
474  if (vm.dbgState->breakpoints[i].inFunction == NULL) {
475  index = i;
476  break;
477  }
478  }
479  if (index == vm.dbgState->breakpointsCount) {
480  return -1;
481  }
482  } else {
483  index = vm.dbgState->breakpointsCount++;
484  }
485 
486  vm.dbgState->breakpoints[index].inFunction = target;
487  vm.dbgState->breakpoints[index].offset = offset;
488  vm.dbgState->breakpoints[index].originalOpcode = target->chunk.code[offset];
489  vm.dbgState->breakpoints[index].flags = flags;
490  target->chunk.code[offset] = OP_BREAKPOINT;
491 
492  return index;
493 }
494 
495 int krk_debug_addBreakpointFileLine(KrkString * filename, size_t line, int flags) {
496 
497  KrkCodeObject * target = NULL;
498 
499  /* Examine all code objects to find one that matches the requested
500  * filename and line number... */
501  KrkObj * object = vm.objects;
502  while (object) {
503  if (object->type == KRK_OBJ_CODEOBJECT) {
504  KrkChunk * chunk = &((KrkCodeObject*)object)->chunk;
505  if (filename == chunk->filename) {
506  /* We have a candidate. */
507  if (krk_lineNumber(chunk, 0) <= line &&
508  krk_lineNumber(chunk,chunk->count) >= line) {
509  target = (KrkCodeObject*)object;
510  break;
511  }
512  }
513  }
514  object = object->next;
515  }
516 
517  /* No matching function was found... */
518  if (!target) return -1;
519 
520  /* Find the right offset in this function */
521 
522  size_t offset = 0;
523  for (size_t i = 0; i < target->chunk.linesCount; ++i) {
524  if (target->chunk.lines[i].line > line) break;
525  if (target->chunk.lines[i].line == line) {
526  offset = target->chunk.lines[i].startOffset;
527  break;
528  }
529  offset = target->chunk.lines[i].startOffset;
530  }
531 
532  return krk_debug_addBreakpointCodeOffset(target, offset, flags);
533 }
534 
535 int krk_debug_enableBreakpoint(int breakIndex) {
536  if (breakIndex < 0 || breakIndex >= vm.dbgState->breakpointsCount || vm.dbgState->breakpoints[breakIndex].inFunction == NULL)
537  return 1;
538  vm.dbgState->breakpoints[breakIndex].inFunction->chunk.code[vm.dbgState->breakpoints[breakIndex].offset] = OP_BREAKPOINT;
539  return 0;
540 }
541 
542 int krk_debug_disableBreakpoint(int breakIndex) {
543  if (breakIndex < 0 || breakIndex >= vm.dbgState->breakpointsCount || vm.dbgState->breakpoints[breakIndex].inFunction == NULL)
544  return 1;
545  vm.dbgState->breakpoints[breakIndex].inFunction->chunk.code[vm.dbgState->breakpoints[breakIndex].offset] =
546  vm.dbgState->breakpoints[breakIndex].originalOpcode;
547  if (breakIndex == vm.dbgState->repeatStack_top) {
548  vm.dbgState->repeatStack_top = -1;
549  }
550  return 0;
551 }
552 
553 int krk_debug_removeBreakpoint(int breakIndex) {
554  if (breakIndex < 0 || breakIndex >= vm.dbgState->breakpointsCount || vm.dbgState->breakpoints[breakIndex].inFunction == NULL)
555  return 1;
556  krk_debug_disableBreakpoint(breakIndex);
557  vm.dbgState->breakpoints[breakIndex].inFunction = NULL;
558  while (vm.dbgState->breakpointsCount && vm.dbgState->breakpoints[vm.dbgState->breakpointsCount-1].inFunction == NULL) {
559  vm.dbgState->breakpointsCount--;
560  }
561  return 0;
562 }
563 /*
564  * Begin debugger utility functions.
565  *
566  * These functions are exported for use in debuggers that
567  * are registered with krk_registerDebugger(...);
568  */
569 
578  int flagsBefore = krk_currentThread.flags;
581 
582  krk_runtimeError(vm.exceptions->baseException, "(breakpoint)");
584 
586  krk_currentThread.flags = flagsBefore;
587 }
588 
590  krk_currentThread.flags |= KRK_THREAD_SINGLE_STEP;
591 }
592 
594  krk_currentThread.flags &= ~(KRK_THREAD_SINGLE_STEP);
595 }
596 
598  if (!vm.dbgState->debuggerHook)
599  abort();
600 
601  if (vm.dbgState->repeatStack_top != -1) {
602  /* Re-enable stored repeat breakpoint */
603  krk_debug_enableBreakpoint(vm.dbgState->repeatStack_top);
604  }
605 
606  vm.dbgState->repeatStack_top = vm.dbgState->repeatStack_bottom;
607  vm.dbgState->repeatStack_bottom = -1;
608 
609  if (!vm.dbgState->thisWasForced) {
610  int result = vm.dbgState->debuggerHook(frame);
611  switch (result) {
612  case KRK_DEBUGGER_CONTINUE:
614  break;
615  case KRK_DEBUGGER_ABORT:
616  abort();
617  break;
618  case KRK_DEBUGGER_STEP:
620  break;
621  case KRK_DEBUGGER_QUIT:
622  exit(0);
623  break;
624  case KRK_DEBUGGER_RAISE:
625  krk_runtimeError(vm.exceptions->baseException, "raise from debugger");
626  break;
627  }
628  } else {
629  /* If we weren't asked to step to the next breakpoint, we need to disable single stepping. */
631  vm.dbgState->thisWasForced = 0;
632  }
633 
634  /* If the top of the repeat stack is an index, we need to ensure we re-enable that breakpoint */
635  if (vm.dbgState->repeatStack_top != -1 && !(krk_currentThread.flags & KRK_THREAD_SINGLE_STEP)) {
636  vm.dbgState->thisWasForced = 1;
638  }
639 
640  return 0;
641 }
642 
644  if (vm.dbgState->debuggerHook) return 1;
645  vm.dbgState->debuggerHook = hook;
646  return 0;
647 }
648 
649 int krk_debug_examineBreakpoint(int breakIndex, KrkCodeObject ** funcOut, size_t * offsetOut, int * flagsOut, int * enabled) {
650  if (breakIndex < 0 || breakIndex >= vm.dbgState->breakpointsCount)
651  return -1;
652  if (vm.dbgState->breakpoints[breakIndex].inFunction == NULL)
653  return -2;
654 
655  if (funcOut) *funcOut = vm.dbgState->breakpoints[breakIndex].inFunction;
656  if (offsetOut) *offsetOut = vm.dbgState->breakpoints[breakIndex].offset;
657  if (flagsOut) *flagsOut = vm.dbgState->breakpoints[breakIndex].flags;
658  if (enabled) *enabled = (vm.dbgState->breakpoints[breakIndex].inFunction->chunk.code[vm.dbgState->breakpoints[breakIndex].offset] == OP_BREAKPOINT) || breakIndex == vm.dbgState->repeatStack_top;
659 
660  return 0;
661 }
662 
664  int index = -1;
665 
667  KrkCodeObject * callee = frame->closure->function;
668  size_t offset = (frame->ip - 1) - callee->chunk.code;
669 
670  for (int i = 0; i < vm.dbgState->breakpointsCount; ++i) {
671  if (vm.dbgState->breakpoints[i].inFunction == callee && vm.dbgState->breakpoints[i].offset == offset) {
672  index = i;
673  }
674  }
675 
676  /* A breakpoint instruction without an associated breakpoint entry?
677  * This must be an invalid block of bytecode - just bail! */
678  if (index == -1) {
679  abort();
680  }
681 
682  /* Restore the instruction to its original state. If the debugger
683  * wants to break here again it can set the breakpoint again */
684  callee->chunk.code[offset] = vm.dbgState->breakpoints[index].originalOpcode;
685 
686  /* If this was a single-shot, we can remove it. */
687  if (vm.dbgState->breakpoints[index].flags == KRK_BREAKPOINT_ONCE) {
689  } else if (vm.dbgState->breakpoints[index].flags == KRK_BREAKPOINT_REPEAT) {
690  vm.dbgState->repeatStack_bottom = index;
691  }
692 
693  /* Rewind to rerun this instruction. */
694  frame->ip--;
695 
696  return krk_debuggerHook(frame);
697 }
698 
699 void krk_debug_init(void) {
700  vm.dbgState = calloc(1, sizeof(struct DebuggerState));
701  vm.dbgState->repeatStack_top = -1;
702  vm.dbgState->repeatStack_bottom = -1;
703 }
704 
705 void krk_debug_addExpression(KrkCodeObject * codeobject, uint8_t start, uint8_t midStart, uint8_t midEnd, uint8_t end) {
706  /* Traceback entries point to the last byte of an opcode, due to the way instruction fetch
707  * advances the instruction pointer past all of the constituent operands of an opcode; as
708  * such, our map is based on these last bytes and we need to look at the byte preceding
709  * the current count when adding new entries. */
710  size_t offset = codeobject->chunk.count - 1;
711 
712  /* We can feasibly support offsets larger than UINT32_MAX on 64-bit platforms, though this
713  * should never really happen. Just in case, avoid messing up our table with bad values. */
714  if (offset > UINT32_MAX) return;
715 
716  if (codeobject->expressionsCapacity < codeobject->expressionsCount + 1) {
717  size_t old = codeobject->expressionsCapacity;
718  codeobject->expressionsCapacity = KRK_GROW_CAPACITY(old);
719  codeobject->expressions = KRK_GROW_ARRAY(KrkExpressionsMap, codeobject->expressions, old, codeobject->expressionsCapacity);
720  }
721 
722  codeobject->expressions[codeobject->expressionsCount] = (KrkExpressionsMap){offset,start,midStart,midEnd,end};
723  codeobject->expressionsCount++;
724 }
725 
726 int krk_debug_expressionUnderline(const KrkCodeObject* codeobject, uint8_t* start, uint8_t* midStart, uint8_t* midEnd, uint8_t* end, size_t instruction) {
727  /* We could do binary search here, but as we only print these when an exception 'escapes',
728  * it's not really worth the optimization over a linear search per line in the traceback. */
729  for (size_t i = 0; i < codeobject->expressionsCount; ++i) {
730  if (codeobject->expressions[i].bytecodeOffset == instruction) {
731  *start = codeobject->expressions[i].start;
732  *midStart = codeobject->expressions[i].midStart;
733  *midEnd = codeobject->expressions[i].midEnd;
734  *end = codeobject->expressions[i].end;
735  return 1;
736  }
737  }
738  return 0;
739 }
740 
741 #endif
Exported methods for the source compiler.
Functions for debugging bytecode execution.
#define KRK_BREAKPOINT_ONCE
Definition: debug.h:232
int krk_debug_addBreakpointCodeOffset(KrkCodeObject *codeObject, size_t offset, int flags)
Add a breakpoint to the given code object.
Definition: debug.c:469
void krk_debug_disableSingleStep(void)
Disable single stepping in the current thread.
Definition: debug.c:593
int krk_debuggerHook(KrkCallFrame *frame)
Called by the VM on single step.
Definition: debug.c:597
int krk_debug_examineBreakpoint(int breakIndex, KrkCodeObject **funcOut, size_t *offsetOut, int *flagsOut, int *enabledOut)
Retreive information on a breakpoint.
Definition: debug.c:649
void krk_debug_init(void)
Initialize debugger state. Call exactly once per VM.
Definition: debug.c:699
int(* KrkDebugCallback)(KrkCallFrame *frame)
Function pointer for a debugger hook. krk_debug_registerCallback()
Definition: debug.h:73
int krk_debug_enableBreakpoint(int breakpointId)
Enable a breakpoint.
Definition: debug.c:535
int krk_debugBreakpointHandler(void)
Called by the VM when a breakpoint is encountered.
Definition: debug.c:663
void krk_debug_dumpTraceback(void)
Safely dump a traceback to stderr.
Definition: debug.c:577
void krk_debug_enableSingleStep(void)
Enable single stepping in the current thread.
Definition: debug.c:589
int krk_debug_removeBreakpoint(int breakpointId)
Remove a breakpoint from the breakpoint table.
Definition: debug.c:553
int krk_debug_registerCallback(KrkDebugCallback hook)
Register a debugger callback.
Definition: debug.c:643
int krk_debug_disableBreakpoint(int breakpointId)
Disable a breakpoint.
Definition: debug.c:542
size_t krk_disassembleInstruction(FILE *f, KrkCodeObject *func, size_t offset)
Print a disassembly of a single opcode instruction.
Definition: debug.c:415
int krk_debug_addBreakpointFileLine(KrkString *filename, size_t line, int flags)
Add a breakpoint to the given line of a file.
Definition: debug.c:495
#define KRK_BREAKPOINT_REPEAT
Definition: debug.h:233
void krk_disassembleCodeObject(FILE *f, KrkCodeObject *func, const char *name)
Print a disassembly of 'func' to the stream 'f'.
Definition: debug.c:163
void krk_debug_dumpStack(FILE *f, KrkCallFrame *frame)
Print the elements on the stack.
Definition: debug.c:125
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:460
void krk_dumpTraceback(void)
If there is an active exception, print a traceback to stderr.
Definition: exceptions.c:366
Internal header.
Represents a managed call state in a VM thread.
Definition: vm.h:44
size_t slots
Definition: vm.h:47
KrkClosure * closure
Definition: vm.h:45
uint8_t * ip
Definition: vm.h:46
Opcode chunk of a code object.
Definition: chunk.h:36
size_t krk_lineNumber(KrkChunk *chunk, size_t offset)
Obtain the line number for a byte offset into a bytecode chunk.
Definition: chunk.c:75
Function object.
Definition: object.h:195
KrkCodeObject * function
The codeobject containing the bytecode run when this function is called.
Definition: object.h:197
Code object.
Definition: object.h:163
unsigned short potentialPositionals
Precalculated positional arguments for complex argument processing.
Definition: object.h:167
size_t overlongJumpsCount
Number of entries in pessimal jump table.
Definition: object.h:185
KrkChunk chunk
Bytecode data.
Definition: object.h:170
KrkValue jumpTargets
Possibly a set of jump targets...
Definition: object.h:182
void krk_debug_addExpression(KrkCodeObject *codeobject, uint8_t start, uint8_t midStart, uint8_t midEnd, uint8_t end)
Add an expression mapping to the bytecode chunk.
Definition: debug.c:705
KrkLocalEntry * localNames
Stores the names of local variables used in the function, for debugging.
Definition: object.h:177
unsigned short keywordArgs
Arity of keyword (default) arguments.
Definition: object.h:166
KrkOverlongJump * overlongJumps
Pessimal overlong jump container.
Definition: object.h:183
KrkExpressionsMap * expressions
Mapping of bytecode offsets to expression spans for debugging.
Definition: object.h:181
KrkObj obj
Base.
Definition: object.h:164
size_t expressionsCapacity
Capacity of expressions.
Definition: object.h:179
int krk_debug_expressionUnderline(const KrkCodeObject *codeobject, uint8_t *start, uint8_t *midStart, uint8_t *endStart, uint8_t *end, size_t instruction)
Extract expression mapping from chunk.
Definition: debug.c:726
size_t expressionsCount
Number of entries in expressions.
Definition: object.h:180
size_t localNameCount
Number of entries in localNames.
Definition: object.h:176
unsigned short totalArguments
Total argument cells we can fill in complex argument processing.
Definition: object.h:168
KrkValue krk_dict_of(int argc, const KrkValue argv[], int hasKw)
Create a dict object.
Definition: obj_dict.c:19
Map entry of opcode offsets to expressions spans.
Definition: object.h:141
size_t birthday
Instruction offset that this local name became valid on.
Definition: object.h:131
KrkString * name
Name of the local.
Definition: object.h:133
size_t id
Local ID as used by opcodes; offset from the frame's stack base.
Definition: object.h:130
size_t deathday
Instruction offset that this local name becomes invalid on.
Definition: object.h:132
Managed binding to a C function.
Definition: object.h:309
The most basic object type.
Definition: object.h:41
uint16_t flags
General object flags, mostly related to garbage collection.
Definition: object.h:43
uint16_t type
Tag indicating core type.
Definition: object.h:42
struct KrkObj * next
Invasive linked list of all objects in the VM.
Definition: object.h:45
uint16_t intendedTarget
High bytes of the intended target.
Definition: object.h:153
uint8_t originalOpcode
Original jump opcode to execute.
Definition: object.h:154
uint32_t instructionOffset
Instruction (operand offset) this jump target applies to.
Definition: object.h:152
Immutable sequence of Unicode codepoints.
Definition: object.h:93
char * chars
UTF8 canonical data.
Definition: object.h:97
int krk_tableGet(KrkTable *table, KrkValue key, KrkValue *value)
Obtain the value associated with a key in a table.
Definition: table.c:211
int krk_tableSet(KrkTable *table, KrkValue key, KrkValue value)
Assign a value to a key in a table.
Definition: table.c:148
KrkValue currentException
Definition: vm.h:164
size_t frameCount
Definition: vm.h:156
KrkValue * stack
Definition: vm.h:158
KrkValue * stackTop
Definition: vm.h:159
KrkCallFrame * frames
Definition: vm.h:155
int flags
Definition: vm.h:165
KrkValue * values
Definition: value.h:78
Stack reference or primative value.
const char * krk_typeName(KrkValue value)
Get the name of the type of a value.
Definition: vm.c:984
void krk_printValueSafe(FILE *f, KrkValue value)
Print a value without calling the VM.
Definition: debug.c:21
Utilities for creating native bindings.
Core API for the bytecode virtual machine.
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:131
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118