1 package net.mograsim.plugin.launch;
3 import static net.mograsim.plugin.preferences.PluginPreferences.MAX_MEMORY_CHANGE_INTERVAL;
5 import java.math.BigInteger;
6 import java.util.HashSet;
8 import java.util.concurrent.atomic.AtomicBoolean;
10 import org.eclipse.core.runtime.IStatus;
11 import org.eclipse.core.runtime.PlatformObject;
12 import org.eclipse.core.runtime.Status;
13 import org.eclipse.debug.core.DebugEvent;
14 import org.eclipse.debug.core.DebugException;
15 import org.eclipse.debug.core.DebugPlugin;
16 import org.eclipse.debug.core.ILaunch;
17 import org.eclipse.debug.core.model.IDebugTarget;
18 import org.eclipse.debug.core.model.IMemoryBlockExtension;
19 import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
20 import org.eclipse.debug.core.model.MemoryByte;
22 import net.mograsim.machine.MainMemory;
23 import net.mograsim.machine.MainMemoryDefinition;
24 import net.mograsim.machine.Memory.MemoryCellModifiedListener;
25 import net.mograsim.plugin.MograsimActivator;
27 public class MainMemoryBlockExtension extends PlatformObject implements IMemoryBlockExtension
29 private static final byte MEM_BYTE_FLAGS = (byte) (MemoryByte.READABLE | MemoryByte.WRITABLE | MemoryByte.ENDIANESS_KNOWN
30 | MemoryByte.BIG_ENDIAN);
32 private final String expression;
33 private final MachineDebugTarget debugTarget;
34 private final MainMemory mem;
36 private final MainMemoryDefinition memDef;
37 private final int cellWidthBits;
38 private final int cellWidthBytes;
39 private final BigInteger cellWidthBytesBI;
40 private final BigInteger minAddrWords;
41 private final BigInteger maxAddrWords;
43 private BigInteger baseAddrWords;
44 private BigInteger lengthWords;
46 private final Set<Object> clients;
47 private final MemoryCellModifiedListener memListener;
48 private final AtomicBoolean memListenerRegistered;
50 private final int maxContentChangeInterval;
51 private final Object contentChangeLock;
52 private Thread contentChangeThread;
53 private long nextContentChangeAllowedMillis;
54 private boolean contentChangeQueued;
56 public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
59 this.expression = expression;
60 this.debugTarget = debugTarget;
61 this.mem = debugTarget.getMachine().getMainMemory();
63 this.memDef = mem.getDefinition();
64 this.cellWidthBits = memDef.getCellWidth();
65 this.cellWidthBytes = (cellWidthBits + 7) / 8;
66 this.cellWidthBytesBI = BigInteger.valueOf(cellWidthBytes);
67 this.minAddrWords = BigInteger.valueOf(memDef.getMinimalAddress());
68 this.maxAddrWords = BigInteger.valueOf(memDef.getMaximalAddress());
70 // TODO parse expression better
71 this.baseAddrWords = new BigInteger(expression, 16);
72 this.lengthWords = BigInteger.ONE;
74 if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
75 throwDebugException("Base address out of range");
76 if (baseAddrWords.add(lengthWords).subtract(BigInteger.ONE).compareTo(maxAddrWords) > 0)
77 throwDebugException("End address out of range");
79 this.clients = new HashSet<>();
80 // don't check whether the address is in range, because this memory block could be read outside its "range"
81 this.memListener = a -> queueFireContentChangeEvent();
82 this.memListenerRegistered = new AtomicBoolean();
84 // TODO add a listener
85 this.maxContentChangeInterval = MograsimActivator.instance().getPluginPrefs().getInt(MAX_MEMORY_CHANGE_INTERVAL);
86 this.contentChangeLock = new Object();
87 this.nextContentChangeAllowedMillis = System.currentTimeMillis() - maxContentChangeInterval - 1;
91 public long getStartAddress()
93 return baseAddrWords.multiply(cellWidthBytesBI).longValueExact();
97 public long getLength()
99 return lengthWords.multiply(cellWidthBytesBI).longValueExact();
103 public byte[] getBytes() throws DebugException
105 BigInteger endAddrWords = baseAddrWords.add(lengthWords);
106 if (endAddrWords.compareTo(maxAddrWords) > 0)
107 throwDebugException("End address out of range");
108 int lengthBytes = lengthWords.multiply(cellWidthBytesBI).intValueExact();
110 byte[] bytes = new byte[lengthBytes];
113 for (i = 0, j = baseAddrWords.longValue(); i < lengthBytes; i += cellWidthBytes, j++)
115 BigInteger word = mem.getCellAsBigInteger(j);
116 System.arraycopy(word.toByteArray(), 0, bytes, i, cellWidthBytes);
122 public boolean supportsValueModification()
128 public void setValue(long offset, byte[] bytes) throws DebugException
130 if (offset % cellWidthBytes != 0 || bytes.length % cellWidthBytes != 0)
131 throwDebugException("Requested unaligned memory write");
132 BigInteger startAddrWords = baseAddrWords.add(BigInteger.valueOf(offset / cellWidthBytes));
133 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
134 throwDebugException("Start address out of range");
136 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
137 if (endAddrWords.compareTo(maxAddrWords) > 0)
138 throwDebugException("End address out of range");
142 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
144 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
145 mem.setCellAsBigInteger(j, word);
150 public String getModelIdentifier()
152 return MograsimActivator.PLUGIN_ID;
156 public IDebugTarget getDebugTarget()
162 public ILaunch getLaunch()
164 return debugTarget.getLaunch();
168 public String getExpression()
174 public BigInteger getBigBaseAddress() throws DebugException
176 return baseAddrWords;
180 public BigInteger getMemoryBlockStartAddress() throws DebugException
186 public BigInteger getMemoryBlockEndAddress() throws DebugException
192 public BigInteger getBigLength() throws DebugException
194 return maxAddrWords.subtract(minAddrWords);
198 public int getAddressSize() throws DebugException
200 return (getBigLength().bitLength() + 7) / 8;
204 public boolean supportBaseAddressModification() throws DebugException
210 public boolean supportsChangeManagement()
216 public void setBaseAddress(BigInteger address) throws DebugException
218 if (address.compareTo(minAddrWords) < 0 || address.compareTo(maxAddrWords) > 0)
219 throwDebugException("Address out of range");
220 this.baseAddrWords = address;
224 public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException
226 return getBytesFromAddress(getBigBaseAddress().add(unitOffset), addressableUnits);
230 public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException
233 throwDebugException("Requested negative amount of unites");
234 int lengthBytes = BigInteger.valueOf(units).multiply(cellWidthBytesBI).intValueExact();
236 MemoryByte[] bytes = new MemoryByte[lengthBytes];
239 for (i = 0, j = address; i < lengthBytes; i += cellWidthBytes, j = j.add(BigInteger.ONE))
241 if (j.compareTo(minAddrWords) >= 0 && j.compareTo(maxAddrWords) <= 0)
243 BigInteger word = mem.getCellAsBigInteger(j.longValue());
244 byte[] wordBytes = word.toByteArray();
245 int l = wordBytes[0] == 0 ? 1 : 0;
247 for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
248 bytes[i + k] = new MemoryByte((byte) 0, MEM_BYTE_FLAGS);
249 for (; k < cellWidthBytes; k++, l++)
250 bytes[i + k] = new MemoryByte(wordBytes[l], MEM_BYTE_FLAGS);
252 for (int k = 0; k < cellWidthBytes; k++)
253 bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
259 public void setValue(BigInteger offset, byte[] bytes) throws DebugException
261 if (bytes.length % cellWidthBytes != 0)
262 throwDebugException("Requested unaligned memory write");
263 BigInteger startAddrWords = baseAddrWords.add(offset);
264 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
265 throwDebugException("Start address out of range");
266 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
267 if (endAddrWords.compareTo(maxAddrWords) > 0)
268 throwDebugException("End address out of range");
270 unregisterMemoryListener();
274 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
276 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
277 mem.setCellAsBigInteger(j, word);
280 if (!clients.isEmpty())
281 registerMemoryListener();
282 queueFireContentChangeEvent();
286 public void connect(Object client)
288 registerMemoryListener();
293 public void disconnect(Object client)
295 clients.remove(client);
297 if (clients.isEmpty())
298 unregisterMemoryListener();
302 public Object[] getConnections()
305 Set<Object> clientsLocal = clients;
306 return clientsLocal == null ? new Object[0] : clientsLocal.toArray();
309 private void registerMemoryListener()
311 if (!memListenerRegistered.getAndSet(true))
312 mem.registerCellModifiedListener(memListener);
315 private void unregisterMemoryListener()
317 if (memListenerRegistered.getAndSet(false))
318 mem.deregisterCellModifiedListener(memListener);
320 Thread contentChangeThreadLocal;
321 synchronized (contentChangeLock)
323 contentChangeThreadLocal = contentChangeThread;
324 // set contentChangeQueued here to prevent the following scenario:
325 // 1. A change event is requested -> it gets fired "directly"
326 // 2. A second change event is requested during the "cooldown time" -> a queue thread gets started
327 // 3. The last client is disconnected -> the queue thread is interrupted
328 // 4. A new client is connected
329 // 5. A third change event is requested; queueFireContentChangeEvent locks contentChangeLock
330 // before the queue thread locks contentChangeLock.
331 // Now queueFireContentChangeEvent would return doing nothing, since contentChangeQueued still is true,
332 // causing a change event to be missed.
333 contentChangeQueued = false;
335 if (contentChangeThreadLocal != null)
336 contentChangeThreadLocal.interrupt();
340 public void dispose() throws DebugException
343 unregisterMemoryListener();
347 public IMemoryBlockRetrievalExtension getMemoryBlockRetrieval()
353 public int getAddressableSize() throws DebugException
355 return cellWidthBytes;
358 private void queueFireContentChangeEvent()
361 boolean fireInOwnThread = false;
362 synchronized (contentChangeLock)
364 if (contentChangeQueued)
366 long nextContentChangeAllowedMillisLocal = nextContentChangeAllowedMillis;
367 long now = System.currentTimeMillis();
368 sleepTime = nextContentChangeAllowedMillisLocal - now;
371 fireInOwnThread = true;
372 contentChangeQueued = true;
373 nextContentChangeAllowedMillis = nextContentChangeAllowedMillisLocal + maxContentChangeInterval;
376 fireInOwnThread = false;
377 nextContentChangeAllowedMillis = now + maxContentChangeInterval;
382 // the following two statements can't cause racing problems since we set contentChangeQueued to true,
383 // which means no-one will write this field until the thread started:
384 // this method will never get here, and in this moment there is no (other) content change thread running,
385 // since contentChangeQueued was false
386 contentChangeThread = new Thread(() ->
388 boolean interrupted = false;
391 Thread.sleep(sleepTime);
393 catch (@SuppressWarnings("unused") InterruptedException e)
397 synchronized (contentChangeLock)
399 contentChangeThread = null;
400 contentChangeQueued = false;
402 if (!interrupted && !Thread.interrupted())
403 fireContentChangeEventNow();
405 contentChangeThread.start();
407 fireContentChangeEventNow();
411 * Fires a terminate event for this debug element.
413 private void fireContentChangeEventNow()
415 fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
419 * Fires a debug event.
421 * @param event debug event to fire
423 private static void fireEvent(DebugEvent event)
425 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
428 private static void throwDebugException(String message) throws DebugException
430 throw new DebugException(
431 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));