2 Commits 450710f75b ... 7707155e8a

Author SHA1 Message Date
  Michael Buesch 7707155e8a awlsim-test: Add options to trigger coreserver mem read/write 5 years ago
  Michael Buesch 6ccbf65a40 coreserver: Fix crash due to memory API misuse 5 years ago
2 changed files with 168 additions and 70 deletions
  1. 107 3
      awlsim-test
  2. 61 67
      awlsim/coreserver/memarea.py

+ 107 - 3
awlsim-test

@@ -40,6 +40,14 @@ class TestAwlSimClient(AwlSimClient):
 	def handle_CPUDUMP(self, dumpText):
 		emitCpuDump(dumpText)
 
+	def handle_MEMORY(self, memAreas):
+		for memArea in memAreas:
+			if memArea.flags & (memArea.FLG_ERR_READ |
+					    memArea.FLG_ERR_WRITE):
+				raise AwlSimError("awlsim-test: "
+					"Failed to access memory: %s" % (
+					str(memArea)))
+
 class ConsoleSSHTunnel(SSHTunnel):
 	def sshMessage(self, message, isDebug):
 		if opt_loglevel > Logging.LOG_INFO:
@@ -80,6 +88,8 @@ def usage():
 	print(" -b|--spawn-backend    Spawn a new backend server and connect to it")
 	if not isWinStandalone:
 		print(" -i|--interpreter EXE  Set the backend interpreter executable")
+	print(" -R|--mem-read AREA:OFFS:BITWIDTH       Memory read access.")
+	print(" -W|--mem-write AREA:OFFS:BITWIDTH:VAL  Memory write access.")
 	print("")
 	print("Loading hardware modules:")
 	print(" -H|--hardware NAME:PARAM=VAL:PARAM=VAL...")
@@ -199,6 +209,9 @@ def run(inputFile):
 		if cython_helper.shouldUseCython():
 			printInfo("*** Using accelerated CYTHON core "
 				  "(AWLSIM_CYTHON environment variable is set)")
+		if opt_memReads or opt_memWrites:
+			raise AwlSimError("awlsim-test --mem-read and --mem-write "
+				"are not supported in non-server-mode.")
 
 		project = readInputFile(inputFile)
 
@@ -394,10 +407,17 @@ def runWithServerBackend(inputFile):
 		# Run the client-side event loop
 		printInfo("[Initialization finished - Remote-CPU is executing user code]")
 		try:
+			if opt_memReads:
+				client.setMemoryReadRequests(memAreas=opt_memReads,
+							     repetitionPeriod=0.001,
+							     sync=False)
 			if not opt_noCpuDump:
 				clearConsole()
 			while True:
-				client.processMessages(None)
+				client.processMessages(timeout=0.05)
+				if opt_memWrites:
+					client.writeMemory(memAreas=opt_memWrites,
+							   sync=True)
 		finally:
 			if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
 				clearConsole()
@@ -428,6 +448,73 @@ def __signalHandler(sig, frame):
 		# Raise SIGINT. It will shut down everything.
 		os.kill(os.getpid(), signal.SIGINT)
 
