This page covers how to find bugs in your apps.
π Cheat codes#
Breaking the law, breaking the law.
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 = 2Next, define the cheat callback in the app:
#[unsafe(no_mangle)]
extern "C" 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;
}
}
}CHEAT int32_t cheat(int32_t cmd, int32_t val) {
switch (cmd) {
case 1:
return get_health();
case 2:
set_health();
return 1;
default:
return 0;
}
}pub fn cheat(cmd: Int, val: Int) -> Int {
match cmd {
1 => get_health(),
2 => {
set_health(val)
1
},
_ => 0,
}
}function cheat(cmd, val)
if cmd == 1 then
return get_health()
end
if cmd == 2 then
set_health(val)
return 1
end
return 0
endNow, 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:
use firefly_rust::*;
log_debug("before foo");
foo();
log_debug("before bar");
bar();
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");log_debug("before foo");
foo();
log_debug("before bar");
bar();
log_debug("after bar");@firefly.log_debug("before foo")
foo()
@firefly.log_debug("before bar")
bar()
@firefly.log_debug("after bar")firefly.log_debug("before foo")
foo()
firefly.log_debug("before bar")
bar()
firefly.log_debug("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_debugbefore 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 β
ββββββββββββββββββββββ ββββββββββββββββββββββcpushows the CPU usage.lagshows the total time (per second) that the app was lagging behind. If it’s not zero, theupdateandrendercallbacks are too slow, at least sometimes. Big values may cause visual lag and annoy players. Optimize!busyis how much time was spent running code, including system operations.idleis how much time the runtime was sleeping and doing nothing. If it’s zero, every single update was lagging.
memoryshows RAM usage.floorshows 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.ceilis how many memory pages are allocated. One page is 64 KB.
fuelis 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
unreachableinstruction 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.
error: no global memory allocator found but one is required; link to std or add
#[global_allocator]to a static item that implements the GlobalAlloc trait
You’re trying to build a Rust app that requires a global allocator but you don’t have one configured. The easiest way to fix it is to activate the talc crate feature for the SDK in Cargo.toml:
firefly-rust = { version = "*", features = ["talc"] }runtime error: error calling boot: all fuel consumed by WebAssembly.
The app has reached the maximum number of instructions that can be executed in a single call. The limit is here to prevent the device from freezing and not responding any user input.
Reaching the limit means that the callback function (in the example above, boot) does too many things which would freeze the device. Possible solutions:
- Optimize the code.
- Delay some logic. For example, instead of loading all assets for all levels at startup, load only assets required for one level when the level is launched.
- Split the heavy operation to several steps executed across multiple calls and show a progress bar.