mirror of
https://github.com/FuelLabs/sway.git
synced 2025-08-15 08:00:37 +00:00

## Description Updates fuel dependencies: 1. fuel-core to 0.24.2 2. fuel-vm to 0.48.0 3. sdk to 0.57.0
284 lines
9.1 KiB
Markdown
284 lines
9.1 KiB
Markdown
# 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 thing.
|
|
|
|
```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 the following (this is a simplified example and to see a valid tx json please see [example-tx.json](https://github.com/FuelLabs/sway/blob/master/forc-plugins/forc-debug/examples/example_tx.json)):
|
|
|
|
```json
|
|
{
|
|
"Script": {
|
|
"body": {
|
|
"script_gas_limit": 1000000,
|
|
"script": [],
|
|
"script_data": [],
|
|
"receipts_root": "0000000000000000000000000000000000000000000000000000000000000000"
|
|
},
|
|
"policies": {
|
|
"bits": "MaxFee",
|
|
"values": [...]
|
|
},
|
|
"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": [...]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
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
|
|
```
|