objdump -S file parsed and used to annotate illegal memory accesses by the ARM

This commit is contained in:
JetSetIlly 2021-12-09 19:04:58 +00:00
parent b922589c5c
commit 80308af963
2 changed files with 199 additions and 65 deletions

View file

@ -20,7 +20,9 @@ import (
"strings"
"github.com/jetsetilly/gopher2600/curated"
"github.com/jetsetilly/gopher2600/debugger/terminal/colorterm/easyterm/ansi"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/harmony/arm7tdmi/mapfile"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/harmony/arm7tdmi/objdump"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/preferences"
"github.com/jetsetilly/gopher2600/logger"
@ -69,6 +71,9 @@ type ARM struct {
registers [rCount]uint32
status status
// the PC of the instruction being executed
executingPC uint32
// "peripherals" connected to the variety of ARM7TDMI-S used in the Harmony
// cartridge.
timer timer
@ -175,7 +180,10 @@ type ARM struct {
Ncycle func(bus busAccess, addr uint32)
// mapfile for binary (if available)
mapFile *mapfile.Mapfile
mapfile *mapfile.Mapfile
// obj dump for binary (if available)
objdump *objdump.ObjDump
// illegal accesses already encountered. duplicate accesses will not be logged.
illegalAccesses map[string]bool
@ -214,7 +222,12 @@ func NewARM(mmap MemoryMap, prefs *preferences.ARMPreferences, mem SharedMemory,
logger.Logf("ARM7", "reset: %s", err.Error())
}
arm.mapFile, err = mapfile.NewMapFile(pathToROM)
arm.mapfile, err = mapfile.NewMapFile(pathToROM)
if err != nil {
logger.Logf("ARM7", err.Error())
}
arm.objdump, err = objdump.NewObjDump(pathToROM)
if err != nil {
logger.Logf("ARM7", err.Error())
}
@ -307,6 +320,21 @@ func (arm *ARM) Step(vcsClock float32) {
arm.timer.stepFromVCS(Clk, vcsClock)
}
func (arm *ARM) lookupSource() {
if arm.mapfile != nil {
programLabel := arm.mapfile.FindProgramAccess(arm.executingPC)
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s%s()%s", ansi.DimPens["cyan"], programLabel, ansi.NormalPen)
}
}
if arm.objdump != nil {
src := arm.objdump.FindProgramAccess(arm.executingPC)
if src != "" {
logger.Logf("ARM7", "objdump:\n%s%s%s", ansi.DimPens["cyan"], src, ansi.NormalPen)
}
}
}
func (arm *ARM) read8bit(addr uint32) uint8 {
var mem *[]uint8
@ -321,17 +349,11 @@ func (arm *ARM) read8bit(addr uint32) uint8 {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "read8bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "read8bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return 0
}
@ -353,17 +375,11 @@ func (arm *ARM) write8bit(addr uint32, val uint8) {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "write8bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "write8bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return
}
@ -390,17 +406,11 @@ func (arm *ARM) read16bit(addr uint32) uint16 {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "read16bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "read16bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return 0
}
@ -427,17 +437,11 @@ func (arm *ARM) write16bit(addr uint32, val uint16) {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "write16bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "write16bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return
}
@ -465,17 +469,11 @@ func (arm *ARM) read32bit(addr uint32) uint32 {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "read32bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "read32bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return 0
}
@ -502,17 +500,11 @@ func (arm *ARM) write32bit(addr uint32, val uint32) {
}
arm.memoryError = arm.abortOnIllegalMem
accessKey := fmt.Sprintf("%08x%08x", addr, arm.registers[rPC])
accessKey := fmt.Sprintf("%08x%08x", addr, arm.executingPC)
if _, ok := arm.illegalAccesses[accessKey]; !ok {
arm.illegalAccesses[accessKey] = true
logger.Logf("ARM7", "write32bit: unrecognised address %08x (PC: %08x)", addr, arm.registers[rPC])
if arm.mapFile != nil {
programLabel := arm.mapFile.FindProgramAccess(arm.registers[rPC])
if programLabel != "" {
logger.Logf("ARM7", "mapfile: access in %s()", programLabel)
}
}
logger.Logf("ARM7", "write32bit: unrecognised address %08x (PC: %08x)", addr, arm.executingPC)
arm.lookupSource()
}
return
}
@ -584,7 +576,7 @@ func (arm *ARM) Run(mamcr uint32) (uint32, float32, error) {
// "The program counter points to the instruction being fetched rather than to the instruction
// being executed. This is important because it means that the Program Counter (PC)
// value used in an executing instruction is always two instructions ahead of the address."
executingPC := arm.registers[rPC] - 2
arm.executingPC = arm.registers[rPC] - 2
// set disasmLevel for the next instruction
if arm.disasm == nil {
@ -594,7 +586,7 @@ func (arm *ARM) Run(mamcr uint32) (uint32, float32, error) {
arm.disasmLevel = disasmFull
// check cache for existing disasm entry
if e, ok := arm.disasmCache[executingPC]; ok {
if e, ok := arm.disasmCache[arm.executingPC]; ok {
// use cached entry
arm.disasmEntry = e
@ -610,7 +602,7 @@ func (arm *ARM) Run(mamcr uint32) (uint32, float32, error) {
// in an initial state
if arm.disasmLevel == disasmFull {
arm.disasmEntry.Location = ""
arm.disasmEntry.Address = fmt.Sprintf("%08x", executingPC)
arm.disasmEntry.Address = fmt.Sprintf("%08x", arm.executingPC)
arm.disasmEntry.Operator = ""
arm.disasmEntry.Operand = ""
arm.disasmEntry.Cycles = 0.0
@ -620,22 +612,22 @@ func (arm *ARM) Run(mamcr uint32) (uint32, float32, error) {
}
// check program counter
memIdx := executingPC - arm.programMemoryOffset
memIdx := arm.executingPC - arm.programMemoryOffset
if memIdx+1 >= uint32(arm.programMemoryLen) {
// program counter is out-of-range so find program memory again
// (using the PC value)
err = arm.findProgramMemory()
if err != nil {
// can't find memory so we say the ARM program has finished inadvertently
logger.Logf("ARM7", "PC out of range (%#08x). aborting thumb program early", executingPC)
logger.Logf("ARM7", "PC out of range (%#08x). aborting thumb program early", arm.executingPC)
break // for loop
}
// if it's still out-of-range then give up with an error
memIdx = executingPC - arm.programMemoryOffset
memIdx = arm.executingPC - arm.programMemoryOffset
if memIdx+1 >= uint32(arm.programMemoryLen) {
// can't find memory so we say the ARM program has finished inadvertently
logger.Logf("ARM7", "PC out of range (%#08x). aborting thumb program early", executingPC)
logger.Logf("ARM7", "PC out of range (%#08x). aborting thumb program early", arm.executingPC)
break // for loop
}
}
@ -781,11 +773,11 @@ func (arm *ARM) Run(mamcr uint32) (uint32, float32, error) {
// operand fields and insert into cache
arm.disasmEntry.Operator = fmt.Sprintf("%-4s", arm.disasmEntry.Operator)
arm.disasmEntry.Operand = fmt.Sprintf("%-16s", arm.disasmEntry.Operand)
arm.disasmCache[executingPC] = arm.disasmEntry
arm.disasmCache[arm.executingPC] = arm.disasmEntry
case disasmUpdateOnly:
// entry is cached but notes may have changed so we recache
// the entry
arm.disasmCache[executingPC] = arm.disasmEntry
arm.disasmCache[arm.executingPC] = arm.disasmEntry
case disasmNone:
}

View file

@ -0,0 +1,142 @@
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
// package map file is a very basic parser for obj files as produced by
// "objdump -S" on the base elf file that is used to create a cartridge binary
//
// FindDataAccess() and FindProgramAccess() will return best-guess labels for
// the supplied address.
package objdump
import (
"io"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/jetsetilly/gopher2600/curated"
)
// ObjDump contains the parsed information from the map file.
type ObjDump struct {
lines []string
}
const mapFile = "armcode.obj"
func findObjDump(pathToROM string) *os.File {
// current working directory
sf, err := os.Open(mapFile)
if err == nil {
return sf
}
dir := filepath.Dir(pathToROM)
// same directory as binary
sf, err = os.Open(filepath.Join(dir, mapFile))
if err == nil {
return sf
}
// main sub-directory
sf, err = os.Open(filepath.Join(dir, "main", mapFile))
if err == nil {
return sf
}
// main/bin sub-directory
sf, err = os.Open(filepath.Join(dir, "main", "bin", mapFile))
if err == nil {
return sf
}
return nil
}
// NewObjDump loads and parses a map file. Returns a new instance of Mapfile or
// any errors.
func NewObjDump(pathToROM string) (*ObjDump, error) {
obj := &ObjDump{}
sf := findObjDump(pathToROM)
if sf == nil {
return nil, curated.Errorf("mapfile: gcc .map file not available (%s)", mapFile)
}
defer sf.Close()
data, err := io.ReadAll(sf)
if err != nil {
return nil, curated.Errorf("mapfile: processing error: %v", err)
}
obj.lines = strings.Split(string(data), "\n")
return obj, nil
}
// FindDataAccess returns the data label for the supplied address. Addresses
// will be matched exactly.
func (obj *ObjDump) FindDataAccess(address uint32) string {
return ""
}
// FindProgramAccess returns the program (function) label for the supplied
// address. Addresses may be in a range.
func (obj *ObjDump) FindProgramAccess(address uint32) string {
var start int
var end int
src := false
for i, l := range obj.lines {
flds := strings.Fields(l)
if len(flds) == 0 {
continue
}
if len(flds) >= 2 {
if flds[0][len(flds[0])-1] == ':' {
if src {
end = i
src = false
}
v, err := strconv.ParseInt(flds[0][:len(flds[0])-1], 16, 64)
pc := uint32(v)
if err == nil {
if pc == address {
break // for loop
}
}
} else if !src {
start = i
src = true
}
}
}
s := strings.Builder{}
if end > start {
for _, l := range obj.lines[start:end] {
s.WriteString(l)
s.WriteString("\n")
}
}
return strings.TrimRight(s.String(), "\n")
}