1 package net.mograsim.plugin.launch;
3 import java.math.BigInteger;
4 import java.util.HashSet;
6 import java.util.concurrent.atomic.AtomicBoolean;
8 import org.eclipse.core.runtime.IStatus;
9 import org.eclipse.core.runtime.PlatformObject;
10 import org.eclipse.core.runtime.Status;
11 import org.eclipse.debug.core.DebugEvent;
12 import org.eclipse.debug.core.DebugException;
13 import org.eclipse.debug.core.DebugPlugin;
14 import org.eclipse.debug.core.ILaunch;
15 import org.eclipse.debug.core.model.IDebugTarget;
16 import org.eclipse.debug.core.model.IMemoryBlockExtension;
17 import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
18 import org.eclipse.debug.core.model.MemoryByte;
20 import net.mograsim.machine.MainMemory;
21 import net.mograsim.machine.MainMemoryDefinition;
22 import net.mograsim.machine.Memory.MemoryCellModifiedListener;
23 import net.mograsim.plugin.MograsimActivator;
24 import net.mograsim.preferences.Preferences;
26 public class MainMemoryBlockExtension extends PlatformObject implements IMemoryBlockExtension
28 private static final byte MEM_BYTE_FLAGS = (byte) (MemoryByte.READABLE | MemoryByte.WRITABLE | MemoryByte.ENDIANESS_KNOWN
29 | MemoryByte.BIG_ENDIAN);
31 private final String expression;
32 private final MachineDebugTarget debugTarget;
33 private final MainMemory mem;
35 private final MainMemoryDefinition memDef;
36 private final int cellWidthBits;
37 private final int cellWidthBytes;
38 private final BigInteger cellWidthBytesBI;
39 private final BigInteger minAddrWords;
40 private final BigInteger maxAddrWords;
42 private BigInteger baseAddrWords;
43 private BigInteger lengthWords;
45 private final Set<Object> clients;
46 private final MemoryCellModifiedListener memListener;
47 private final AtomicBoolean memListenerRegistered;
49 private final int maxContentChangeInterval;
50 private final Object contentChangeLock;
51 private Thread contentChangeThread;
52 private long nextContentChangeAllowedMillis;
53 private boolean contentChangeQueued;
55 public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
58 this.expression = expression;
59 this.debugTarget = debugTarget;
60 this.mem = debugTarget.getMachine().getMainMemory();
62 this.memDef = mem.getDefinition();
63 this.cellWidthBits = memDef.getCellWidth();
64 this.cellWidthBytes = (cellWidthBits + 7) / 8;
65 this.cellWidthBytesBI = BigInteger.valueOf(cellWidthBytes);
66 this.minAddrWords = BigInteger.valueOf(memDef.getMinimalAddress());
67 this.maxAddrWords = BigInteger.valueOf(memDef.getMaximalAddress());
69 // TODO parse expression better
70 this.baseAddrWords = new BigInteger(expression, 16);
71 this.lengthWords = BigInteger.ONE;
73 if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
74 throwDebugException("Base address out of range");
75 if (baseAddrWords.add(lengthWords).compareTo(maxAddrWords) > 0)
76 throwDebugException("End address out of range");
78 this.clients = new HashSet<>();
79 // don't check whether the address is in range, because this memory block could be read outside its "range"
80 this.memListener = a -> queueFireContentChangeEvent();
81 this.memListenerRegistered = new AtomicBoolean();
83 this.maxContentChangeInterval = Preferences.current().getInt("net.mograsim.plugin.core.maxmemchangeinterval");
84 this.contentChangeLock = new Object();
85 this.nextContentChangeAllowedMillis = System.currentTimeMillis() - maxContentChangeInterval - 1;
89 public long getStartAddress()
91 return baseAddrWords.multiply(cellWidthBytesBI).longValueExact();
95 public long getLength()
97 return lengthWords.multiply(cellWidthBytesBI).longValueExact();
101 public byte[] getBytes() throws DebugException
103 BigInteger endAddrWords = baseAddrWords.add(lengthWords);
104 if (endAddrWords.compareTo(maxAddrWords) > 0)
105 throwDebugException("End address out of range");
106 int lengthBytes = lengthWords.multiply(cellWidthBytesBI).intValueExact();
108 byte[] bytes = new byte[lengthBytes];
111 for (i = 0, j = baseAddrWords.longValue(); i < lengthBytes; i += cellWidthBytes, j++)
113 BigInteger word = mem.getCellAsBigInteger(j);
114 System.arraycopy(word.toByteArray(), 0, bytes, i, cellWidthBytes);
120 public boolean supportsValueModification()
126 public void setValue(long offset, byte[] bytes) throws DebugException
128 if (offset % cellWidthBytes != 0 || bytes.length % cellWidthBytes != 0)
129 throwDebugException("Requested unaligned memory write");
130 BigInteger startAddrWords = baseAddrWords.add(BigInteger.valueOf(offset / cellWidthBytes));
131 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
132 throwDebugException("Start address out of range");
134 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
135 if (endAddrWords.compareTo(maxAddrWords) > 0)
136 throwDebugException("End address out of range");
140 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
142 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
143 mem.setCellAsBigInteger(j, word);
148 public String getModelIdentifier()
150 return MograsimActivator.PLUGIN_ID;
154 public IDebugTarget getDebugTarget()
160 public ILaunch getLaunch()
162 return debugTarget.getLaunch();
166 public String getExpression()
172 public BigInteger getBigBaseAddress() throws DebugException
174 return baseAddrWords;
178 public BigInteger getMemoryBlockStartAddress() throws DebugException
184 public BigInteger getMemoryBlockEndAddress() throws DebugException
190 public BigInteger getBigLength() throws DebugException
192 return maxAddrWords.subtract(minAddrWords);
196 public int getAddressSize() throws DebugException
202 public boolean supportBaseAddressModification() throws DebugException
208 public boolean supportsChangeManagement()
214 public void setBaseAddress(BigInteger address) throws DebugException
216 if (address.compareTo(minAddrWords) < 0 || address.compareTo(maxAddrWords) > 0)
217 throwDebugException("Address out of range");
218 this.baseAddrWords = address;
222 public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException
224 return getBytesFromAddress(getBigBaseAddress().add(unitOffset), addressableUnits);
228 public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException
231 throwDebugException("Requested negative amount of unites");
232 int lengthBytes = BigInteger.valueOf(units).multiply(cellWidthBytesBI).intValueExact();
234 MemoryByte[] bytes = new MemoryByte[lengthBytes];
237 for (i = 0, j = address; i < lengthBytes; i += cellWidthBytes, j = j.add(BigInteger.ONE))
239 if (j.compareTo(minAddrWords) >= 0 && j.compareTo(maxAddrWords) <= 0)
241 BigInteger word = mem.getCellAsBigInteger(j.longValue());
242 byte[] wordBytes = word.toByteArray();
243 int l = wordBytes[0] == 0 ? 1 : 0;
245 for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
246 bytes[i + k] = new MemoryByte((byte) 0, MEM_BYTE_FLAGS);
247 for (; k < cellWidthBytes; k++, l++)
248 bytes[i + k] = new MemoryByte(wordBytes[l], MEM_BYTE_FLAGS);
250 for (int k = 0; k < cellWidthBytes; k++)
251 bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
257 public void setValue(BigInteger offset, byte[] bytes) throws DebugException
259 if (bytes.length % cellWidthBytes != 0)
260 throwDebugException("Requested unaligned memory write");
261 BigInteger startAddrWords = baseAddrWords.add(offset);
262 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
263 throwDebugException("Start address out of range");
264 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
265 if (endAddrWords.compareTo(maxAddrWords) > 0)
266 throwDebugException("End address out of range");
268 unregisterMemoryListener();
272 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
274 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
275 mem.setCellAsBigInteger(j, word);
278 if (!clients.isEmpty())
279 registerMemoryListener();
280 queueFireContentChangeEvent();
284 public void connect(Object client)
286 registerMemoryListener();
291 public void disconnect(Object client)
293 clients.remove(client);
295 if (clients.isEmpty())
296 unregisterMemoryListener();
300 public Object[] getConnections()
303 Set<Object> clientsLocal = clients;
304 return clientsLocal == null ? new Object[0] : clientsLocal.toArray();
307 private void registerMemoryListener()
309 if (!memListenerRegistered.getAndSet(true))
310 mem.registerCellModifiedListener(memListener);
313 private void unregisterMemoryListener()
315 if (memListenerRegistered.getAndSet(false))
316 mem.deregisterCellModifiedListener(memListener);
318 Thread contentChangeThreadLocal;
319 synchronized (contentChangeLock)
321 contentChangeThreadLocal = contentChangeThread;
322 // set contentChangeQueued here to prevent the following scenario:
323 // 1. A change event is requested -> it gets fired "directly"
324 // 2. A second change event is requested during the "cooldown time" -> a queue thread gets started
325 // 3. The last client is disconnected -> the queue thread is interrupted
326 // 4. A new client is connected
327 // 5. A third change event is requested; queueFireContentChangeEvent locks contentChangeLock
328 // before the queue thread locks contentChangeLock.
329 // Now queueFireContentChangeEvent would return doing nothing, since contentChangeQueued still is true,
330 // causing a change event to be missed.
331 contentChangeQueued = false;
333 if (contentChangeThreadLocal != null)
334 contentChangeThreadLocal.interrupt();
338 public void dispose() throws DebugException
341 unregisterMemoryListener();
345 public IMemoryBlockRetrievalExtension getMemoryBlockRetrieval()
351 public int getAddressableSize() throws DebugException
353 return cellWidthBytes;
356 private void queueFireContentChangeEvent()
359 boolean fireInOwnThread = false;
360 synchronized (contentChangeLock)
362 if (contentChangeQueued)
364 long nextContentChangeAllowedMillisLocal = nextContentChangeAllowedMillis;
365 long now = System.currentTimeMillis();
366 sleepTime = nextContentChangeAllowedMillisLocal - now;
369 fireInOwnThread = true;
370 contentChangeQueued = true;
371 nextContentChangeAllowedMillis = nextContentChangeAllowedMillisLocal + maxContentChangeInterval;
374 fireInOwnThread = false;
375 nextContentChangeAllowedMillis = now + maxContentChangeInterval;
380 // the following two statements can't cause racing problems since we set contentChangeQueued to true,
381 // which means no-one will write this field until the thread started:
382 // this method will never get here, and in this moment there is no (other) content change thread running,
383 // since contentChangeQueued was false
384 contentChangeThread = new Thread(() ->
386 boolean interrupted = false;
389 Thread.sleep(sleepTime);
391 catch (@SuppressWarnings("unused") InterruptedException e)
395 synchronized (contentChangeLock)
397 contentChangeThread = null;
398 contentChangeQueued = false;
400 if (!interrupted && !Thread.interrupted())
401 fireContentChangeEventNow();
403 contentChangeThread.start();
405 fireContentChangeEventNow();
409 * Fires a terminate event for this debug element.
411 private void fireContentChangeEventNow()
413 fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
417 * Fires a debug event.
419 * @param event debug event to fire
421 private static void fireEvent(DebugEvent event)
423 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
426 private static void throwDebugException(String message) throws DebugException
428 throw new DebugException(
429 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));