/*******************************************************************************
* Copyright (c) 2008, 2010 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tm.internal.tcf.debug.ui.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.IMemoryBlockRetrieval;
import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.tm.internal.tcf.debug.model.ITCFConstants;
import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch;
import org.eclipse.tm.internal.tcf.debug.ui.Activator;
import org.eclipse.tm.tcf.protocol.IChannel;
import org.eclipse.tm.tcf.protocol.IToken;
import org.eclipse.tm.tcf.protocol.Protocol;
import org.eclipse.tm.tcf.services.IExpressions;
import org.eclipse.tm.tcf.services.IMemory;
import org.eclipse.tm.tcf.services.ISymbols;
import org.eclipse.tm.tcf.services.IMemory.MemoryError;
import org.eclipse.tm.tcf.util.TCFDataCache;
/**
* A memory block retrieval allows the user interface to request a memory block from a debugger when needed.
* TCF memory block retrieval is based on TCF Memory service.
*/
class TCFMemoryBlockRetrieval implements IMemoryBlockRetrievalExtension {
private final TCFNodeExecContext exec_ctx;
private final HashSet<MemoryBlock> mem_blocks = new HashSet<MemoryBlock>();
private static class MemData {
final BigInteger addr;
final MemoryByte[] data;
final byte[] bytes;
MemData(BigInteger addr, MemoryByte[] data) {
int i = 0;
this.addr = addr;
this.data = data;
this.bytes = new byte[data.length];
for (MemoryByte b : data) bytes[i++] = b.getValue();
}
}
private class MemoryBlock extends PlatformObject implements IMemoryBlockExtension {
private final String expression;
private final long length;
private final Set<Object> connections = new HashSet<Object>();
private final TCFDataCache<IExpressions.Expression> remote_expression;
private final TCFDataCache<IExpressions.Value> expression_value;
private final TCFDataCache<ISymbols.Symbol> expression_type;
private boolean disposed;
private MemData mem_data; // current memory block data
private MemData mem_prev; // previous data - before last suspend
private MemData mem_last; // last retrieved memory block data
MemoryBlock(final String expression, long length) {
this.expression = expression;
this.length = length;
mem_blocks.add(this);
final TCFLaunch launch = exec_ctx.model.getLaunch();
final IChannel channel = launch.getChannel();
remote_expression = new TCFDataCache<IExpressions.Expression>(channel) {
@Override
protected boolean startDataRetrieval() {
IExpressions exps = launch.getService(IExpressions.class);
if (exps == null) {
set(null, new Exception("Expressions service not available"), null);
return true;
}
command = exps.create(exec_ctx.id, null, expression, new IExpressions.DoneCreate() {
public void doneCreate(IToken token, Exception error, IExpressions.Expression context) {
if (disposed) {
IExpressions exps = channel.getRemoteService(IExpressions.class);
exps.dispose(context.getID(), new IExpressions.DoneDispose() {
public void doneDispose(IToken token, Exception error) {
if (error == null) return;
if (channel.getState() != IChannel.STATE_OPEN) return;
Activator.log("Error disposing remote expression evaluator", error);
}
});
return;
}
set(token, error, context);
}
});
return false;
}
};
expression_value = new TCFDataCache<IExpressions.Value>(channel) {
@Override
protected boolean startDataRetrieval() {
if (!remote_expression.validate(this)) return false;
final IExpressions.Expression ctx = remote_expression.getData();
if (ctx == null) {
set(null, null, null);
return true;
}
IExpressions exps = launch.getService(IExpressions.class);
command = exps.evaluate(ctx.getID(), new IExpressions.DoneEvaluate() {
public void doneEvaluate(IToken token, Exception error, IExpressions.Value value) {
set(token, error, value);
}
});
return false;
}
};
expression_type = new TCFDataCache<ISymbols.Symbol>(channel) {
@Override
protected boolean startDataRetrieval() {
if (!expression_value.validate(this)) return false;
IExpressions.Value val = expression_value.getData();
if (val == null) {
set(null, expression_value.getError(), null);
return true;
}
TCFDataCache<ISymbols.Symbol> type_cache = exec_ctx.model.getSymbolInfoCache(val.getTypeID());
if (type_cache == null) {
set(null, null, null);
return true;
}
if (!type_cache.validate(this)) return false;
set(null, type_cache.getError(), type_cache.getData());
return true;
}
};
}
public synchronized void connect(Object client) {
connections.add(client);
}
public synchronized void disconnect(Object client) {
connections.remove(client);
}
public synchronized Object[] getConnections() {
return connections.toArray(new Object[connections.size()]);
}
public void dispose() throws DebugException {
new TCFDebugTask<Boolean>(exec_ctx.getChannel()) {
public void run() {
disposed = true;
expression_value.dispose();
expression_type.dispose();
if (remote_expression.isValid() && remote_expression.getData() != null) {
final IChannel channel = exec_ctx.channel;
if (channel.getState() == IChannel.STATE_OPEN) {
IExpressions exps = channel.getRemoteService(IExpressions.class);
exps.dispose(remote_expression.getData().getID(), new IExpressions.DoneDispose() {
public void doneDispose(IToken token, Exception error) {
if (error == null) return;
if (channel.getState() != IChannel.STATE_OPEN) return;
Activator.log("Error disposing remote expression evaluator", error);
}
});
}
}
remote_expression.dispose();
mem_blocks.remove(MemoryBlock.this);
done(Boolean.TRUE);
}
}.getD();
}
public int getAddressSize() throws DebugException {
return new TCFDebugTask<Integer>(exec_ctx.getChannel()) {
public void run() {
if (exec_ctx.isDisposed()) {
error("Context is disposed");
}
else {
TCFDataCache<IMemory.MemoryContext> cache = exec_ctx.getMemoryContext();
if (!cache.validate(this)) return;
if (cache.getError() != null) {
error(cache.getError());
}
else {
IMemory.MemoryContext mem = cache.getData();
if (mem == null) {
error("Context does not provide memory access");
}
else {
done(mem.getAddressSize());
}
}
}
}
}.getD();
}
public int getAddressableSize() throws DebugException {
// TODO: support for addressable size other then 1 byte
return 1;
}
public BigInteger getBigBaseAddress() throws DebugException {
return new TCFDebugTask<BigInteger>(exec_ctx.getChannel()) {
public void run() {
if (!expression_value.validate()) {
expression_value.wait(this);
}
else if (expression_value.getError() != null) {
error(expression_value.getError());
}
else if (expression_value.getData() == null) {
error("Address expression evaluation failed");
}
else if (!expression_type.validate()) {
expression_type.wait(this);
}
else if (expression_type.getError() != null) {
error(expression_type.getError());
}
else {
IExpressions.Value value = expression_value.getData();
byte[] data = value.getValue();
if (data == null || data.length == 0) {
error("Address expression value is empty (void)");
}
else {
ISymbols.Symbol type = expression_type.getData();
boolean signed = type != null && type.getTypeClass() == ISymbols.TypeClass.integer;
done(TCFNumberFormat.toBigInteger(data, 0, data.length, value.isBigEndian(), signed));
}
}
}
}.getD();
}
public MemoryByte[] getBytesFromAddress(final BigInteger address, final long units) throws DebugException {
return new TCFDebugTask<MemoryByte[]>(exec_ctx.getChannel()) {
int offs = 0;
public void run() {
if (mem_data != null &&
address.compareTo(mem_data.addr) >= 0 &&
address.add(BigInteger.valueOf(units)).compareTo(
mem_data.addr.add(BigInteger.valueOf(mem_data.data.length))) <= 0) {
offs = address.subtract(mem_data.addr).intValue();
MemoryByte[] res = mem_data.data;
if (units < mem_data.data.length) {
res = new MemoryByte[(int)units];
System.arraycopy(mem_data, offs, res, 0, res.length);
}
setHistoryFlags();
done(res);
return;
}
if (exec_ctx.isDisposed()) {
error("Context is disposed");
return;
}
TCFDataCache<IMemory.MemoryContext> cache = exec_ctx.getMemoryContext();
if (!cache.validate(this)) return;
if (cache.getError() != null) {
error(cache.getError());
return;
}
final IMemory.MemoryContext mem = cache.getData();
if (mem == null) {
error("Context does not provide memory access");
return;
}
final int size = (int)units;
final int mode = IMemory.MODE_CONTINUEONERROR | IMemory.MODE_VERIFY;
final byte[] buf = new byte[size];
final MemoryByte[] res = new MemoryByte[size];
mem.get(address, 1, buf, 0, size, mode, new IMemory.DoneMemory() {
public void doneMemory(IToken token, MemoryError error) {
int big_endian = 0;
if (mem.getProperties().get(IMemory.PROP_BIG_ENDIAN) != null) {
big_endian |= MemoryByte.ENDIANESS_KNOWN;
if (mem.isBigEndian()) big_endian |= MemoryByte.BIG_ENDIAN;
}
int cnt = 0;
while (offs < size) {
int flags = big_endian;
if (error instanceof IMemory.ErrorOffset) {
IMemory.ErrorOffset ofs = (IMemory.ErrorOffset)error;
int status = ofs.getStatus(cnt);
if (status == IMemory.ErrorOffset.BYTE_VALID) {
flags |= MemoryByte.READABLE | MemoryByte.WRITABLE;
}
else if ((status & IMemory.ErrorOffset.BYTE_UNKNOWN) != 0) {
if (cnt > 0) break;
}
}
else if (error == null) {
flags |= MemoryByte.READABLE | MemoryByte.WRITABLE;
}
res[offs] = new MemoryByte(buf[offs], (byte)flags);
offs++;
cnt++;
}
if (offs < size) {
mem.get(address.add(BigInteger.valueOf(offs)), 1, buf, offs, size - offs, mode, this);
}
else {
mem_last = mem_data = new MemData(address, res);
setHistoryFlags();
done(res);
}
}
});
}
}.getD();
}
private void setHistoryFlags() {
if (mem_data == null) return;
BigInteger addr = mem_data.addr;
BigInteger his_start = null;
BigInteger his_end = null;
if (mem_prev != null) {
his_start = mem_prev.addr;
his_end = mem_prev.addr.add(BigInteger.valueOf(mem_prev.data.length));
}
for (MemoryByte b : mem_data.data) {
int flags = b.getFlags();
if (mem_prev != null && addr.compareTo(his_start) >= 0 && addr.compareTo(his_end) < 0) {
flags |= MemoryByte.HISTORY_KNOWN;
int offs = addr.subtract(his_start).intValue();
if (b.getValue() != mem_prev.data[offs].getValue()) {
flags |= MemoryByte.CHANGED;
}
}
else {
flags &= ~(MemoryByte.HISTORY_KNOWN | MemoryByte.CHANGED);
}
b.setFlags((byte)flags);
addr = addr.add(BigInteger.valueOf(1));
}
}
public MemoryByte[] getBytesFromOffset(BigInteger offset, long units) throws DebugException {
return getBytesFromAddress(getBigBaseAddress().add(offset), units);
}
public String getExpression() {
return expression;
}
public IMemoryBlockRetrieval getMemoryBlockRetrieval() {
return TCFMemoryBlockRetrieval.this;
}
public long getStartAddress() {
return 0; // Unbounded
}
public long getLength() {
return length;
}
public BigInteger getMemoryBlockStartAddress() throws DebugException {
return null; // Unbounded
}
public BigInteger getMemoryBlockEndAddress() throws DebugException {
return null; // Unbounded
}
public BigInteger getBigLength() throws DebugException {
return BigInteger.valueOf(length);
}
public void setBaseAddress(BigInteger address) throws DebugException {
}
public void setValue(BigInteger offset, final byte[] bytes) throws DebugException {
final BigInteger address = getBigBaseAddress().add(offset);
new TCFDebugTask<Object>(exec_ctx.getChannel()) {
public void run() {
if (exec_ctx.isDisposed()) {
error("Context is disposed");
return;
}
TCFDataCache<IMemory.MemoryContext> cache = exec_ctx.getMemoryContext();
if (!cache.validate(this)) return;
if (cache.getError() != null) {
error(cache.getError());
return;
}
final IMemory.MemoryContext mem = cache.getData();
if (mem == null) {
error("Context does not provide memory access");
return;
}
final int mode = IMemory.MODE_CONTINUEONERROR | IMemory.MODE_VERIFY;
mem.set(address, 1, bytes, 0, bytes.length, mode, new IMemory.DoneMemory() {
public void doneMemory(IToken token, MemoryError error) {
if (error != null) {
error(error);
}
else {
done(null);
}
}
});
}
}.getD();
}
public boolean supportBaseAddressModification() throws DebugException {
return false;
}
public boolean supportsChangeManagement() {
return true;
}
public byte[] getBytes() throws DebugException {
if (mem_data == null) return null;
return mem_data.bytes;
}
public void setValue(long offset, byte[] bytes) throws DebugException {
setValue(BigInteger.valueOf(offset), bytes);
}
public boolean supportsValueModification() {
return true;
}
public IDebugTarget getDebugTarget() {
return null;
}
public ILaunch getLaunch() {
return exec_ctx.model.getLaunch();
}
public String getModelIdentifier() {
return ITCFConstants.ID_TCF_DEBUG_MODEL;
}
@Override
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
if (adapter == IMemoryBlockRetrieval.class) return TCFMemoryBlockRetrieval.this;
if (adapter == IMemoryBlockRetrievalExtension.class) return TCFMemoryBlockRetrieval.this;
return super.getAdapter(adapter);
}
}
TCFMemoryBlockRetrieval(TCFNodeExecContext exec_ctx) {
this.exec_ctx = exec_ctx;
}
public IMemoryBlockExtension getExtendedMemoryBlock(final String expression, Object context) throws DebugException {
return new TCFDebugTask<IMemoryBlockExtension>() {
public void run() {
done(new MemoryBlock(expression, -1));
}
}.getD();
}
public IMemoryBlock getMemoryBlock(final long address, final long length) throws DebugException {
return new TCFDebugTask<IMemoryBlockExtension>() {
public void run() {
done(new MemoryBlock("0x" + Long.toHexString(address), length));
}
}.getD();
}
public boolean supportsStorageRetrieval() {
return true;
}
public String getMemoryID() {
return exec_ctx.id;
}
void onMemoryChanged(boolean suspended) {
assert Protocol.isDispatchThread();
if (mem_blocks.size() == 0) return;
ArrayList<DebugEvent> list = new ArrayList<DebugEvent>();
for (MemoryBlock b : mem_blocks) {
if (suspended) b.mem_prev = b.mem_last;
b.mem_data = null;
list.add(new DebugEvent(b, DebugEvent.CHANGE, DebugEvent.CONTENT));
}
DebugPlugin.getDefault().fireDebugEventSet(list.toArray(new DebugEvent[list.size()]));
}
}