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  }