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 // TODO do we want to make the memory accessible byte-wise?
30 private final String expression;
31 private final MachineDebugTarget debugTarget;
32 private final MainMemory mem;
34 private final MainMemoryDefinition memDef;
35 private final int cellWidthBits;
36 private final int cellWidthBytes;
37 private final BigInteger cellWidthBytesBI;
38 private final BigInteger minAddrWords;
39 private final BigInteger maxAddrWords;
41 private BigInteger baseAddrWords;
42 private BigInteger lengthWords;
44 private final Set<Object> clients;
45 private final MemoryCellModifiedListener memListener;
46 private final AtomicBoolean memListenerRegistered;
48 private final int maxContentChangeInterval;
49 private final Object contentChangeLock;
50 private Thread contentChangeThread;
51 private long nextContentChangeAllowedMillis;
52 private boolean contentChangeQueued;
54 public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
57 this.expression = expression;
58 this.debugTarget = debugTarget;
59 this.mem = debugTarget.getMachine().getMainMemory();
61 this.memDef = mem.getDefinition();
62 this.cellWidthBits = memDef.getCellWidth();
63 this.cellWidthBytes = (cellWidthBits + 7) / 8;
64 this.cellWidthBytesBI = BigInteger.valueOf(cellWidthBytes);
65 this.minAddrWords = BigInteger.valueOf(memDef.getMinimalAddress());
66 this.maxAddrWords = BigInteger.valueOf(memDef.getMaximalAddress());
68 // TODO parse expression better
69 this.baseAddrWords = new BigInteger(expression, 16);
70 this.lengthWords = BigInteger.ONE;
72 if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
73 throwDebugException("Base address out of range");
74 if (baseAddrWords.add(lengthWords).compareTo(maxAddrWords) > 0)
75 throwDebugException("End address out of range");
77 this.clients = new HashSet<>();
78 // don't check whether the address is in range, because this memory block could be read outside its "range"
79 this.memListener = a -> queueFireContentChangeEvent();
80 this.memListenerRegistered = new AtomicBoolean();
82 this.maxContentChangeInterval = Preferences.current().getInt("net.mograsim.plugin.core.maxmemchangeinterval");
83 this.contentChangeLock = new Object();
84 this.nextContentChangeAllowedMillis = System.currentTimeMillis() - maxContentChangeInterval - 1;
88 public long getStartAddress()
90 return baseAddrWords.multiply(cellWidthBytesBI).longValueExact();
94 public long getLength()
96 return lengthWords.multiply(cellWidthBytesBI).longValueExact();
100 public byte[] getBytes() throws DebugException
102 BigInteger endAddrWords = baseAddrWords.add(lengthWords);
103 if (endAddrWords.compareTo(maxAddrWords) > 0)
104 throwDebugException("End address out of range");
105 int lengthBytes = lengthWords.multiply(cellWidthBytesBI).intValueExact();
107 byte[] bytes = new byte[lengthBytes];
110 for (i = 0, j = baseAddrWords.longValue(); i < lengthBytes; i += cellWidthBytes, j++)
112 BigInteger word = mem.getCellAsBigInteger(j);
113 System.arraycopy(word.toByteArray(), 0, bytes, i, cellWidthBytes);
119 public boolean supportsValueModification()
125 public void setValue(long offset, byte[] bytes) throws DebugException
127 if (offset % cellWidthBytes != 0 || bytes.length % cellWidthBytes != 0)
128 throwDebugException("Requested unaligned memory write");
129 BigInteger startAddrWords = baseAddrWords.add(BigInteger.valueOf(offset / cellWidthBytes));
130 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
131 throwDebugException("Start address out of range");
133 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
134 if (endAddrWords.compareTo(maxAddrWords) > 0)
135 throwDebugException("End address out of range");
139 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
141 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
142 mem.setCellAsBigInteger(j, word);
147 public String getModelIdentifier()
149 return MograsimActivator.PLUGIN_ID;
153 public IDebugTarget getDebugTarget()
159 public ILaunch getLaunch()
161 return debugTarget.getLaunch();
165 public String getExpression()
171 public BigInteger getBigBaseAddress() throws DebugException
173 return baseAddrWords;
177 public BigInteger getMemoryBlockStartAddress() throws DebugException
183 public BigInteger getMemoryBlockEndAddress() throws DebugException
189 public BigInteger getBigLength() throws DebugException
191 return maxAddrWords.subtract(minAddrWords);
195 public int getAddressSize() throws DebugException
201 public boolean supportBaseAddressModification() throws DebugException
207 public boolean supportsChangeManagement()
213 public void setBaseAddress(BigInteger address) throws DebugException
215 if (address.compareTo(minAddrWords) < 0 || address.compareTo(maxAddrWords) > 0)
216 throwDebugException("Address out of range");
217 this.baseAddrWords = address;
221 public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException
223 return getBytesFromAddress(getBigBaseAddress().add(unitOffset), addressableUnits);
227 public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException
230 throwDebugException("Requested negative amount of unites");
231 int lengthBytes = BigInteger.valueOf(units).multiply(cellWidthBytesBI).intValueExact();
233 MemoryByte[] bytes = new MemoryByte[lengthBytes];
236 for (i = 0, j = address; i < lengthBytes; i += cellWidthBytes, j = j.add(BigInteger.ONE))
238 if (j.compareTo(minAddrWords) >= 0 && j.compareTo(maxAddrWords) <= 0)
240 BigInteger word = mem.getCellAsBigInteger(j.longValue());
241 byte[] wordBytes = word.toByteArray();
242 int l = wordBytes[0] == 0 ? 1 : 0;
244 for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
245 bytes[i + k] = new MemoryByte();
246 for (; k < cellWidthBytes; k++, l++)
247 bytes[i + k] = new MemoryByte(wordBytes[l]);
249 for (int k = 0; k < cellWidthBytes; k++)
250 bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
256 public void setValue(BigInteger offset, byte[] bytes) throws DebugException
258 if (bytes.length % cellWidthBytes != 0)
259 throwDebugException("Requested unaligned memory write");
260 BigInteger startAddrWords = baseAddrWords.add(offset);
261 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
262 throwDebugException("Start address out of range");
263 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
264 if (endAddrWords.compareTo(maxAddrWords) > 0)
265 throwDebugException("End address out of range");
267 unregisterMemoryListener();
271 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
273 BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
274 mem.setCellAsBigInteger(j, word);
277 if (!clients.isEmpty())
278 registerMemoryListener();
279 queueFireContentChangeEvent();
283 public void connect(Object client)
285 registerMemoryListener();
290 public void disconnect(Object client)
292 clients.remove(client);
294 if (clients.isEmpty())
295 unregisterMemoryListener();
299 public Object[] getConnections()
302 Set<Object> clientsLocal = clients;
303 return clientsLocal == null ? new Object[0] : clientsLocal.toArray();
306 private void registerMemoryListener()
308 if (!memListenerRegistered.getAndSet(true))
309 mem.registerCellModifiedListener(memListener);
312 private void unregisterMemoryListener()
314 if (memListenerRegistered.getAndSet(false))
315 mem.deregisterCellModifiedListener(memListener);
317 Thread contentChangeThreadLocal;
318 synchronized (contentChangeLock)
320 contentChangeThreadLocal = contentChangeThread;
321 // set contentChangeQueued here to prevent the following scenario:
322 // 1. A change event is requested -> it gets fired "directly"
323 // 2. A second change event is requested during the "cooldown time" -> a queue thread gets started
324 // 3. The last client is disconnected -> the queue thread is interrupted
325 // 4. A new client is connected
326 // 5. A third change event is requested; queueFireContentChangeEvent locks contentChangeLock
327 // before the queue thread locks contentChangeLock.
328 // Now queueFireContentChangeEvent would return doing nothing, since contentChangeQueued still is true,
329 // causing a change event to be missed.
330 contentChangeQueued = false;
332 if (contentChangeThreadLocal != null)
333 contentChangeThreadLocal.interrupt();
337 public void dispose() throws DebugException
340 unregisterMemoryListener();
344 public IMemoryBlockRetrievalExtension getMemoryBlockRetrieval()
350 public int getAddressableSize() throws DebugException
352 return cellWidthBytes;
355 private void queueFireContentChangeEvent()
358 boolean fireInOwnThread = false;
359 synchronized (contentChangeLock)
361 if (contentChangeQueued)
363 long nextContentChangeAllowedMillisLocal = nextContentChangeAllowedMillis;
364 long now = System.currentTimeMillis();
365 sleepTime = nextContentChangeAllowedMillisLocal - now;
368 fireInOwnThread = true;
369 contentChangeQueued = true;
370 nextContentChangeAllowedMillis = nextContentChangeAllowedMillisLocal + maxContentChangeInterval;
373 fireInOwnThread = false;
374 nextContentChangeAllowedMillis = now + maxContentChangeInterval;
379 // the following two statements can't cause racing problems since we set contentChangeQueued to true,
380 // which means no-one will write this field until the thread started:
381 // this method will never get here, and in this moment there is no (other) content change thread running,
382 // since contentChangeQueued was false
383 contentChangeThread = new Thread(() ->
385 boolean interrupted = false;
388 Thread.sleep(sleepTime);
390 catch (@SuppressWarnings("unused") InterruptedException e)
394 synchronized (contentChangeLock)
396 contentChangeThread = null;
397 contentChangeQueued = false;
399 if (!interrupted && !Thread.interrupted())
400 fireContentChangeEventNow();
402 contentChangeThread.start();
404 fireContentChangeEventNow();
408 * Fires a terminate event for this debug element.
410 private void fireContentChangeEventNow()
412 fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
416 * Fires a debug event.
418 * @param event debug event to fire
420 private static void fireEvent(DebugEvent event)
422 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
425 private static void throwDebugException(String message) throws DebugException
427 throw new DebugException(
428 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));