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