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