github.com/cloudwego/iasm@v0.2.0/repl/iasm.go (about) 1 // 2 // Copyright 2024 CloudWeGo Authors 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 package repl 18 19 import ( 20 `fmt` 21 `io` 22 `math` 23 `os` 24 `path` 25 `runtime` 26 `strconv` 27 `strings` 28 `unicode` 29 `unsafe` 30 31 `github.com/knz/go-libedit` 32 ) 33 34 // IASM is the interactive REPL. 35 type IASM struct { 36 run bool 37 off uintptr 38 efd libedit.EditLine 39 ias _IASMArchSpecific 40 mem map[uint64]_Memory 41 fns map[string]unsafe.Pointer 42 } 43 44 // HistoryFile is the file that saves the REPL history, defaults to "~/.iasmhistory". 45 var HistoryFile = path.Clean(os.ExpandEnv("$HOME/.iasmhistory")) 46 47 // Start starts a new REPL session. 48 func (self *IASM) Start() { 49 var err error 50 var efd libedit.EditLine 51 52 /* greeting messages */ 53 println("Interactive Assembler v1.0") 54 println("Compiled with " + strconv.Quote(runtime.Version()) + ".") 55 println("History will be loaded from " + strconv.Quote(HistoryFile) + ".") 56 println(`Type ".help" for more information.`) 57 58 /* initialize libedit */ 59 if efd, err = libedit.Init("iasm", false); err != nil { 60 panic(err) 61 } 62 63 /* initialize IASM */ 64 self.off = 0 65 self.efd = efd 66 self.run = true 67 self.mem = map[uint64]_Memory{} 68 self.fns = map[string]unsafe.Pointer{} 69 70 /* initialze the editor instance */ 71 defer self.efd.Close() 72 self.efd.RebindControlKeys() 73 74 /* enable history */ 75 _ = self.efd.UseHistory(-1, true) 76 _ = self.efd.LoadHistory(HistoryFile) 77 78 /* set prompt and enable auto-save */ 79 self.efd.SetLeftPrompt(">>> ") 80 self.efd.SetAutoSaveHistory(HistoryFile, true) 81 82 /* main REPL loop */ 83 for self.run { 84 self.handleOnce() 85 } 86 } 87 88 func (self *IASM) readLine() string { 89 var err error 90 var ret string 91 92 /* read the line */ 93 for { 94 if ret, err = self.efd.GetLine(); err == nil { 95 break 96 } else if err != libedit.ErrInterrupted { 97 panic(err) 98 } else { 99 println("^C") 100 } 101 } 102 103 /* check for empty line */ 104 if ret == "" { 105 return "" 106 } 107 108 /* add to history */ 109 _ = self.efd.AddHistory(ret) 110 return ret 111 } 112 113 func (self *IASM) handleEOF() { 114 self.run = false 115 println() 116 } 117 118 func (self *IASM) handleOnce() { 119 defer self.handleError() 120 self.handleCommand(strings.TrimSpace(self.readLine())) 121 } 122 123 func (self *IASM) handleError() { 124 switch v := recover(); v { 125 case nil : break 126 case io.EOF : self.handleEOF() 127 default : println(fmt.Sprintf("iasm: %v", v)) 128 } 129 } 130 131 func (self *IASM) handleCommand(cmd string) { 132 var pos int 133 var fnv func(*IASM, string) 134 135 /* check for empty command */ 136 if cmd == "" { 137 return 138 } 139 140 /* find the command delimiter */ 141 if pos = strings.IndexFunc(cmd, unicode.IsSpace); pos == -1 { 142 pos = len(cmd) 143 } 144 145 /* handle syntax errors via panic/recover */ 146 defer func() { 147 if v := recover(); v != nil { 148 if e, ok := v.(_SyntaxError); !ok { 149 panic(v) 150 } else { 151 println("iasm: " + e.Error()) 152 } 153 } 154 }() 155 156 /* assembly immediately or call the command, if any */ 157 if cmd[0] != '.' { 158 self.handleAsmImmediate(cmd) 159 } else if fnv = _CMDS[cmd[1:pos]]; fnv != nil { 160 fnv(self, strings.TrimSpace(cmd[pos:])) 161 } else { 162 println("iasm: unknown command: " + cmd) 163 } 164 } 165 166 func (self *IASM) handleAsmImmediate(asm string) { 167 var err error 168 var buf []byte 169 170 /* assemble the instruction */ 171 if buf, err = self.ias.doasm(self.off, asm); err != nil { 172 println("iasm: " + err.Error()) 173 return 174 } 175 176 /* dump the instruction, and adjust the display offset */ 177 println(asmdump(buf, self.off, asm)) 178 self.off += uintptr(len(buf)) 179 } 180 181 var _CMDS = map[string]func(*IASM, string) { 182 "free" : (*IASM)._cmd_free, 183 "malloc" : (*IASM)._cmd_malloc, 184 "info" : (*IASM)._cmd_info, 185 "read" : (*IASM)._cmd_read, 186 "write" : (*IASM)._cmd_write, 187 "fill" : (*IASM)._cmd_fill, 188 "regs" : (*IASM)._cmd_regs, 189 "asm" : (*IASM)._cmd_asm, 190 "sys" : (*IASM)._cmd_sys, 191 "base" : (*IASM)._cmd_base, 192 "exit" : (*IASM)._cmd_exit, 193 "help" : (*IASM)._cmd_help, 194 } 195 196 func (self *IASM) _cmd_free(v string) { 197 var ok bool 198 var err error 199 var mid uint64 200 var mem _Memory 201 202 /* parse the memory ID */ 203 scan (v). 204 uint (&mid). 205 close () 206 207 /* find the memory block */ 208 if mem, ok = self.mem[mid]; !ok { 209 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 210 return 211 } 212 213 /* unmap the memory block */ 214 if err = munmap(mem); err == nil { 215 delete(self.mem, mid) 216 } else { 217 println("iasm: munmap(): " + err.Error()) 218 } 219 } 220 221 func (self *IASM) _cmd_malloc(v string) { 222 var err error 223 var mid uint64 224 var nbs uint64 225 var mem _Memory 226 227 /* parse the memory ID and size */ 228 scan (v). 229 uint (&mid). 230 uintopt (&nbs). 231 close () 232 233 /* default to one page of memory */ 234 if nbs == 0 { 235 nbs = _PageSize 236 } 237 238 /* check for duplicaded IDs */ 239 if _, ok := self.mem[mid]; ok { 240 println(fmt.Sprintf("iasm: duplicated memory ID: %d", mid)) 241 return 242 } 243 244 /* map the memory */ 245 if mem, err = mmap(nbs); err != nil { 246 println("iasm: mmap(): " + err.Error()) 247 return 248 } 249 250 /* save the memory block */ 251 self.mem[mid] = mem 252 println(fmt.Sprintf("Memory mapped at address %#x with size %d", mem.addr, mem.size)) 253 } 254 255 func (self *IASM) _cmd_info(v string) { 256 var ok bool 257 var mid uint64 258 var mem _Memory 259 260 /* parse the memory ID */ 261 scan (v). 262 uint (&mid). 263 close () 264 265 /* find the memory block */ 266 if mem, ok = self.mem[mid]; !ok { 267 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 268 return 269 } 270 271 /* print the memory info */ 272 println(fmt.Sprintf("Address : %#x", mem.addr)) 273 println(fmt.Sprintf("Size : %d bytes", mem.size)) 274 } 275 276 func (self *IASM) _cmd_read(v string) { 277 var ok bool 278 var off uint64 279 var mid uint64 280 var mem _Memory 281 282 /* parse the memory ID, offset and the optional size */ 283 nbs := uint64(math.MaxUint64) 284 scan(v).idoff(&mid, &off).uintopt(&nbs).close() 285 286 /* find the memory block */ 287 if mem, ok = self.mem[mid]; !ok { 288 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 289 return 290 } 291 292 /* read the whole block if not specified */ 293 if nbs == math.MaxUint64 { 294 nbs = mem.size - off 295 } 296 297 /* check the offset and size */ 298 if off + nbs > mem.size { 299 if diff := off + nbs - mem.size; diff == 1 { 300 println("iasm: memory read 1 byte past the boundary") 301 return 302 } else { 303 println(fmt.Sprintf("iasm: memory read %d bytes past the boundary", diff)) 304 return 305 } 306 } 307 308 /* dump the content */ 309 print(hexdump( 310 mem.buf()[off:off + nbs], 311 mem.addr, 312 )) 313 } 314 315 func (self *IASM) _cmd_write(v string) { 316 var ok bool 317 var val string 318 var off uint64 319 var mid uint64 320 var mem _Memory 321 322 /* parse the memory ID, offset, filling byte and the optional size */ 323 scan(v).idoff(&mid, &off).str(&val).close() 324 nbs := uint64(len(val)) 325 326 /* find the memory block */ 327 if mem, ok = self.mem[mid]; !ok { 328 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 329 return 330 } 331 332 /* check the offset and size */ 333 if off + nbs > mem.size { 334 if diff := off + nbs - mem.size; diff == 1 { 335 println("iasm: memory fill 1 byte past the boundary") 336 } else { 337 println(fmt.Sprintf("iasm: memory fill %d bytes past the boundary", diff)) 338 } 339 } 340 341 /* copy the data into memory */ 342 copy(mem.buf()[off:], val) 343 println(fmt.Sprintf("%d bytes written into %#x+%#x", nbs, mem.addr, off)) 344 } 345 346 func (self *IASM) _cmd_fill(v string) { 347 var ok bool 348 var val uint64 349 var off uint64 350 var mid uint64 351 var mem _Memory 352 353 /* parse the memory ID, offset, filling byte and the optional size */ 354 nbs := uint64(math.MaxUint64) 355 scan(v).idoff(&mid, &off).uint(&val).uintopt(&nbs).close() 356 357 /* check the filling value */ 358 if val > 255 { 359 println(fmt.Sprintf("iasm: invalid filling value: %d is not a byte", val)) 360 return 361 } 362 363 /* find the memory block */ 364 if mem, ok = self.mem[mid]; !ok { 365 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 366 return 367 } 368 369 /* read the whole block if not specified */ 370 if nbs == math.MaxUint64 { 371 nbs = mem.size - off 372 } 373 374 /* check the offset and size */ 375 if off + nbs > mem.size { 376 if diff := off + nbs - mem.size; diff == 1 { 377 println("iasm: memory fill 1 byte past the boundary") 378 } else { 379 println(fmt.Sprintf("iasm: memory fill %d bytes past the boundary", diff)) 380 } 381 } 382 383 /* fill the memory with byte */ 384 for i := off; i < off + nbs; i++ { 385 *(*byte)(unsafe.Pointer(uintptr(mem.p()) + uintptr(i))) = byte(val) 386 } 387 } 388 389 func (self *IASM) _cmd_regs(v string) { 390 regs := _regs.Dump(13) 391 sels := map[string]bool{} 392 393 /* check for register selectors */ 394 if fv := strings.Fields(v); len(fv) != 0 { 395 for _, x := range fv { 396 sels[strings.ToLower(x)] = true 397 } 398 } 399 400 /* dump the registers */ 401 for _, reg := range regs { 402 if v == "*" || sels[reg.reg] || (!reg.vec && len(sels) == 0) { 403 println(fmt.Sprintf("%10s = %s", reg.reg, reg.val)) 404 } 405 } 406 } 407 408 func (self *IASM) _cmd_asm(v string) { 409 var ok bool 410 var err error 411 var off uint64 412 var mid uint64 413 var fnv uintptr 414 var mem _Memory 415 416 /* parse the memory ID, offset, filling byte and the optional size */ 417 scan (v). 418 idoff (&mid, &off). 419 close () 420 421 /* find the memory block */ 422 if mem, ok = self.mem[mid]; !ok { 423 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 424 return 425 } 426 427 /* check for memory boundary */ 428 if fnv = mem.addr + uintptr(off); off >= mem.size { 429 println("iasm: indexing past the end of memory") 430 return 431 } 432 433 /* prompt messages */ 434 println(fmt.Sprintf("Assemble in memory block #(%d)+%#x (%#x).", mid, off, fnv)) 435 println(`Type ".end" and ENTER to end the assembly session.`) 436 437 /* restore prompt later */ 438 buf := []byte(nil) 439 rem := mem.size - off 440 defer self.efd.SetLeftPrompt(">>> ") 441 self.efd.SetLeftPrompt(fmt.Sprintf("(%#x) ", fnv)) 442 443 /* read and assemble the assembly source */ 444 for { 445 src := strings.TrimSuffix(self.readLine(), "\n") 446 val := strings.TrimSpace(src) 447 448 /* check for end of assembly */ 449 if val == ".end" { 450 break 451 } 452 453 /* feed into the assembler */ 454 if buf, err = self.ias.doasm(fnv, src); err != nil { 455 println("iasm: assembly failed: " + err.Error()) 456 continue 457 } 458 459 /* check for memory space */ 460 if rem < uint64(len(buf)) { 461 println(fmt.Sprintf("iasm: no space left in memory block: %d < %d", rem, len(buf))) 462 continue 463 } 464 465 /* update the input line if stdout is a terminal */ 466 if isatty(os.Stdout.Fd()) { 467 println("\x1b[F\x1b[K" + asmdump(buf, fnv, src)) 468 } 469 470 /* save the machine code */ 471 ptr := mem.buf() 472 copy(ptr[off:], buf) 473 474 /* update the prompt and offsets */ 475 rem -= uint64(len(buf)) 476 off += uint64(len(buf)) 477 fnv += uintptr(len(buf)) 478 self.efd.SetLeftPrompt(fmt.Sprintf("(%#x) ", fnv)) 479 } 480 } 481 482 func (self *IASM) _cmd_sys(v string) { 483 var ok bool 484 var err error 485 var off uint64 486 var mid uint64 487 var fnv uintptr 488 var mem _Memory 489 var rs0 _RegFile 490 var rs1 _RegFile 491 492 /* parse the memory ID, offset, filling byte and the optional size */ 493 scan (v). 494 idoff (&mid, &off). 495 close () 496 497 /* find the memory block */ 498 if mem, ok = self.mem[mid]; !ok { 499 println(fmt.Sprintf("iasm: no such memory block with ID %d", mid)) 500 return 501 } 502 503 /* check for memory boundary */ 504 if fnv = mem.addr + uintptr(off); off >= mem.size { 505 println("iasm: indexing past the end of memory") 506 return 507 } 508 509 /* execute the code */ 510 if rs0, rs1, err = _exec.Execute(fnv); err != nil { 511 println(fmt.Sprintf("iasm: cannot execute at memory address %#x: %s", fnv, err)) 512 return 513 } 514 515 /* print the differences */ 516 for _, diff := range rs0.Compare(rs1, 13) { 517 println(fmt.Sprintf("%10s = %s", diff.reg, diff.val)) 518 } 519 } 520 521 func (self *IASM) _cmd_base(v string) { 522 scan(v).uint((*uint64)(unsafe.Pointer(&self.off))).close() 523 } 524 525 func (self *IASM) _cmd_exit(_ string) { 526 self.run = false 527 } 528 529 func (self *IASM) _cmd_help(_ string) { 530 println("Supported commands:") 531 println(" .free ID ........................ Free a block of memory with ID.") 532 println() 533 println(" .malloc ID [SIZE] ................. Allocate a block of memory with ID of") 534 println(" SIZE bytes if specified, or one page of") 535 println(" memory if SIZE is not present.") 536 println() 537 println(" .info ID ........................ Print basic informations of a memory") 538 println(" block identified by ID.") 539 println() 540 println(" .read ID[+OFF] [SIZE] ........... Read a block of memory identified by") 541 println(" ID[+OFF] of SIZE bytes, default to the") 542 println(" whole block if SIZE is not set.") 543 println() 544 println(" .write ID[+OFF] DATA ............. Write DATA into a block of memory") 545 println(" identified by ID[+OFF].") 546 println() 547 println(" .fill ID[+OFF] BYTE [SIZE] ...... Fill a block of memory identified by") 548 println(" ID[+OFF] with BYTE of SIZE bytes,") 549 println(" default to the whole block if SIZE is") 550 println(" not set.") 551 println() 552 println(" .regs [REG*] .................... Print the content of the specified") 553 println(" registers, default to general purpose") 554 println(" registers if not specified. To also") 555 println(` include SIMD registers, type ".regs *".`) 556 println() 557 println(" .asm ID[+OFF] .................. Assemble into memory block identified by") 558 println(" ID[+OFF].") 559 println() 560 println(" .sys ID[+OFF] .................. Execute code in memory block identified") 561 println(" by ID[+OFF] with the CALL instruction.") 562 println() 563 println(" .base [BASE] .................... Set the base address for immediate") 564 println(" assembling mode, just for display.") 565 println() 566 println(" .exit ............................. Exit Interactive Assembler.") 567 println(" .help ............................. This help message.") 568 println() 569 }