1package bytecodealliance:[email protected];
2
3world debug-main {
4  import wasi:io/poll@0.2.6;
5  import debuggee;
6  export debugger;
7}
8
9interface debugger {
10  use debuggee.{debuggee};
11
12  /// Launch the debugger top-half (e.g., protocol server or UI)
13  /// implemented by this component, given the provided interface
14  /// to a debuggee. The debuggee will be paused and awaiting
15  /// a resumption.
16  ///
17  /// `args` contains the command-line arguments of the debugger,
18  /// starting with the program name (args[0]).
19  debug: func(d: borrow<debuggee>, args: list<string>);
20}
21
22interface debuggee {
23  use wasi:io/poll@0.2.6.{pollable};
24
25  /// A debuggee consisting of one Store over which we have
26  /// debugging control.
27  ///
28  /// A debuggee is always either "paused" or "running", and certain
29  /// methods below are only available in one or the other state.
30  resource debuggee {
31    /// List all modules that exist in the Store.
32    ///
33    /// If invoked while already running, causes a trap.
34    all-modules: func() -> list<module>;
35
36    /// List of all instances that exist in the Store.
37    ///
38    /// If invoked while already running, causes a trap.
39    all-instances: func() -> list<instance>;
40
41    /// Force an interruption event in any running code.
42    ///
43    /// Usable in paused or running states. If invoked in
44    /// the paused state, the next execution will immediately
45    /// yield an "interrupted" event.
46    interrupt: func();
47
48    /// Single-step, returning a future that will yield an event
49    /// when the single-step execution is complete.
50    ///
51    /// The `resumption` value indicates any mutations to
52    /// perform to execution state as part of resuming:
53    /// for example, injecting a call to another function,
54    /// or throwing an exception, or forcing an early return
55    /// from the current function.
56    ///
57    /// Usable in paused state; transitions to running state.
58    /// Debuggee remains in running state until the returned
59    /// future completes.
60    ///
61    /// If invoked while already running, causes a trap.
62    single-step: func(resumption: resumption-value) -> event-future;
63
64    /// Continue execution, returning a future that will
65    /// yield an event when the resumed execution
66    /// is complete.
67    ///
68    /// The `resumption` value indicates any mutations to
69    /// perform to execution state as part of resuming:
70    /// for example, injecting a call to another function,
71    /// or throwing an exception, or forcing an early return
72    /// from the current function.
73    ///
74    /// Usable in paused state; transitions to running state.
75    /// Debuggee remains in running state until the returned
76    /// future completes.
77    ///
78    /// If invoked while already running, causes a trap.
79    continue: func(resumption: resumption-value) -> event-future;
80
81    /// Get the current exit frames for each activation.
82    ///
83    /// If invoked while already running, causes a trap.
84    exit-frames: func() -> list<frame>;
85  }
86
87  /// A future that represents asynchronous execution of the
88  /// debuggee until the next debug event pausing execution.
89  ///
90  /// When complete, yields the resulting debug event.
91  resource event-future {
92    /// Subscribe to this future, returning a wasi-io pollable.
93    subscribe: func() -> pollable;
94
95    /// Consume this future, producing the resulting
96    /// event. Blocks if not ready. The debuggee
97    /// must be provided as well.
98    finish: static func(self: event-future, debuggee: borrow<debuggee>) -> result<event, error>;
99  }
100
101  /// A `resumption` value indicates how we want to continue
102  /// execution after a pause.
103  ///
104  /// By default, if we are a "read-only" debugger, there is only
105  /// one answer: with no mutation, according to the abstract
106  /// Wasm machine semantics and current machine state.
107  ///
108  /// However, this interface also supports various kinds of mutations.
109  /// For example, the debugger can ask to insert a call to an arbitrary
110  /// Wasm function, it can force an early return from the current function,
111  /// or it can throw an exception.
112  ///
113  /// This `resumption` value is orthogonal to the
114  /// `continue` vs. `single-step` resume axis:
115  /// the resumption value indicates whether we want to mutate
116  /// the executing abstract machine's *state*, while the single-step
117  /// vs. continue axis indicates how we want to step that machine
118  /// forward from whatever state is indicated (i.e., one step or
119  /// many until an event).
120  variant resumption-value {
121    /// Resume normally: do not alter the machine state.
122    normal,
123    /// Inject a call to a Wasm function at the current point, as-if
124    /// the program contained a `call` instruction with the given
125    /// values on the operand stack.
126    ///
127    /// Execution is subject to ordinary debugger events as with
128    /// any other; e.g., the function we call may experience breakpoint
129    /// pauses, may be single-stepped if we resume with `single-step`,
130    /// etc.
131    ///
132    /// If the injected function call completes normally, an
133    /// `injected-call-return` debug event is raised with the
134    /// return value(s).
135    inject-call(inject-call),
136    /// Resume as-if an exception were thrown at the current point.
137    throw-exception(wasm-exception),
138    /// Force a return from the current function frame, with the given
139    /// value(s) as return value(s).
140    early-return(list<wasm-value>),
141  }
142
143  /// A function call to be injected upon resumption.
144  record inject-call {
145    callee: wasm-func,
146    arguments: list<wasm-value>,
147  }
148
149  /// A debug event.
150  variant event {
151    /// Execution of the debuggee has completed.
152    complete,
153    /// A trap occurred in the debuggee. Execution
154    /// is paused at the trap-point, and will terminate
155    /// when resuming execution.
156    trap,
157    /// A breakpoint was hit in the debuggee, pausing
158    /// execution.
159    breakpoint,
160    /// An interruption due to `debuggee.interrupt` occurred.
161    interrupted,
162    /// An exception was thrown and caught by Wasm.
163    caught-exception-thrown(wasm-exception),
164    /// An exception was thrown and not caught by Wasm.
165    uncaught-exception-thrown(wasm-exception),
166    /// An injected call completed with return value(s).
167    injected-call-return(list<wasm-value>),
168  }
169
170  resource instance {
171    /// Get this instance's module.
172    get-module: func(d: borrow<debuggee>) -> module;
173
174    /// Get a memory from this instance's memory index space.
175    get-memory: func(d: borrow<debuggee>, memory-index: u32) -> result<memory, error>;
176
177    /// Get a global from this instance's global index space.
178    get-global: func(d: borrow<debuggee>, global-index: u32) -> result<global, error>;
179
180    /// Get a table from this instance's table index space.
181    get-table: func(d: borrow<debuggee>, table-index: u32) -> result<table, error>;
182
183    /// Get a function from this instance's function index space.
184    get-func: func(d: borrow<debuggee>, func-index: u32) -> result<wasm-func, error>;
185
186    /// Get a tag from this instance's tag index space.
187    get-tag: func(d: borrow<debuggee>, tag-index: u32) -> result<wasm-tag, error>;
188
189    /// Clone this handle.
190    clone: func() -> instance;
191
192    /// Get the unique ID of this instance (within the debuggee)
193    /// to allow equality and hashing.
194    unique-id: func() -> u64;
195  }
196
197  resource module {
198    /// Get the original Wasm bytecode for this module, if available.
199    bytecode: func() -> option<list<u8>>;
200
201    /// Add a breakpoint.
202    add-breakpoint: func(d: borrow<debuggee>, pc: u32) -> result<_, error>;
203
204    /// Remove a breakpoint.
205    remove-breakpoint: func(d: borrow<debuggee>, pc: u32) -> result<_, error>;
206
207    /// Clone this handle.
208    clone: func() -> module;
209
210    /// Get the unique ID of this module to allow equality and hashing.
211    unique-id: func() -> u64;
212  }
213
214  resource memory {
215    /// Get the current memory size, in bytes.
216    size-bytes: func(d: borrow<debuggee>) -> u64;
217
218    /// Get the page size, in bytes.
219    page-size-bytes: func(d: borrow<debuggee>) -> u64;
220
221    /// Increase size by the given `delta`. Returns the old size in bytes.
222    grow-to-bytes: func(d: borrow<debuggee>, delta: u64) -> result<u64, error>;
223
224    /// Read `len` bytes starting at `addr`. Returns `out-of-bounds` if any
225    /// byte in the range is out-of-bounds.
226    get-bytes: func(d: borrow<debuggee>, addr: u64, len: u64) -> result<list<u8>, error>;
227
228    /// Write `bytes` starting at `addr`. Returns `out-of-bounds` if any
229    /// byte in the range is out-of-bounds.
230    set-bytes: func(d: borrow<debuggee>, addr: u64, bytes: list<u8>) -> result<_, error>;
231
232    /// Get a u8 (byte) at an address. Returns `none` if out-of-bounds.
233    get-u8: func(d: borrow<debuggee>, addr: u64) -> result<u8, error>;
234    /// Get a u16 (in little endian order) at an address.
235    get-u16: func(d: borrow<debuggee>, addr: u64) -> result<u16, error>;
236    /// Get a u32 (in little endian order) at an address.
237    get-u32: func(d: borrow<debuggee>, addr: u64) -> result<u32, error>;
238    /// Get a u64 (in little endian order) at an address.
239    get-u64: func(d: borrow<debuggee>, addr: u64) -> result<u64, error>;
240
241    /// Set a u8 (byte) at an address. Returns `none` if out-of-bounds.
242    set-u8: func(d: borrow<debuggee>, addr: u64, value: u8) -> result<_, error>;
243    /// Set a u16 (in little endian order) at an address.
244    set-u16: func(d: borrow<debuggee>, addr: u64, value: u16) -> result<_, error>;
245    /// Set a u32 (in little endian order) at an address.
246    set-u32: func(d: borrow<debuggee>, addr: u64, value: u32) -> result<_, error>;
247    /// Set a u64 (in little endian order) at an address.
248    set-u64: func(d: borrow<debuggee>, addr: u64, value: u64) -> result<_, error>;
249
250    /// Clone this handle.
251    clone: func() -> memory;
252
253    /// Get the unique ID of this memory to allow equality and hashing.
254    unique-id: func() -> u64;
255  }
256
257  resource global {
258    /// Get the value of this global.
259    get: func(d: borrow<debuggee>) -> result<wasm-value, error>;
260
261    /// Set the value of this global.
262    set: func(d: borrow<debuggee>, val: wasm-value) -> result<_, error>;
263
264    /// Clone this handle.
265    clone: func() -> global;
266
267    /// Get the unique ID of this memory to allow equality and hashing.
268    unique-id: func() -> u64;
269  }
270
271  resource table {
272    /// Get the current length of this table, in elements.
273    len: func(d: borrow<debuggee>) -> u64;
274
275    /// Get the value at the Nth slot.
276    get-element: func(d: borrow<debuggee>, index: u64) -> result<wasm-value, error>;
277
278    /// Set the value at the Nth slot.
279    set-element: func(d: borrow<debuggee>, index: u64, val: wasm-value) -> result<_, error>;
280
281    /// Clone this handle.
282    clone: func() -> table;
283
284    /// Get the unique ID of this memory to allow equality and hashing.
285    unique-id: func() -> u64;
286  }
287
288  resource wasm-func {
289    /// Get the parameter types.
290    params: func(d: borrow<debuggee>) -> result<list<wasm-type>, error>;
291
292    /// Get the result types.
293    results: func(d: borrow<debuggee>) -> result<list<wasm-type>, error>;
294
295    /// Clone this handle.
296    clone: func() -> wasm-func;
297  }
298
299  resource wasm-exception {
300    /// Get the tag of this exception.
301    get-tag: func(d: borrow<debuggee>) -> wasm-tag;
302
303    /// Get the payload values of this exception.
304    get-values: func(d: borrow<debuggee>) -> result<list<wasm-value>, error>;
305
306    /// Clone this reference.
307    clone: func(d: borrow<debuggee>) -> wasm-exception;
308
309    /// Allocate a new exception.
310    make: static func(
311        d: borrow<debuggee>,
312        tag: borrow<wasm-tag>,
313        values: list<wasm-value>
314    ) -> result<wasm-exception, error>;
315  }
316
317  resource wasm-tag {
318    /// Get the parameter types.
319    params: func(d: borrow<debuggee>) -> result<list<wasm-type>, error>;
320
321    /// Get the unique ID of this tag to allow equality and hashing.
322    unique-id: func() -> u64;
323
324    /// Clone this handle.
325    clone: func() -> wasm-tag;
326
327    /// Allocate a new tag.
328    make: static func(d: borrow<debuggee>, params: list<wasm-type>) -> wasm-tag;
329  }
330
331  resource frame {
332    /// Instance of this frame.
333    get-instance: func(d: borrow<debuggee>) -> result<instance, error>;
334
335    /// Function index in this frame's instance.
336    get-func-index: func(d: borrow<debuggee>) -> result<u32, error>;
337
338    /// Current PC in this frame's instance.
339    get-pc: func(d: borrow<debuggee>) -> result<u32, error>;
340
341    /// Wasm locals.
342    get-locals: func(d: borrow<debuggee>) -> result<list<wasm-value>, error>;
343
344    /// Operand stack.
345    get-stack: func(d: borrow<debuggee>) -> result<list<wasm-value>, error>;
346
347    /// parent frame (the one that called this frame), if any.
348    parent-frame: func(d: borrow<debuggee>) -> result<option<frame>, error>;
349  }
350
351  enum error {
352    invalid-entity,
353    invalid-pc,
354    invalid-frame,
355    unsupported-type,
356    mismatched-type,
357    non-wasm-frame,
358    alloc-failure,
359    breakpoint-update,
360    read-only,
361    out-of-bounds,
362    memory-grow-failure,
363    execution-trap,
364  }
365
366  resource wasm-value {
367    get-type: func() -> wasm-type;
368    unwrap-i32: func() -> u32;
369    unwrap-i64: func() -> u64;
370    unwrap-f32: func() -> f32;
371    unwrap-f64: func() -> f64;
372    unwrap-v128: func() -> list<u8>;
373    unwrap-func: func() -> option<wasm-func>;
374    unwrap-exception: func() -> option<wasm-exception>;
375
376    make-i32: static func(value: u32) -> wasm-value;
377    make-i64: static func(value: u64) -> wasm-value;
378    make-f32: static func(value: f32) -> wasm-value;
379    make-f64: static func(value: f64) -> wasm-value;
380    make-v128: static func(value: list<u8>) -> wasm-value;
381
382    clone: func() -> wasm-value;
383  }
384
385  variant wasm-type {
386    wasm-i32,
387    wasm-i64,
388    wasm-f32,
389    wasm-f64,
390    wasm-v128,
391    wasm-funcref,
392    wasm-exnref,
393    // TODO: GC structs and arrays
394  }
395}
396