The forc debug CLI enables debugging a live transaction on a running Fuel Client node.
First, we need a project to debug, so create a new project using
forc new --script dbg_example && cd dbg_example And then add some content to src/main.sw, for example:
script;
use std::logging::log;
fn factorial(n: u64) -> u64 {
let mut result = 1;
let mut counter = 0;
while counter < n {
counter = counter + 1;
result = result * counter;
}
return result;
}
fn main() {
log::<u64>(factorial(5)); // 120
}Now we are ready to build the project.
forc build After this the resulting binary should be located at out/debug/dbg_example.bin. Because we are interested in the resulting bytecode, we can read that with:
forc parse-bytecode out/debug/dbg_example.binWe can recognize the main loop by observing the control flow. Looking around halfword 58-60, we can see:
half-word byte op raw
58 232 MOVI { dst: 0x11, val: 5 } 72 44 00 05
59 236 LT { dst: 0x10, lhs: 0x10, rhs: 0x11 } 16 41 04 40
60 240 JNZF { cond_nz: 0x10, dynamic: 0x0, fixed: 81 } 76 40 00 51 Here we can see our factorial(5) being set up with MOVI setting the value 5, followed by the LT comparison and conditional jump JNZF. The multiplication for our factorial happens at halfword 147 with MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 }. Finally, we can spot our log statement at halfword 139 with the LOGD instruction.
We can start up the debug infrastructure. On a new terminal session run fuel-core run --db-type in-memory --debug; we need to have that running because it actually executes the program. Now we can fire up the debugger itself: forc-debug. Now if everything is set up correctly, you should see the debugger prompt (>>). You can use help command to list available commands.
The debugger supports tab completion to help you discover files in your current working directory (and its subdirectories):
tx and press tab to recursively search for valid transaction JSON files Now we would like to inspect the program while it's running. To do this, we first need to send the script to the executor, i.e. fuel-core. To do so, we need a transaction specification, tx.json. It looks something like this:
{
"Script": {
"body": {
"script_gas_limit": 1000000,
"script": [
26, 240, 48, 0, 116, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 96, 93, 255, 192, 1, 16, 255, 255, 0, 26, 236, 80, 0, 145, 0, 0, 184, 80, 67, 176, 80, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 37, 80, 71, 176, 40, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 136, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 79, 176, 120, 114, 68, 0, 24, 40, 79, 180, 64, 80, 71, 176, 160, 114, 72, 0, 24, 40, 69, 52, 128, 80, 71, 176, 96, 114, 72, 0, 24, 40, 69, 52, 128, 80, 75, 176, 64, 26, 233, 16, 0, 26, 229, 32, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 144, 26, 71, 208, 0, 80, 75, 176, 24, 114, 76, 0, 16, 40, 73, 20, 192, 80, 71, 176, 144, 114, 76, 0, 16, 40, 69, 36, 192, 114, 72, 0, 16, 40, 65, 20, 128, 93, 69, 0, 1, 93, 65, 0, 0, 37, 65, 16, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 1, 88, 26, 87, 224, 0, 95, 236, 16, 42, 95, 236, 0, 41, 93, 67, 176, 41, 114, 68, 0, 5, 22, 65, 4, 64, 118, 64, 0, 81, 93, 67, 176, 42, 80, 71, 176, 200, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 87, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 71, 176, 160, 114, 72, 0, 24, 40, 71, 180, 128, 80, 75, 176, 24, 114, 76, 0, 24, 40, 73, 20, 192, 80, 71, 176, 88, 114, 76, 0, 24, 40, 69, 36, 192, 93, 83, 176, 11, 93, 79, 176, 12, 93, 71, 176, 13, 114, 72, 0, 8, 16, 73, 20, 128, 21, 73, 36, 192, 118, 72, 0, 1, 116, 0, 0, 7, 114, 72, 0, 2, 27, 73, 52, 128, 114, 76, 0, 8, 16, 77, 36, 192, 38, 76, 0, 0, 40, 29, 68, 64, 26, 80, 112, 0, 16, 73, 68, 64, 95, 73, 0, 0, 114, 64, 0, 8, 16, 65, 20, 0, 80, 71, 176, 112, 95, 237, 64, 14, 95, 237, 48, 15, 95, 237, 0, 16, 80, 67, 176, 48, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 176, 136, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 8, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 177, 48, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 48, 80, 71, 176, 240, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 176, 224, 26, 233, 16, 0, 26, 229, 0, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 56, 26, 67, 208, 0, 80, 71, 176, 72, 114, 72, 0, 16, 40, 69, 4, 128, 80, 67, 177, 32, 114, 72, 0, 16, 40, 65, 20, 128, 80, 71, 176, 184, 114, 72, 0, 16, 40, 69, 4, 128, 93, 67, 240, 0, 93, 71, 176, 23, 93, 75, 176, 24, 52, 1, 4, 82, 26, 244, 0, 0, 116, 0, 0, 8, 93, 67, 176, 41, 16, 65, 0, 64, 95, 237, 0, 41, 93, 67, 176, 42, 93, 71, 176, 41, 27, 65, 4, 64, 95, 237, 0, 42, 117, 0, 0, 91, 146, 0, 1, 88, 26, 249, 80, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 149, 0, 0, 15, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 72, 26, 67, 160, 0, 26, 71, 224, 0, 114, 72, 4, 0, 38, 72, 0, 0, 26, 72, 112, 0, 80, 79, 176, 24, 95, 237, 32, 3, 114, 72, 4, 0, 95, 237, 32, 4, 95, 236, 0, 5, 114, 72, 0, 24, 40, 237, 52, 128, 80, 75, 176, 48, 114, 76, 0, 24, 40, 75, 180, 192, 114, 76, 0, 24, 40, 65, 36, 192, 26, 245, 0, 0, 146, 0, 0, 72, 26, 249, 16, 0, 152, 8, 0, 0, 151, 0, 0, 15, 74, 248, 0, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 104, 26, 67, 160, 0, 26, 71, 144, 0, 26, 75, 224, 0, 80, 79, 176, 80, 114, 80, 0, 24, 40, 77, 5, 0, 114, 64, 0, 24, 40, 237, 52, 0, 80, 67, 176, 40, 114, 76, 0, 24, 40, 67, 180, 192, 93, 79, 176, 5, 80, 65, 0, 16, 80, 83, 176, 64, 95, 237, 48, 8, 80, 77, 64, 8, 114, 84, 0, 8, 40, 77, 5, 64, 80, 67, 176, 24, 114, 76, 0, 16, 40, 65, 68, 192, 114, 76, 0, 16, 40, 69, 4, 192, 26, 245, 16, 0, 146, 0, 0, 104, 26, 249, 32, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 71, 0, 0, 0, 21, 6, 230, 244, 76, 29, 98, 145
],
"script_data": [],
"receipts_root": "0000000000000000000000000000000000000000000000000000000000000000"
},
"policies": {
"bits": "MaxFee",
"values": [0, 0, 0, 0]
},
"inputs": [
{
"CoinSigned": {
"utxo_id": {
"tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2",
"output_index": 18
},
"owner": "f1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e",
"amount": 10599410012256088000,
"asset_id": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc3",
"tx_pointer": {
"block_height": 0,
"tx_index": 0
},
"witness_index": 0,
"maturity": 0,
"predicate_gas_used": null,
"predicate": null,
"predicate_data": null
}
}
],
"outputs": [],
"witnesses": [
{
"data": [156, 254, 34, 102, 65, 96, 133, 170, 254, 105, 147, 35, 196, 199, 179, 133, 132, 240, 208, 149, 11, 46, 30, 96, 44, 91, 121, 195, 145, 184, 159, 235, 117, 82, 135, 41, 84, 154, 102, 61, 61, 16, 99, 123, 58, 173, 75, 226, 219, 139, 62, 33, 41, 176, 16, 18, 132, 178, 8, 125, 130, 169, 32, 108]
}
]
}
} However, the key script should contain the actual bytecode to execute, i.e. the contents of out/debug/dbg_example.bin as a JSON array. The following command can be used to generate it:
python3 -c 'print(list(open("out/debug/dbg_example.bin", "rb").read()))' So now we replace the script array with the result, and save it as tx.json.
Now we can actually execute the script with an ABI to decode the log values:
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Receipt: ScriptResult { result: Success, gas_used: 1273 }
Terminated Looking at the output, we can see our factorial(5) result as the decoded log value of 120. The ABI has helped us decode the raw bytes (0000000000000078) into a meaningful value. It also tells us that the execution terminated without hitting any breakpoints. That's unsurprising, because we haven't set up any. We can do so with breakpoint command:
>> breakpoint 0
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: ScriptResult { result: Success, gas_used: 0 }
Stopped on breakpoint at address 0 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 Now we have stopped execution at the breakpoint on entry (address 0). We can now inspect the initial state of the VM.
>> register ggas
reg[0x9] = 1000000 # ggas
>> memory 0x10 0x8
000010: db f3 63 c9 1c 7f ec 95However, that's not too interesting either, so let's just execute until the end, and then reset the VM to remove the breakpoints.
>> continue
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Terminated
>> reset Next, we will setup a breakpoint to check the state on each iteration of the while loop. For instance, if we'd like to see what numbers get multiplied together, we could set up a breakpoint before the operation. Looking at our bytecode we can see the main multiplication for our factorial happens at:
half-word byte op raw
147 588 MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 } 1b 41 04 40 We can set a breakpoint on its address, at halfword-offset 147.
>>> breakpoint 147
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: ScriptResult { result: Success, gas_used: 82 }
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 Now we can inspect the inputs to multiply. Looking at the specification tells us that the instruction MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 } means reg[0x10] = reg[0x10] * reg[0x11]. So inspecting the inputs:
>> r 0x10 0x11
reg[0x10] = 1 # reg16
reg[0x11] = 1 # reg17 So on the first round the numbers are 1 and 1, so we can continue to the next iteration with the c command:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 1 # reg16
reg[0x11] = 2 # reg17And the next one:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 2 # reg16
reg[0x11] = 3 # reg17And fourth one:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 6 # reg16
reg[0x11] = 4 # reg17And round 5:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 24 # reg16
reg[0x11] = 5 # reg17At this point we can look at the values
| 0x10 | 0x11 |
|---|---|
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 6 | 4 |
| 24 | 5 |
From this we can clearly see that the left side, register 0x10 is the result variable which accumulates the factorial calculation (1, 1, 2, 6, 24), and register 0x11 is the counter which increments from 1 to 5. Now the counter equals the given factorial function argument 5, and the loop terminates. So when we continue, the program finishes without encountering any more breakpoints:
>> c
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Terminated