Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Applies bug fixes and adds the -src option to dcpu-ar. It dumps each

instruction in an archive along with source context.
  • Loading branch information...
commit aba03fce73fef9abdd83fafb54891a8fe6e29dc0 1 parent f8cecda
@jteeuwen authored
View
4 archive/archive.go
@@ -4,9 +4,9 @@
package archive
import (
- "github.com/jteeuwen/dcpu/cpu"
"compress/gzip"
"fmt"
+ "github.com/jteeuwen/dcpu/cpu"
"io"
)
@@ -44,7 +44,7 @@ func (a *Archive) Words() []cpu.Word {
w[i] = cpu.Word(v)
}
- return w
+ return w
}
// Outdated returns true if the source files associated with this
View
42 dcpu-ar/README.md
@@ -2,10 +2,46 @@
The ar command performs various operations on compiled archives.
-One of which dumps its contents to stdout in human-readale form. Thus allowing
+One of which dumps its contents to stdout in human-readable form. Thus allowing
us to examine it more closely. This is mostly useful for debugging purposes
-on my part. Another options lets us extract all DCPU binary code into a
-separate file, allowing us to load a program into other emulators.
+on my part.
+
+Another options lets us extract all DCPU binary code into a separate file,
+allowing us to load a program into other emulators.
+
+A third option dumps each instruction to stdout, along with source context.
+For example:
+
+ $ dcpu-ar -src dcpu
+ PC INST OP A B FILE & SOURCE
+ ===============================================================================
+ 0000 ffff | 001f 001f 003f | m35fd.dasm:6 :M35fd dat -1
+ 0001 ffff | 001f 001f 003f | sped3.dasm:6 :Sped3 dat -1
+ 0002 ffff | 001f 001f 003f | keyboard.dasm:6 :Keyboard dat -1
+ 0003 ffff | 001f 001f 003f | clock.dasm:6 :Clock dat -1
+ 0004 ffff | 001f 001f 003f | spc2000.dasm:6 :Spc2000 dat -1
+ 0005 ffff | 001f 001f 003f | lem1802.dasm:6 :Lem1802 dat -1
+ 0006 1701 | 0001 0018 0005 | bootstrap.dasm:10 func Bootstrap {
+ 0007 0f01 | 0001 0018 0003 | bootstrap.dasm:10 func Bootstrap {
+ 0008 1301 | 0001 0018 0004 | bootstrap.dasm:10 func Bootstrap {
+ 0009 1600 | 0000 0010 0005 | bootstrap.dasm:11 hwn z
+ 000a 88a3 | 0003 0005 0022 | bootstrap.dasm:12 sub z, 1
+ 000b 1620 | 0000 0011 0005 | bootstrap.dasm:15 hwq z
+ 000c 7c12 | 0012 0000 001f | bootstrap.dasm:18 ife a, 0xb402
+ 000d b402 | 0002 0000 002d | bootstrap.dasm:18 ife a, 0xb402
+ 000e 7c32 | 0012 0001 001f | bootstrap.dasm:19 ife b, 0x12d0
+ 000f 12d0 | 0010 0016 0004 | bootstrap.dasm:19 ife b, 0x12d0
+ 0010 17c1 | 0001 001e 0005 | bootstrap.dasm:20 set [Clock], z
+ 0011 0003 | 0003 0000 0000 | bootstrap.dasm:20 set [Clock], z
+ ...
+
+As can be seen, some source lines appear multiple times. This is because
+some instructions generate multiple words.
+
+The `INST` column shows the value at the memory address denoted by `PC`.
+The `OP`, `A` and `B` columns show the opcode and the instruction operands.
+The last column shows the source file, line and actual code for the given
+instruction.
### License
View
57 dcpu-ar/main.go
@@ -7,14 +7,16 @@ import (
"flag"
"fmt"
"github.com/jteeuwen/dcpu/archive"
+ "github.com/jteeuwen/dcpu/cpu"
"github.com/jteeuwen/dcpu/path"
"os"
)
var (
- dump = flag.Bool("d", false, "")
+ dump = flag.Bool("dump", false, "")
+ src = flag.Bool("src", false, "")
bigendian = flag.Bool("b", false, "")
- splitfile = flag.String("split", "", "")
+ strip = flag.String("split", "", "")
)
func main() {
@@ -37,7 +39,12 @@ func main() {
return
}
- if len(*splitfile) > 0 {
+ if *src {
+ dumpSource(ar)
+ return
+ }
+
+ if len(*strip) > 0 {
err = saveCode(ar)
if err != nil {
@@ -47,9 +54,33 @@ func main() {
}
}
+// dumpSource dumps each instruction to stdout, and appends source context
+// for where it was originally defined. This includes the file name, line and
+// the code itself.
+func dumpSource(ar *archive.Archive) {
+ sr := make(SourceCache)
+ code := ar.Code
+
+ fmt.Println(" PC INST OP A B FILE & SOURCE")
+ fmt.Println("===============================================================================")
+
+ for pc := range code {
+ line, ok := sr.Line(ar, uint16(pc))
+ op, a, b := cpu.Decode(cpu.Word(code[pc]))
+
+ if ok {
+ fmt.Printf("%04x %04x | %04x %04x %04x | %s\n",
+ pc, code[pc], op, a, b, line)
+ } else {
+ fmt.Printf("%04x %04x | %04x %04x %04x\n",
+ pc, code[pc], op, a, b)
+ }
+ }
+}
+
// saveCode writes the archive's Code section to a separate file.
func saveCode(ar *archive.Archive) error {
- fd, err := os.Create(*splitfile)
+ fd, err := os.Create(*strip)
if err != nil {
return err
}
@@ -122,18 +153,24 @@ dcpu-ar performs various operations on compiled archives.
[OPTIONS]
- -d
+ -dump
Dump a human-readable form of the target package to stdout.
This allows us to closely examine its contents and is mostly useful
for debugging.
- -split <filename>
- Split copies the DCPU executable code into the given file. This turns it
- into a standalone executable, which can be run on other emulators that
- do not understand our archive file format.
+ -src
+ Dump all instructions to stdout, along with source context. This includes
+ the file, line and column the instruction was originall defined at.
+ This can be useful to ensure the assembler generated correct code.
+
+ -strip <filename>
+ strip copies the DCPU executable code into the given file, without all the
+ additional information stored in an archive. This turns it into a
+ standalone executable, which can be run on other emulators that do not
+ understand our archive file format.
-b
- When used in conjunction with -split, tells the command to save the binary
+ When used in conjunction with -strip, tells the command to save the binary
code in Big Endian format. This defaults to Little Edian.
-version
View
85 dcpu-ar/sourcecache.go
@@ -0,0 +1,85 @@
+// This file is subject to a 1-clause BSD license.
+// Its contents can be found in the enclosed LICENSE file.
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "github.com/jteeuwen/dcpu/archive"
+ "os"
+ "path/filepath"
+)
+
+// SourceCache allows access to source context for a given instruction.
+type SourceCache map[uint16]string
+
+// Clear clears the source cache.
+func (s *SourceCache) Clear() { *s = make(SourceCache) }
+
+// Line returns source context for the given PC.
+func (s SourceCache) Line(ar *archive.Archive, pc uint16) (string, bool) {
+ line, ok := s[pc]
+ if ok {
+ if len(line) == 0 {
+ return "", false
+ }
+
+ return line, true
+ }
+
+ s[pc] = ""
+
+ if int(pc) >= len(ar.Debug) {
+ return "", false
+ }
+
+ dbg := ar.Debug[pc]
+ if int(dbg.File) > len(ar.Files) {
+ return "", false
+ }
+
+ file := ar.Files[dbg.File]
+ line, err := s.readLine(file.Path, dbg.Line)
+
+ if err != nil {
+ return "", false
+ }
+
+ s[pc] = line
+ return line, true
+}
+
+// readLine reads a specific line from a source file.
+func (s SourceCache) readLine(file string, line uint16) (string, error) {
+ fd, err := os.Open(file)
+ if err != nil {
+ return "", err
+ }
+
+ defer fd.Close()
+
+ var src string
+
+ r := bufio.NewReader(fd)
+ count := uint16(1)
+
+ for {
+ data, err := r.ReadBytes('\n')
+ if err != nil {
+ return "", err
+ }
+
+ if count == line {
+ src = string(data[:len(data)-1])
+ break
+ }
+
+ count++
+ }
+
+ _, file = filepath.Split(file)
+
+ src = fmt.Sprintf("%s:%d %s", file, line, src)
+ return src, nil
+}
View
2  dcpu-ar/version.go
@@ -11,7 +11,7 @@ import (
const (
AppName = "dcpu-ar"
AppVersionMajor = 0
- AppVersionMinor = 1
+ AppVersionMinor = 2
)
// revision part of the program version.
View
23 dcpu-emu/emu.go
@@ -17,6 +17,7 @@ import (
)
type Emu struct {
+ source SourceCache
fc FrameCounter
disp Display
cpu *cpu.CPU
@@ -33,6 +34,7 @@ func NewEmu(display string, c *cpu.CPU) *Emu {
e.debug = false
e.trace = false
e.vsync = true
+ e.source = make(SourceCache)
switch display {
case "lem1802":
@@ -48,12 +50,22 @@ func NewEmu(display string, c *cpu.CPU) *Emu {
return
}
- fmt.Fprintf(
- os.Stdout,
- "%04x: %04x %04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x | %04x %04x %04x\n",
- pc, op, a, b, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA,
- )
+ line, ok := e.source.Line(pc)
+ if ok {
+ fmt.Fprintf(
+ os.Stdout,
+ "%04x: %04x %04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x | %04x %04x %04x | %s\n",
+ pc, op, a, b, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA, line,
+ )
+ } else {
+ fmt.Fprintf(
+ os.Stdout,
+ "%04x: %04x %04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x | %04x %04x %04x\n",
+ pc, op, a, b, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA,
+ )
+ }
}
+
return e
}
@@ -197,6 +209,7 @@ func (e *Emu) Init() (err error) {
e.cpu.Stop()
e.cpu.ClockSpeed = time.Millisecond / 100
e.cpu.Store.Clear()
+ e.source.Clear()
mem := e.cpu.Store.Mem
View
1  dcpu-emu/main.go
@@ -102,6 +102,7 @@ func parseArgs() *cpu.CPU {
err := loadBinary(c)
if err != nil {
fmt.Fprintf(os.Stderr, "Load binary: %s.\n", err)
+ os.Exit(1)
}
return c
View
87 dcpu-emu/sourcecache.go
@@ -0,0 +1,87 @@
+// This file is subject to a 1-clause BSD license.
+// Its contents can be found in the enclosed LICENSE file.
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "github.com/jteeuwen/dcpu/cpu"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// SourceCache allows access to source context for a given instruction.
+type SourceCache map[cpu.Word]string
+
+// Clear clears the source cache.
+func (s *SourceCache) Clear() { *s = make(SourceCache) }
+
+// Line returns source context for the given PC.
+func (s SourceCache) Line(pc cpu.Word) (string, bool) {
+ line, ok := s[pc]
+ if ok {
+ if len(line) == 0 {
+ return "", false
+ }
+
+ return line, true
+ }
+
+ s[pc] = ""
+
+ if int(pc) >= len(arch.Debug) {
+ return "", false
+ }
+
+ dbg := arch.Debug[pc]
+ if int(dbg.File) > len(arch.Files) {
+ return "", false
+ }
+
+ file := arch.Files[dbg.File]
+ line, err := s.readLine(file.Path, dbg.Line)
+
+ if err != nil {
+ return "", false
+ }
+
+ s[pc] = line
+ return line, true
+}
+
+// readLine reads a specific line from a source file.
+func (s SourceCache) readLine(file string, line uint16) (string, error) {
+ fd, err := os.Open(file)
+ if err != nil {
+ return "", err
+ }
+
+ defer fd.Close()
+
+ var src string
+
+ r := bufio.NewReader(fd)
+ count := uint16(1)
+
+ for {
+ data, err := r.ReadBytes('\n')
+ if err != nil {
+ return "", err
+ }
+
+ if count == line {
+ src = string(data[:len(data)-1])
+ break
+ }
+
+ count++
+ }
+
+ _, file = filepath.Split(file)
+
+ src = strings.TrimSpace(src)
+ src = fmt.Sprintf("%s:%d | %s", file, line, src)
+ return src, nil
+}
Please sign in to comment.
Something went wrong with that request. Please try again.