Debug Library
This library provides an interface to internal debugging information and hooks, as well as some access to internal data structures.
This is a pretty unsafe library. It allows you to peek into a lot of internal script state and change values that shouldn't be changed under normal circumstances. You might even be able to make the host program to crash by messing with internal state of native functions. Be careful.
Many of these functions operate either implicitly or explicitly on a thread. Those that operate on a thread take an optional first parameter which is the thread object to use. If this parameter is elided, the current thread (the one that called the function) will be used.
Members
- The Call Stack
- `debug.setHook([t: thread,] func: null|function, mask: string = "", …
- debug.getHook([t: thread])
- debug.callDepth([t: thread])
- debug.sourceName([t: thread,] func: int|function)
- debug.sourceLine([t: thread,] func: int|function)
- debug.getFunc([t: thread,] func: int)
- debug.numLocals([t: thread,] callIndex: int)
- debug.localName([t: thread,] callIndex: int, localIndex: int)
- `debug.getLocal([t: thread,] callIndex: int, localIndex: …
- `debug.setLocal([t: thread,] callIndex: int, localIndex: int|string, …
- debug.numUpvals([t: thread,] func: int|function)
- debug.upvalName([t: thread,] func: int|function, upval: int)
- debug.getUpval([t: thread,] func: int|function, upval: int|string)
- `debug.setUpval([t: thread,] func: int|function, upval: int|string, …
- debug.currentLine([t: thread,] callIndex: int)
- debug.lineInfo([t: thread,] func: int|function)
- debug.getMetatable(typeName: string)
- debug.setMetatable(typeName: string, mt: null|namespace)
- debug.getRegistry()
- debug.getExtraBytes(inst: instance)
- debug.setExtraBytes(inst: instance, data: Vector)
- debug.numExtraFields(inst: instance)
- debug.getExtraField(inst: instance, idx: int)
- debug.setExtraField(inst: instance, idx: int, value)
The Call Stack
When a hook function is called, there will be at least one function on the thread's call stack, which is the call to the hook function itself. The call stack is accessed by many debug functions through an integer index. This index is in the range 0 <= index < debug.callDepth(t). 0 means the currently-executing function; 1 means the function that called that; and so on. Giving an invalid call stack index will cause an error to be thrown.
Some call stack entries do not have a function associated with them. Some of these are "special" stack frames which are used to implement coroutine yields and resumes. Others are just tail call entries which were overwritten.
There are also many functions which operate on either a call stack index or on a plain function object. In that case, the function's parameter will take either an int or a function.
debug.setHook([t: thread,] func: null|function, mask: string = "", delay: int = 0)
Sets or removes the hook function for the given thread (the current thread if the t parameter is skipped).
You can use the hook function to hook into various points in the execution of MiniD scripts. You can read more about them in the minid.interpreter docs, in the setHookFunc documentation.
This works almost exactly like the native setHookFunc API. The mask parameter differs in that it's a string rather than a bitflag. The mask string may contain the following letters: 'c' means "call hooks"; 'r' means "return hooks"; and 'l' means "line hooks." So a mask like "lc" would mean "hook on lines and calls." There are no letters for delay or tail-return hooks.
Just like the setHookFunc API, if the delay parameter is 0, delay hooks will be disabled.
Finally if you either pass 'null' for the func param or the empty string for the mask, the hook function will be removed from the given thread.
Note that if you turn on return hooks in the thread that called debug.setHook, the return hook will be triggered upon the return from debug.setHook.
debug.getHook([t: thread])
Gets the hook information for the given thread (the current thread if the t parameter is skipped). The information is returned as three values: the hook function, the hook mask (as a string like the one passed to debug.setHook), and the hook delay. If hooks are disabled for the given thread, the return values will be 'null', "" (the empty string), and 0.
debug.callDepth([t: thread])
Gets the call depth for the given thread (the current thread if the t parameter is skipped), including tailcalls. If the calling thread's call depth is being queried, the call to debug.callDepth is not included.
debug.sourceName([t: thread,] func: int|function)
Gets the name of the source code file from which the given function was compiled. The func parameter is either a call stack index or a function object. If the function was not compiled from a source file (i.e. it was compiled from a string), it will be something like "<loaded by eval>". If the function is a native function, or if there is no function at the given call stack index, the name will be "" (the empty string).
debug.sourceLine([t: thread,] func: int|function)
Gets the line on which the given function was declared in its source code. The func parameter is either a call stack index or a function object. If the function is a native function, or if there is no function at the given call stack index, the line will be 0.
debug.getFunc([t: thread,] func: int)
Gets the function object associated with the given call stack index. If there is no function associated with the given index, returns 'null'.
debug.numLocals([t: thread,] callIndex: int)
Get the number of locals visible at the current program counter at the given call stack index. If the given index has no function associated with it, or if the function is native, returns 0. Once you get the number of locals, you can get their names and get and set their values.
debug.localName([t: thread,] callIndex: int, localIndex: int)
Gets the name of the local variable at the given local index in the given call stack index. The localIndex must be in the range 0 <= localIndex < debug.numLocals(t, callIndex). Local variables that begin with two underscores (like "__dummy0") are hidden variables generated by the compiler and used by the interpreter and should most likely not be modified.
This function throws an error if the given call index has no function or is a native function.
debug.getLocal([t: thread,] callIndex: int, localIndex: int|string)
Gets the value of the local variable at the given local index in the given call stack index. The localIndex can be an integer like for debug.localName, or it can be the name of the local as a string. If there is no local with that name, an error will be thrown.
This function throws an error if the given call index has no function or is a native function.
debug.setLocal([t: thread,] callIndex: int, localIndex: int|string, value)
Sets the value of the local variable at the given local index in the given call stack index. The localIndex can be an integer like for debug.localName, or it can be the name of the local as a string. If there is no local with that name, an error will be thrown.
This function throws an error if the given call index has no function or is a native function.
debug.numUpvals([t: thread,] func: int|function)
Gets the number of upvalues associated with the function. Can be a script or a native function. If there is no function at the given call stack index, returns 0.
debug.upvalName([t: thread,] func: int|function, upval: int)
Gets the name of the upvalue at the given index in the given function. The upval parameter must be in the range 0 <= upval <= debug.numUpvals(t, func). This function returns "" (the empty string) for all upvalues of native functions.
This function throws an error if the given call index has no function.
debug.getUpval([t: thread,] func: int|function, upval: int|string)
Gets the value of the upvalue at the given index in the given function. The upval parameter can be an integer in the range 0 <= upval <= debug.numUpvals(t, func), or it can be the name of the upvalue as a string. If there is no upvalue with that name, an error will be thrown. Attempting to get an upvalue by name from a native function will always throw an error.
This function throws an error if the given call index has no function.
debug.setUpval([t: thread,] func: int|function, upval: int|string, value)
Sets the value of the upvalue at the given index in the given function. The upval parameter can be an integer in the range 0 <= upval <= debug.numUpvals(t, func), or it can be the name of the upvalue as a string. If there is no upvalue with that name, an error will be thrown. Attempting to set an upvalue by name in a native function will always throw an error.
This function throws an error if the given call index has no function.
debug.currentLine([t: thread,] callIndex: int)
Gets the current line of execution in the function at the given call depth. The callIndex must be in the range 0 <= callIndex < debug.callDepth(t). If this function is called in the thread that it is looking at, the call to debug.currentLine will not be visible (that is, a callIndex of 0 will get the line of the function that called debug.currentLine). If there is no function associated with the given call index, or if the function is native, returns 0.
debug.lineInfo([t: thread,] func: int|function)
Returns an array, in sorted order, of all lines of source code of a function which are mapped to at least one MiniD instruction. If there is no function at the given call stack index, or if the function is native, returns a zero-length array.
debug.getMetatable(typeName: string)
Gets the global type metatable for the given type. The typeName must be one of "null", "bool", "int", "float", "char", "string", "table", "array", "function", "class", "instance", "namespace", "thread", "nativeobj" or "weakref". Returns a namespace if the given type has a metatable, and 'null' if not.
debug.setMetatable(typeName: string, mt: null|namespace)
Sets the global type metatable for the given type. The typeName must be one of "null", "bool", "int", "float", "char", "string", "table", "array", "function", "class", "instance", "namespace", "thread", "nativeobj" or "weakref". If the mt parameter is 'null', removes the metatable for that type.
debug.getRegistry()
Gets the registry namespace associated with this thread's VM. This is a namespace that is normally hidden from MiniD code and is sort of a secret global namespace for native code. You can't replace the registry namespace, but you can modify it.
debug.getExtraBytes(inst: instance)
Gets a Vector(u8) representing the extra bytes associated with a given class instance. These extra bytes are associated with classes created by native code and can hold arbitrary data. The returned Vector instance does not point directly at the instance's data; you must use debug.setExtraBytes to actually modify the data held in the instance.
debug.setExtraBytes(inst: instance, data: Vector)
Copies the data out of any Vector object into the extra bytes associated with a given class instance. The given Vector can be of any type, but its size in bytes (that is, its length times its item size) must exactly match the number of extra bytes associated with the instance, or else an error will be thrown.
debug.numExtraFields(inst: instance)
Returns how many extra fields are associated with a given class instance. These are hidden fields that can be associated with instances by native code and work like a fixed-size array of MiniD values.
debug.getExtraField(inst: instance, idx: int)
Gets the extra field at the given index out of the given class instance.
debug.setExtraField(inst: instance, idx: int, value)
Sets the extra field at the given index in the given class instance to the given value.