+def parseMemoryArea(memAreaStr, withData):
+	try:
+		def dataToBytes(value, length):
+			if not (0 <= value <= ((1 << length) - 1)):
+				raise ValueError
+			return WordPacker.toBytes(byteBuffer=bytearray(length // 8),
+						  bitWidth=length,
+						  value=value)
+
+		memAreaStr = memAreaStr.split(":")
+		start = index = length = 0
+		data = b''
+		memType = {
+			"E"	: MemoryArea.TYPE_E,
+			"A"	: MemoryArea.TYPE_A,
+			"M"	: MemoryArea.TYPE_M,
+			"L"	: MemoryArea.TYPE_L,
+			"DB"	: MemoryArea.TYPE_DB,
+			"T"	: MemoryArea.TYPE_T,
+			"Z"	: MemoryArea.TYPE_Z,
+			"STW"	: MemoryArea.TYPE_STW,
+		}[memAreaStr[0].upper().strip()]
+		if memType in { MemoryArea.TYPE_E,
+				MemoryArea.TYPE_A,
+				MemoryArea.TYPE_M,
+				MemoryArea.TYPE_L, }:
+			start = int(memAreaStr[1])
+			length = int(memAreaStr[2])
+			if (not (0 <= start <= 0xFFFF) or
+			    length not in (8, 16, 32)):
+				raise ValueError
+			if withData:
+				data = dataToBytes(int(memAreaStr[3]), length)
+		elif memType == MemoryArea.TYPE_DB:
+			index = int(memAreaStr[1])
+			start = int(memAreaStr[2])
+			length = int(memAreaStr[3])
+			if (not (0 <= start <= 0xFFFF) or
+			    not (0 <= index <= 0xFFFF) or
+			    length not in (8, 16, 32)):
+				raise ValueError
+			if withData:
+				data = dataToBytes(int(memAreaStr[4]), length)
+		elif memType in { MemoryArea.TYPE_T,
+				  MemoryArea.TYPE_Z, }:
+			index = int(memAreaStr[1])
+			length = 16
+			if not (0 <= index <= 0xFFFF):
+				raise ValueError
+			if withData:
+				data = dataToBytes(int(memAreaStr[2]), 16)
+		elif memType == MemoryArea.TYPE_STW:
+			length = 16
+			if withData:
+				data = dataToBytes(int(memAreaStr[1]), 16)
+		else:
+			assert(0)
+		return MemoryArea(memType=memType,
+				  flags=0,
+				  index=index,
+				  start=start,
+				  length=length // 8,
+				  data=data)
+	except (ValueError, IndexError, KeyError, AwlSimError) as e:
+		pass
+	return None
+
 def main():
 	global opt_cycletime
 	global opt_maxRuntime
@@ -446,6 +533,8 @@ def main():
 	global opt_connectTo
 	global opt_spawnBackend
 	global opt_interpreter
+	global opt_memReads
+	global opt_memWrites
 
 	opt_cycletime = None
 	opt_maxRuntime = None
@@ -464,16 +553,19 @@ def main():
 	opt_connectTo = False
 	opt_spawnBackend = False
 	opt_interpreter = None
+	opt_memReads = []
+	opt_memWrites = []
 
 	try:
 		(opts, args) = getopt.getopt(sys.argv[1:],
-			"hY:M:24qDSxt:T:m:O:H:I:P:L:cC:bi:",
+			"hY:M:24qDSxt:T:m:O:H:I:P:L:cC:bi:R:W:",
 			[ "help", "cycle-limit=", "max-runtime=", "twoaccu", "fouraccu",
 			  "quiet", "no-cpu-dump", "speed-stats", "extended-insns",
 			  "obtemp=", "clock-mem=", "mnemonics=", "optimizers=",
 			  "hardware=", "hardware-info=", "profile=",
 			  "loglevel=",
-			  "connect", "connect-to=", "spawn-backend", "interpreter=", ])
+			  "connect", "connect-to=", "spawn-backend", "interpreter=",
+			  "mem-read=", "mem-write=", ])
 	except getopt.GetoptError as e:
 		printError(str(e))
 		usage()
@@ -580,6 +672,18 @@ def main():
 				printError("-i|--interpreter not supported on win-standalone")
 				sys.exit(1)
 			opt_interpreter = v
+		if o in ("-R", "--mem-read"):
+			memArea = parseMemoryArea(v, withData=False)
+			if not memArea:
+				printError("-R|--mem-read invalid arguments.")
+				sys.exit(1)
+			opt_memReads.append(memArea)
+		if o in ("-W", "--mem-write"):
+			memArea = parseMemoryArea(v, withData=True)
+			if not memArea:
+				printError("-W|--mem-write invalid arguments.")
+				sys.exit(1)
+			opt_memWrites.append(memArea)
 	if len(args) != 1 and not opt_hwinfos:
 		usage()
 		return ExitCodes.EXIT_ERR_CMDLINE

