This page covers how to find bugs in your apps.
๐ Cheat codes #
You can add cheat codes to your apps. Cheat codes can be used to put the app into a specific state: jump to a specific level, reset NPCs’ positions, set character health, etc.
First, add [cheats]
table into firefly.toml
. The table maps cheat code names to numbers that get passed into the app:
[cheats]
get-health = 1
set-health = 2
Next, define the cheat
callback in the app:
#[no_mangle]
extern fn cheat(cmd: i32, val: i32) -> i32 {
match cmd {
1 => get_health(),
2 => {
set_health(val);
1
},
_ => 0,
}
}
package main
import "github.com/firefly-zero/firefly-go/firefly"
func init() {
firefly.Cheat = cheat
}
func cheat(cmd, val int) int {
switch cmd {
case 1:
return getHealth()
case 2:
setHealth(val)
return 1
default:
return 0
}
}
pub export fn cheat(cmd: i32, val: i32) i32 {
switch (cmd) {
1 => {
return GetHealth();
},
2 => {
setHealth(val);
return 1;
},
else => {
return 0;
}
}
}
Now, you can send cheat codes into a running app using the CLI:
firefly_cli set-health 56 # prints 1
firefly_cli get-health 0 # prints 56
๐ Decompilation #
WebAssembly specification describes 2 file formats: a binary format that Firefly Zero uses and a text format that is human-readable. You can convert the binary format into the text format using the wasm2wat tool from wabt.
Installing wabt
On Linux, wabt can be installed by running
sudo apt install -y wabt
. For other systems, check out Github Releases.Having trouble installing wabt? You can use the wasm2wat online demo instead.
For example, here is how to view the text representation of the default launcher’s binary:
wasm2wat --generate-names --fold-exprs \
$(firefly_cli vfs)/roms/sys/launcher/_bin \
| less
When is it useful?
Looking into the wasm2wat output is useful only for compiled languages and for small binaries. For interpreted languages, like Python, you’ll see the code of the interpreter instead of the code of your app. And for big binaries, it’s just hard to find in the output what you need. It might be a good idea to create a very small example that reproduces your issue and the decompile it instead.
๐ฌ Inspection #
The firefly_cli inspect
command shows lots of useful information about the ROM: files, their sizes, metadata, WASM binary sections, elements in them, exported callbacks, imported host functions, etc. For example, to inspect the default system launcher:
firefly_cli inspect sys.launcher
๐ Finding the point of failure #
When an app explodes (panic
in Rust and Go, raise
in Python, etc), the runtime logs will tell you in which app callback it happened and what’s the last runtime function that was called. In many cases, this is enough to find where the code failed. If it’s not, add log_debug
function calls before and after each line of code that you suspect might fail:
firefly_rust::log_debug("before foo")
foo()
firefly_rust::log_debug("before bar")
bar()
firefly_rust::log_debug("after bar")
firefly.LogDebug("before foo")
foo()
firefly.LogDebug("before bar")
bar()
firefly.LogDebug("after bar")
const ff = @import("firefly");
ff.logDebug("before foo")
foo()
ff.logDebug("before bar")
bar()
ff.logDebug("after bar")
If you see in logs the message printed before the call but not after, your suspicion is correct and you found the point of failure. You can then print values of all variables before the failure to see which input causes the issues.
Debugging big functions with “wolf fence” (aka “bisect”)
If there is a lot of code that might be failing, it might be hard to add
log_debug
before every line. In that case, you can do a binary search of the point of failure. First, add one log message in the middle of failing function and run the code. If you see the message, the failure happens after that point. If you don’t see, it fails before. Now, insert a message in the middle of the failing region and repeat the process until you find the exact failing line.This debugging technique is called Wolf Fence or Bisect.
๐ฃ Tracebacks #
There are no tracebacks (aka “stack traces” or “back traces”) yet. See this issue: wasmi#538. To find the point of failure, use the debugging techniques covered above.
๐ Monitoring performance #
Run firefly_cli monitor
to launch a dashboard showing the runtime stats for a running app. It looks something like this:
โโดcpuโถโโโโโโโโโโโโโโโโ โโดmemoryโถโโโโโโโโโโโโโ
โ lag 0 ns 0% โ โ floor 1089 KB โ
โ busy 313 ms 31% โ โ ceil 1152 KB 18p โ
โ idle 693 ms 69% โ โโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโ
โโดfuel: updateโถโโโโโโโ โโดfuel: renderโถโโโโโโโ
โ min 276 โ โ min 663 โ
โ max 276 โ โ max 663 โ
โ mean 276 โ โ mean 663 โ
โ stdev 0 โ โ stdev 0 โ
โโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ
cpu
shows the CPU usage.lag
shows the total time (per second) that the app was lagging behind. If it’s not zero, theupdate
andrender
callbacks are too slow, at least sometimes. Big values may cause visual lag and annoy players. Optimize!busy
is how much time was spent running code, including system operations.idle
is how much time the runtime was sleeping and doing nothing. If it’s zero, every single update was lagging.
memory
shows RAM usage.floor
shows the position of the last non-zero byte in memory. Depending on the allocator used by the app, it might either show the actual memory usage or mean absolutely nothing.ceil
is how many memory pages are allocated. One page is 64 KB.
fuel
is the number of instructions executed per second.
Keep lag
zero and busy
, memory
, and fuel
low.
๐ข Common errors #
Below are some of the common errors related to Firefly Zero that you may encounter.
unsupported compression method 93
You’re trying to manually extract a ROM from a ZIP archive created using firefly_cli export
. ROMs use ZSTD compression that isn’t supported by your archive manager. You should use firefly_cli import
for installing ROMs instead.
wasm
unreachable
instruction executed.
The app hit an invalid state. Usually, it would raise an “exception” (or “panic”, depending on the language) but WebAssembly used by your language doesn’t support exceptions. See Finding the point of failure and Tracebacks.