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