+ 61 - 67
awlsim/coreserver/memarea.py

@@ -2,7 +2,7 @@
 #
 # AWL simulator - PLC core server memory area helpers
 #
-# Copyright 2013-2014 Michael Buesch <m@bues.ch>
+# Copyright 2013-2018 Michael Buesch <m@bues.ch>
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -26,8 +26,11 @@ from awlsim.common.compat import *
 from awlsim.common.enumeration import *
 from awlsim.common.util import *
 from awlsim.common.exceptions import *
+from awlsim.common.wordpacker import *
 
-import struct
+from awlsim.core.cpu import * #+cimport
+from awlsim.core.memory import * #+cimport
+from awlsim.core.offset import * #+cimport
 
 
 class MemoryArea(object):
@@ -47,8 +50,6 @@ class MemoryArea(object):
 	FLG_ERR_READ	= 0x01
 	FLG_ERR_WRITE	= 0x02
 
-	_dwordStruct = struct.Struct(str(">I"))
-
 	def __init__(self, memType, flags, index, start, length, data=b''):
 		self.memType = memType
 		self.flags = flags
@@ -71,41 +72,38 @@ class MemoryArea(object):
 		self.flags |= self.FLG_ERR_WRITE
 		raise exception
 
-	def __read_E(self, cpu):
-		dataBytes = cpu.inputs.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
+	def __memoryRead(self, memory): #@nocy
+#@cy	def __memoryRead(self, AwlMemory memory):
+#@cy		cdef AwlMemoryObject memObj
+
+		try:
+			if (not (0 <= self.start <= 0xFFFF) or
+			    self.length not in (1, 2, 4)):
+				raise ValueError
+			memObj = memory.fetch(make_AwlOffset(self.start, 0),
+					      self.length * 8)
+		except (AwlSimError, ValueError) as e:
 			self.__raiseReadErr(
 				AwlSimError("MemoryArea: Read range error")
 			)
-		self.data = dataBytes[self.start : end]
+		self.data = AwlMemoryObject_asBytes(memObj)[:]
+
+	def __read_E(self, cpu):
+		self.__memoryRead(cpu.inputs)
 
 	def __read_A(self, cpu):
-		dataBytes = cpu.outputs.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseReadErr(
-				AwlSimError("MemoryArea: Read range error")
-			)
-		self.data = dataBytes[self.start : end]
+		self.__memoryRead(cpu.outputs)
 
 	def __read_M(self, cpu):
-		dataBytes = cpu.flags.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseReadErr(
-				AwlSimError("MemoryArea: Read range error")
-			)
-		self.data = dataBytes[self.start : end]
+		self.__memoryRead(cpu.flags)
 
 	def __read_L(self, cpu):
-		dataBytes = cpu.callStackTop.localdata.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
+		#TODO use self.index to select which L-stack we want to access.
+		if not cpu.activeLStack:
 			self.__raiseReadErr(
-				AwlSimError("MemoryArea: Read range error")
+				AwlSimError("MemoryArea: No active L-stack")
 			)
-		self.data = dataBytes[self.start : end]
+		self.__memoryRead(cpu.activeLStack.memory)
 
 	def __read_DB(self, cpu):
 		try:
@@ -120,13 +118,7 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Read access to "
 				"read-protected DB %d" % self.index)
 			)
-		dataBytes = db.structInstance.memory.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseReadErr(
-				AwlSimError("MemoryArea: Read range error")
-			)
-		self.data = dataBytes[self.start : end]
+		self.__memoryRead(db.structInstance.memory)
 
 	def __read_T(self, cpu):
 		try:
@@ -136,7 +128,8 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Invalid timer index %d" % self.index)
 			)
 		v = (timer.get() << 31) | timer.getTimevalS5TwithBase()
-		self.data, self.length = self._dwordStruct.pack(v), 4
+		self.data = WordPacker.toBytes(bytearray(4), 32, 0, v)
+		self.length = 4
 
 	def __read_Z(self, cpu):
 		try:
