17 Commits e63540ea54 ... 6794dd73fe

Author SHA1 Message Date
  Michael Buesch 6794dd73fe Bump version 5 years ago
  Michael Buesch 965e56a449 awlsim-test: Add options to trigger coreserver mem read/write 5 years ago
  Michael Buesch 2df9a978a6 coreserver: Fix crash due to memory API misuse 5 years ago
  Michael Buesch 44a3f0fba0 Fix crash by incorrect access to s5t_to_seconds() 5 years ago
  Michael Buesch 862c4ccf91 setup: Remove py2exe support 5 years ago
  Michael Buesch f12fbffae0 win build: Remove py2exe support 5 years ago
  Michael Buesch 60b9204789 win-build: Add Cython build support 5 years ago
  Michael Buesch 2d3d105e33 coreserver: Fix Cython compilation 5 years ago
  Michael Buesch 5999340d68 Add Cython support to frozen coreserver executable 5 years ago
  Michael Buesch 681cfaa2e3 awlsim-win wrapper: Add Cython option 5 years ago
  Michael Buesch 38cd1bc63b Add no_cython to MANIFEST 5 years ago
  Michael Buesch dcd6829f7a Add endianness helpers for Win build 5 years ago
  Michael Buesch 6f8aeb5d59 cython: Fix patching on Windows 5 years ago
  Michael Buesch 49064ac352 Add Windows build script 5 years ago
  Michael Buesch c18c01e896 coreserver: Avoid usage of posix select on non-posix platform 5 years ago
  Michael Buesch f1bf260ec1 cython: Add posix/win annotations 5 years ago
  Michael Buesch be8492273a win: Copy all html and examples to the frozen archive 5 years ago

+ 2 - 0
DEVELOPMENT.md

@@ -19,6 +19,8 @@ In order to support both compiling the Awlsim core with Cython and running the s
 * `#@cy` : Enable (un-comment) this line during Cython patching.
 * `#@cy2` : Enable (un-comment) this line during Cython patching, if compiling for Python 2.
 * `#@cy3` : Enable (un-comment) this line during Cython patching, if compiling for Python 3.
+* `#@cy-posix` : Enable (un-comment) this line during Cython patching, if compiling for a Posix platform.
+* `#@cy-win` : Enable (un-comment) this line during Cython patching, if compiling for a Windows platform.
 * `#@nocy` : Disable (comment) this line during Cython patching.
 * `#@no-cython-patch` : Do not touch this line during cython patching.
 * `#+NoneToNULL` : Transform all `None` keywords in this line into `NULL`.

+ 1 - 0
MANIFEST.in

@@ -1,5 +1,6 @@
 global-include *.pxd.in
 global-include *.pyx.in
+global-include no_cython
 
 graft awlsim/gui/icons/icon-images
 graft debian

+ 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

+ 11 - 0
awlsim-win.cmd

@@ -1,5 +1,7 @@
 @echo off
 setlocal ENABLEDELAYEDEXPANSION
+set basedir=%~dp0
+set awlsim_base=%basedir%
 
 set PYPROG=awlsim-gui
 for /D %%f in ( "progs\putty\*" ) do set PATH=%%f\putty;!PATH!
@@ -10,6 +12,15 @@ for /D %%f in ( "C:\PYTHON*" ) do set PATH=!PATH!;%%f
 for /D %%f in ( "%USERPROFILE%\AppData\Local\Programs\Python\Python*" ) do set PATH=!PATH!;%%f;%%f\Scripts
 
 
+rem Set Cython paths
+if not exist %awlsim_base%\build goto no_cython
+for /D %%f in ( "%awlsim_base%\build\lib.*-*-3.*" ) do set PYTHONPATH=%%f\;!PYTHONPATH!
+set PYTHONPATH=.;%PYTHONPATH%
+set AWLSIM_CYTHON=1
+echo PYTHONPATH=%PYTHONPATH%
+:no_cython
+
+
 py -h >NUL 2>&1
 if %ERRORLEVEL% EQU 0 goto exec_py
 

+ 4 - 2
awlsim/common/datatypehelpers.pxd.in

@@ -22,7 +22,8 @@
 from awlsim.common.cython_support cimport *
 
 
-cdef extern from "<endian.h>":
+cdef extern from "<endian.h>":		#@cy-posix
+cdef extern from "endian-win.h":	#@cy-win
 	enum: BYTE_ORDER
 	enum: LITTLE_ENDIAN
 	enum: BIG_ENDIAN
@@ -37,7 +38,8 @@ cdef extern from "<endian.h>":
 	uint32_t be32toh(uint32_t)
 	uint32_t le32toh(uint32_t)
 
-cdef extern from "<byteswap.h>":
+cdef extern from "<byteswap.h>":	#@cy-posix
+cdef extern from "byteswap-win.h":	#@cy-win
 	uint16_t bswap_16(uint16_t)
 	uint32_t bswap_32(uint32_t)
 

+ 1 - 1
awlsim/common/version.py

@@ -16,7 +16,7 @@ __all__ = [
 
 VERSION_MAJOR	= 0
 VERSION_MINOR	= 67
-VERSION_BUGFIX	= 0
+VERSION_BUGFIX	= 1
 VERSION_EXTRA	= ""
 
 

+ 1 - 1
awlsim/core/operators.py

@@ -329,7 +329,7 @@ class AwlOperator(object): #+cdef
 		if self.operType == AwlOperatorTypes.IMM_REAL:
 			return str(dwordToPyFloat(self.immediate))
 		elif self.operType == AwlOperatorTypes.IMM_S5T:
-			seconds = Timer.s5t_to_seconds(self.immediate)
+			seconds = Timer_s5t_to_seconds(self.immediate)
 			return "S5T#" + AwlDataType.formatTime(seconds)
 		elif self.operType == AwlOperatorTypes.IMM_TIME:
 			return "T#" + AwlDataType.formatTime(self.immediate / 1000.0)

+ 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")
 			)

+ 20 - 7
awlsim/coreserver/run.py

@@ -1,8 +1,9 @@
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
 # AWL simulator - PLC core server
 #
-# Copyright 2013-2017 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
@@ -20,12 +21,24 @@
 #
 
 from __future__ import division, absolute_import, print_function, unicode_literals
-#from awlsim.common.cython_support cimport * #@cy
-from awlsim.common.compat import *
-
 
 if __name__ == "__main__":
+	import awlsim_loader.coverage_helper
+	import awlsim_loader.cython_helper as __cython
 	import sys
-	from awlsim.common.dynamic_import import importModule
-	mod = importModule("awlsim.coreserver.server")
-	sys.exit(mod.AwlSimServer._execute())
+
+	__modname = "awlsim.coreserver.server"
+	__mod = None
+
+	if __cython.shouldUseCython(__modname):
+		__cymodname = __cython.cythonModuleName(__modname)
+		try:
+			exec("import %s as __mod" % __cymodname)
+		except ImportError as e:
+			__cython.cythonImportError(__cymodname, str(e))
+	if not __cython.shouldUseCython(__modname):
+		exec("import %s as __mod" % __modname)
+
+	if __mod:
+		sys.exit(__mod.AwlSimServer._execute())
+	sys.exit(1)

+ 0 - 0
awlsim/coreserver/server.pxd.in


Some files were not shown because too many files changed in this diff