-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
161 changed files
with
6,250 additions
and
1,508 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
# Debugging with CLI | ||
|
||
The `forc debug` CLI enables debugging a live transaction on a running Fuel Client node. | ||
|
||
## An example project | ||
|
||
First, we need a project to debug, so create a new project using | ||
|
||
```bash | ||
forc new --script dbg_example && cd dbg_example | ||
``` | ||
|
||
And then add some content to `src/main.sw`, for example: | ||
|
||
```sway | ||
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 | ||
} | ||
``` | ||
|
||
## Building and bytecode output | ||
|
||
Now we are ready to build the project. | ||
|
||
```bash | ||
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: | ||
|
||
```bash | ||
forc parse-bytecode out/debug/dbg_example.bin | ||
``` | ||
|
||
Which should give us something like | ||
|
||
```text | ||
half-word byte op raw notes | ||
0 0 JI { imm: 4 } 90 00 00 04 jump to byte 16 | ||
1 4 NOOP 47 00 00 00 | ||
2 8 InvalidOpcode 00 00 00 00 data section offset lo (0) | ||
3 12 InvalidOpcode 00 00 00 44 data section offset hi (68) | ||
4 16 LW { ra: 63, rb: 12, imm: 1 } 5d fc c0 01 | ||
5 20 ADD { ra: 63, rb: 63, rc: 12 } 10 ff f3 00 | ||
6 24 MOVE { ra: 18, rb: 1 } 1a 48 10 00 | ||
7 28 MOVE { ra: 17, rb: 0 } 1a 44 00 00 | ||
8 32 LW { ra: 16, rb: 63, imm: 0 } 5d 43 f0 00 | ||
9 36 LT { ra: 16, rb: 17, rc: 16 } 16 41 14 00 | ||
10 40 JNZI { ra: 16, imm: 13 } 73 40 00 0d conditionally jump to byte 52 | ||
11 44 LOG { ra: 18, rb: 0, rc: 0, rd: 0 } 33 48 00 00 | ||
12 48 RET { ra: 0 } 24 00 00 00 | ||
13 52 ADD { ra: 17, rb: 17, rc: 1 } 10 45 10 40 | ||
14 56 MUL { ra: 18, rb: 18, rc: 17 } 1b 49 24 40 | ||
15 60 JI { imm: 8 } 90 00 00 08 jump to byte 32 | ||
16 64 NOOP 47 00 00 00 | ||
17 68 InvalidOpcode 00 00 00 00 | ||
18 72 InvalidOpcode 00 00 00 05 | ||
``` | ||
|
||
We can recognize the `while` loop by the conditional jumps `JNZI`. The condition just before the first jump can be identified by `LT` instruction (for `<`). Some notable instructions that are generated only once in our code include `MUL` for multiplication and `LOG {.., 0, 0, 0}` from the `log` function. | ||
|
||
## Setting up the debugging | ||
|
||
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. | ||
|
||
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: | ||
|
||
```json | ||
{ | ||
"Script": { | ||
"script_gas_limit": 1000000, | ||
"script": [], | ||
"script_data": [], | ||
"policies": { | ||
"bits": "GasPrice", | ||
"values": [0,0,0,0] | ||
}, | ||
"inputs": [ | ||
{ | ||
"CoinSigned": { | ||
"utxo_id": { | ||
"tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2", | ||
"output_index": 18 | ||
}, | ||
"owner": "f1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e", | ||
"amount": 10599410012256088338, | ||
"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 | ||
] | ||
} | ||
], | ||
"receipts_root": "0000000000000000000000000000000000000000000000000000000000000000" | ||
} | ||
} | ||
``` | ||
|
||
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: | ||
|
||
```bash | ||
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`. | ||
|
||
## Using the debugger | ||
|
||
Now we can actually execute the script: | ||
|
||
```text | ||
>> start_tx tx.json | ||
Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } | ||
Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } | ||
Receipt: ScriptResult { result: Success, gas_used: 60 } | ||
Terminated | ||
``` | ||
|
||
Looking at the first output line, we can see that it logged `ra: 120` which is the correct return value for `factorial(5)`. 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: | ||
|
||
```text | ||
>> breakpoint 0 | ||
>> start_tx tx.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. | ||
|
||
```text | ||
>> register ggas | ||
reg[0x9] = 1000000 # ggas | ||
>> memory 0x10 0x8 | ||
000010: e9 5c 58 86 c8 87 26 dd | ||
``` | ||
|
||
However, that's not too interesting either, so let's just execute until the end, and then reset the VM to remove the breakpoints. | ||
|
||
```text | ||
>> continue | ||
Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } | ||
Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } | ||
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. The bytecode has only a single `MUL` instruction: | ||
|
||
```text | ||
half-word byte op raw notes | ||
14 56 MUL { ra: 18, rb: 18, rc: 17 } 1b 49 24 40 | ||
``` | ||
|
||
We can set a breakpoint on its address, at halfword-offset `14`. | ||
|
||
```text | ||
>>> breakpoint 14 | ||
>> start_tx tx.json | ||
Receipt: ScriptResult { result: Success, gas_used: 9 } | ||
Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
``` | ||
|
||
Now we can inspect the inputs to multiply. Looking at [the specification](https://github.com/FuelLabs/fuel-specs/blob/master/src/fuel-vm/instruction-set.md#mul-multiply) tells us that the instruction `MUL { ra: 18, rb: 18, rc: 17 }` means `reg[18] = reg[18] * reg[17]`. So inspecting the inputs tells us that | ||
|
||
```text | ||
>> r 18 17 | ||
reg[0x12] = 1 # reg18 | ||
reg[0x11] = 1 # reg17 | ||
``` | ||
|
||
So on the first round the numbers are `1` and `1`, so we can continue to the next iteration: | ||
|
||
```text | ||
>> c | ||
Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
>> r 18 17 | ||
reg[0x12] = 1 # reg18 | ||
reg[0x11] = 2 # reg17 | ||
``` | ||
|
||
And the next one: | ||
|
||
```text | ||
>> c | ||
Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
>> r 18 17 | ||
reg[0x12] = 2 # reg18 | ||
reg[0x11] = 3 # reg17 | ||
``` | ||
|
||
And fourth one: | ||
|
||
```text | ||
>> c | ||
Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
>> r 18 17 | ||
reg[0x12] = 6 # reg18 | ||
reg[0x11] = 4 # reg17 | ||
``` | ||
|
||
And round 5: | ||
|
||
```text | ||
>> c | ||
Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
>> r 18 17 | ||
reg[0x12] = 24 # reg18 | ||
reg[0x11] = 5 # reg17 | ||
``` | ||
|
||
At this point we can look at the values | ||
|
||
17 | 18 | ||
---|---- | ||
1 | 1 | ||
2 | 1 | ||
3 | 2 | ||
4 | 6 | ||
5 | 24 | ||
|
||
From this we can clearly see that the left side, register `17` is the `counter` variable, and register `18` is `result`. 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: | ||
|
||
```text | ||
>> c | ||
Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } | ||
Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } | ||
Terminated | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Debugging with IDE | ||
|
||
The `forc debug` plugin also enables line-by-line debugging of Sway unit tests in VSCode. | ||
|
||
## Installation | ||
|
||
1. Install the Sway VSCode extension from the [marketplace](https://marketplace.visualstudio.com/items?itemName=FuelLabs.sway-vscode-plugin). | ||
2. Ensure you have the forc-debug binary installed. `which forc-debug`. | ||
It can be installed with `fuelup component add forc-debug`. | ||
3. Create a `.vscode/launch.json` file with the following contents: | ||
|
||
```json | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "sway", | ||
"request": "launch", | ||
"name": "Debug Sway", | ||
"program": "${file}" | ||
}] | ||
} | ||
``` | ||
|
||
## An example project | ||
|
||
Given this example contract: | ||
|
||
```sway | ||
contract; | ||
abi CallerContract { | ||
fn test_false() -> bool; | ||
} | ||
impl CallerContract for Contract { | ||
fn test_false() -> bool { | ||
false | ||
} | ||
} | ||
abi CalleeContract { | ||
fn test_true() -> bool; | ||
} | ||
#[test] | ||
fn test_multi_contract_calls() { | ||
let caller = abi(CallerContract, CONTRACT_ID); | ||
let callee = abi(CalleeContract, callee::CONTRACT_ID); | ||
let should_be_false = caller.test_false(); | ||
let should_be_true = callee.test_true(); | ||
assert(!should_be_false); | ||
assert(should_be_true); | ||
} | ||
``` | ||
|
||
Within the sway file open in VSCode, you can set breakpoints on lines within the test or functions that it calls, and click Run -> Start Debugging to begin debugging the unit test. | ||
|
||
This will build the sway project and run it in debug mode. The debugger will stop the VM execution when a breakpoint is hit. | ||
|
||
The debug panel will show VM registers under the Variables tab, as well as the current VM opcode where execution is suspended. You can continue execution, or use the Step Over function to step forward, instruction by instruction. |
Oops, something went wrong.