@@ -146,7 +139,8 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Invalid counter index %d" % self.index)
 			)
 		v = (counter.get() << 31) | counter.getValueBCD()
-		self.data, self.length = self._dwordStruct.pack(v), 4
+		self.data = WordPacker.toBytes(bytearray(4), 32, 0, v)
+		self.length = 4
 
 	def __read_STW(self, cpu):
 		stw = cpu.statusWord.getWord()
@@ -163,32 +157,32 @@ class MemoryArea(object):
 		TYPE_STW	: __read_STW,
 	}
 
-	def __write_E(self, cpu):
-		dataBytes = cpu.inputs.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
+	def __memoryWrite(self, memory): #@nocy
+#@cy	def __memoryWrite(self, AwlMemory memory):
+#@cy		cdef AwlMemoryObject memObj
+
+		try:
+			if (not (0 <= self.start <= 0xFFFF) or
+			    self.length not in (1, 2, 4) or
+			    self.length != len(self.data)):
+				raise ValueError
+			memObj = make_AwlMemoryObject_fromBytes(self.data,
+								self.length * 8)
+			memory.store(make_AwlOffset(self.start, 0),
+				     memObj)
+		except (AwlSimError, ValueError) as e:
 			self.__raiseWriteErr(
 				AwlSimError("MemoryArea: Write range error")
 			)
-		dataBytes[self.start : end] = self.data
+
+	def __write_E(self, cpu):
+		self.__memoryWrite(cpu.inputs)
 
 	def __write_A(self, cpu):
-		dataBytes = cpu.outputs.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseWriteErr(
-				AwlSimError("MemoryArea: Write range error")
-			)
-		dataBytes[self.start : end] = self.data
+		self.__memoryWrite(cpu.outputs)
 
 	def __write_M(self, cpu):
-		dataBytes = cpu.flags.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseWriteErr(
-				AwlSimError("MemoryArea: Write range error")
-			)
-		dataBytes[self.start : end] = self.data
+		self.__memoryWrite(cpu.flags)
 
 	def __write_DB(self, cpu):
 		try:
@@ -203,13 +197,7 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Write access to "
 				"write-protected DB %d" % self.index)
 			)
-		dataBytes = db.structInstance.memory.dataBytes
-		end = self.start + self.length
-		if end > len(dataBytes):
-			self.__raiseWriteErr(
-				AwlSimError("MemoryArea: Write range error")
-			)
-		dataBytes[self.start : end] = self.data
+		self.__memoryWrite(db.structInstance.memory)
 
 	def __write_T(self, cpu):
 		try:
@@ -219,11 +207,14 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Invalid timer index %d" % self.index)
 			)
 		try:
-			(dword, ) = self._dwordStruct.unpack(self.data)
+			if (self.length not in (2, 4) or
+			    self.length != len(self.data)):
+				raise ValueError
+			dword = WordPacker.fromBytes(self.data, self.length * 8, 0)
 			if dword > 0xFFFF:
 				raise ValueError
 			timer.setTimevalS5T(dword)
-		except (struct.error, ValueError, AwlSimError) as e:
+		except (ValueError, AwlSimError) as e:
 			self.__raiseWriteErr(
 				AwlSimError("MemoryArea: Timer value error")
 			)
@@ -236,11 +227,14 @@ class MemoryArea(object):
 				AwlSimError("MemoryArea: Invalid counter index %d" % self.index)
 			)
 		try:
-			(dword, ) = self._dwordStruct.unpack(self.data)
+			if (self.length not in (2, 4) or
+			    self.length != len(self.data)):
+				raise ValueError
+			dword = WordPacker.fromBytes(self.data, self.length * 8, 0)
 			if dword > 0xFFFF:
 				raise ValueError
 			counter.setValueBCD(dword)
-		except (struct.error, ValueError, AwlSimError) as e:
+		except (ValueError, AwlSimError) as e:
 			self.__raiseWriteErr(
 				AwlSimError("MemoryArea: Counter value error")
 			)