blob: 4339d95f5a0001dbfa61ab20345fc518258c1126 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 Nokia 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:
* Nokia - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.debug.edc.services;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.debug.edc.disassembler.CodeBufferUnderflowException;
import org.eclipse.cdt.debug.edc.disassembler.DisassembledInstruction;
import org.eclipse.cdt.debug.edc.disassembler.EDCInstruction;
import org.eclipse.cdt.debug.edc.disassembler.EDCInstructionFunctionInfo;
import org.eclipse.cdt.debug.edc.disassembler.EDCMixedInstruction;
import org.eclipse.cdt.debug.edc.disassembler.IDisassembledInstruction;
import org.eclipse.cdt.debug.edc.disassembler.IDisassembler;
import org.eclipse.cdt.debug.edc.internal.EDCDebugger;
import org.eclipse.cdt.debug.edc.internal.services.dsf.EDCServicesMessages;
import org.eclipse.cdt.debug.edc.launch.EDCLaunch;
import org.eclipse.cdt.debug.edc.symbols.ILineEntry;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.debug.service.IDisassembly;
import org.eclipse.cdt.dsf.debug.service.IInstruction;
import org.eclipse.cdt.dsf.debug.service.IMemory;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.debug.service.IMixedInstruction;
import org.eclipse.cdt.dsf.debug.service.IModules;
import org.eclipse.cdt.dsf.debug.service.IModules.AddressRange;
import org.eclipse.cdt.dsf.debug.service.IModules.ISymbolDMContext;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.utils.Addr64;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.tm.tcf.services.IMemory.MemoryError;
public class Disassembly extends AbstractEDCService implements IDisassembly {
/**
* @param classNames
* the type names the service will be registered under. See
* AbstractDsfService#register for details. We tack on base DSF's
* IDisassembly and this class to the list if not provided.
* @since 2.0
*/
public Disassembly(DsfSession session, String[] classNames) {
super(session,
massageClassNames(classNames,
new String[] { IDisassembly.class.getName(), Disassembly.class.getName() }));
}
/**
* @return IStatus.ERROR containing NLS string indicating disassembler not yet available
* @since 2.0
*/
public static IStatus statusNoDisassembler() {
return new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED,
EDCServicesMessages.Disassembly_NoDisassemblerYet, null);
}
/**
* @param memoryAt 1st location of unreadable memory
* @param memoryLength length of unreadable memory
* @return IStatus.ERROR containing formatted message with location & length
*/
private static IStatus statusCannotReadMemory(String memoryAt, Integer memoryLength) {
return new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED,
cantReadMemory(memoryAt, memoryLength.toString()), null);
}
/**
* string for use in both status for error return and in
* pseudo-instruction used in error recovery mode for disassembly view
* @param memoryAt 1st location of unreadable memory
* @param memoryLength length of unreadable memory (incoming null will "unknown" for length)
* @return formatted message with location & length
*/
private static String cantReadMemory(String memoryAt, String memoryLength) {
memoryLength = (memoryLength == null) ? "unknown" : memoryLength;
return MessageFormat.format(EDCServicesMessages.Disassembly_CannotReadMemoryAt,
memoryAt, memoryLength);
}
/**
* check each byte of incoming MemoryByte[] array to see if readable;
* fills the RequestMonitor status in case of unreadable bytes.
* @param memBytes data to translate
* @param start address of first byte of memBytes
* @param rm with which to set status in case of error
* @return code buffer translated from incoming memByte array, null if any are unreadable
* @since 2.0
*/
public static ByteBuffer translateMemoryBytes(MemoryByte[] memBytes,
IAddress start, RequestMonitor rm) {
byte[] bytes = new byte[memBytes.length];
for (int i = 0; i < memBytes.length; i++) {
// check each byte
if (!memBytes[i].isReadable()) {
rm.setStatus(statusCannotReadMemory(start.add(i).toHexAddressString(),
memBytes.length-i));
rm.done();
return null;
}
bytes[i] = memBytes[i].getValue();
}
return ByteBuffer.wrap(bytes);
}
/**
* return a buffer of instruction code whose readability matches the
* first byte of the data for as long as all such bytes are the same
* @param memBytes data to translate
* @return a code buffer either full of readable bytes
* or empty representing the unreadable region
*/
@SuppressWarnings("null") // memByte can't be null if memByteReadable is true
private ByteBuffer translateMemoryBytes(List<MemoryByte> memBytes) {
int count = 0;
byte[] bytes = new byte[memBytes.size()];
MemoryByte firstByte = memBytes.get(0);
boolean firstIsReadable = firstByte != null && firstByte.isReadable();
for (MemoryByte memByte : memBytes) {
boolean memByteReadable = memByte != null && memByte.isReadable();
// check each byte
if (memByteReadable != firstIsReadable)
break;
bytes[count++] = memByteReadable ? memByte.getValue() : 0;
}
return ByteBuffer.wrap(bytes, 0, count);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IDisassembly#getInstructions(org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext, java.math.BigInteger, java.math.BigInteger, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
public void getInstructions(final IDisassemblyDMContext context, BigInteger startAddress, BigInteger endAddress,
final DataRequestMonitor<IInstruction[]> drm) {
// FIXME: ignoring null startAddress and null endAddress semantics
ITargetEnvironment env = getTargetEnvironmentService();
final IDisassembler disassembler = (env != null) ? env.getDisassembler() : null;
if (disassembler == null) {
drm.setStatus(statusNoDisassembler());
drm.done();
return;
}
DsfServicesTracker services = getServicesTracker();
if (services == null) // could be null if async invoked as or after debug session ends
return;
IMemory memoryService = services.getService(IMemory.class);
if (memoryService == null) // could be null if async invoked as or after debug session ends
return;
final int size = endAddress.intValue() - startAddress.intValue() + 16;
final IMemoryDMContext mem_dmc = DMContexts.getAncestorOfType(context, IMemoryDMContext.class);
final IAddress start = new Addr64(startAddress);
memoryService.getMemory(mem_dmc, start, 0, 1, size,
new DataRequestMonitor<MemoryByte[]>(getExecutor(), drm) {
/**
* overridden to create a non-error status plus pseudoInstruction data-set
* in the DRM requested by DisassemblyBackendDsf, where a DRM.status of
* ERROR is turned into an "invalid" block in its document map. Such
* blocks are repeatedly re-requested ... causing scrolling oddities & performance issues.
* @see failedMemoryDsfPseudoInstructions
*/
@Override
protected void handleError() {
IStatus s = getStatus();
Throwable e = s.getException();
if (e instanceof MemoryError && s.getMessage().contains("Fail to read memory")) {
drm.setData(failedMemoryDsfPseudoInstructions(start, size, s.getMessage()));
drm.done();
} else {
super.handleError();
}
}
@Override
protected void handleSuccess() {
List<MemoryByte> memBytes = Arrays.asList(getData());
Map<String, Object> options = new HashMap<String, Object>();
try {
ArrayList<IInstruction> instrs
= fillDisassemblyViewInstructions(memBytes, start, context,
disassembler, options);
drm.setData(instrs.toArray(new IInstruction[instrs.size()]));
} catch (CoreException e) {
drm.setStatus(e.getStatus());
}
drm.done();
}
});
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IDisassembly#getInstructions(org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext, java.lang.String, int, int, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
public void getInstructions(final IDisassemblyDMContext context, String filename, int linenum, final int lines,
final DataRequestMonitor<IInstruction[]> drm) {
// FIXME: ignoring "lines" semantics
IModules modulesService = getServicesTracker().getService(IModules.class);
ISymbolDMContext sym_dmc = DMContexts.getAncestorOfType(context, ISymbolDMContext.class);
filename = EDCLaunch.getLaunchForSession(getSession().getId()).getCompilationPath(filename);
modulesService.calcAddressInfo(sym_dmc, filename, linenum, 0,
new DataRequestMonitor<AddressRange[]>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
AddressRange[] addr_ranges = getData();
IAddress start = addr_ranges[0].getStartAddress();
IAddress end = start.add(lines * 4); // kind of arbitrary end
// address hint.
getInstructions(context, start.getValue(), end.getValue(), drm);
}
});
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IDisassembly#getMixedInstructions(org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext, java.math.BigInteger, java.math.BigInteger, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
public void getMixedInstructions(final IDisassemblyDMContext context, BigInteger startAddress, BigInteger endAddress,
final DataRequestMonitor<IMixedInstruction[]> drm) {
// FIXME: ignoring null startAddress and null endAddress semantics
DsfServicesTracker services = getServicesTracker();
if (services == null) // could be null if async invoked as or after debug session ends
return;
IEDCSymbols symbolsService = services.getService(IEDCSymbols.class);
if (symbolsService == null) // could be null if async invoked as or after debug session ends
return;
IEDCModules modulesService = services.getService(IEDCModules.class);
if (modulesService == null) // could be null if async invoked as or after debug session ends
return;
final ISymbolDMContext sym_dmc = DMContexts.getAncestorOfType(context, ISymbolDMContext.class);
// These are absolute runtime addresses.
final IAddress end = new Addr64(endAddress);
final IAddress start
= getStartAddressForLineEntryContainingAddress(symbolsService, modulesService, sym_dmc,
new Addr64(startAddress), end);
ILineEntry startEntry = symbolsService.getLineEntryForAddress(sym_dmc, start);
if (startEntry == null) {
// startAddress has no source
getInstructions(context, startAddress, endAddress,
new DataRequestMonitor<IInstruction[]>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
IMixedInstruction[] ret = new IMixedInstruction[1];
ret[0] = new EDCMixedInstruction("unknown", 0, getData()); //$NON-NLS-1$
drm.setData(ret);
drm.done();
}
});
} else { // there is source for start address.
final IEDCModuleDMContext module = modulesService.getModuleByAddress(sym_dmc, start);
final List<ILineEntry> codeLines = symbolsService.getLineEntriesForAddressRange(sym_dmc, start, end);
getInstructions(context, startAddress, endAddress,
new DataRequestMonitor<IInstruction[]>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
List<IMixedInstruction> mixedInstructions = new ArrayList<IMixedInstruction>();
mixSource(mixedInstructions, null, module, codeLines, getData());
drm.setData(mixedInstructions.toArray(new IMixedInstruction[mixedInstructions.size()]));
drm.done();
}
});
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IDisassembly#getMixedInstructions(org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext, java.lang.String, int, int, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
public void getMixedInstructions(final IDisassemblyDMContext context, String filename, final int linenum,
final int lines, final DataRequestMonitor<IMixedInstruction[]> drm) {
// FIXME: ignoring "lines" semantics
final ITargetEnvironment env = getTargetEnvironmentService();
final IDisassembler disassembler = (env != null) ? env.getDisassembler() : null;
if (disassembler == null) {
drm.setStatus(statusNoDisassembler());
drm.done();
return;
}
IModules modulesService = getServicesTracker().getService(IModules.class);
ISymbolDMContext sym_dmc = DMContexts.getAncestorOfType(context, ISymbolDMContext.class);
filename = EDCLaunch.getLaunchForSession(getSession().getId()).getCompilationPath(filename);
modulesService.calcAddressInfo(sym_dmc, filename, linenum, 0,
new DataRequestMonitor<AddressRange[]>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
AddressRange[] addr_ranges = getData();
IAddress start = addr_ranges[0].getStartAddress();
IAddress end = start.add(lines * 4); // kind of arbitrary end address hint.
getMixedInstructions(context, start.getValue(), end.getValue(), drm);
}
});
}
private static EDCInstruction pseudoInstruction(IAddress address, int size, String pseudoMnemonic) {
DisassembledInstruction pseudoInstruction = new DisassembledInstruction();
pseudoInstruction.setAddress(address);
pseudoInstruction.setSize(size);
pseudoInstruction.setMnemonics(pseudoMnemonic);
return new EDCInstruction(pseudoInstruction);
}
/**
* used in failedMemoryDsfPseudoInstructions() below as chunk boundary for
* each of the pseudoInstructions in a larger chunk of retrieved memory.
*/
private static final int asmFence = 0x20;
/**
* this utility function creates pseudo-mnemonics indicating failed memory
* read. it was refactored from memoryService.getMemory().handleError() so
* that it could be utilized by subclass override getInstructions() methods
* making the same memoryService.getMemory() call.
* <p>
* <i>background:</i>
* <p>
* as of 2010.oct.01, EDC memoryService no longer caches blocks of memory that
* cannot be read (a correct change, given that this was blocking the caching
* of good memory on the boundaries of such blocks, causing other problems).
* <p>
* when this change was made, Disassembly#fillDisassemblyViewInstructions()
* stopped getting reached through memoryService.getMemory().handleSuccess() .
* therefore, actual bad blocks of memory were not getting filled with pseudo
* mnemonics indicating bad memory . in other words, when "Fail to read memory"
* errors from TCF caused invocation of memoryService.getMemory().handleFailure()
* ... and thus eventually also handleError() ... the result was that
* DsfBackendDisassembly would fill the DisassemblyDocument with "invalid"
* sections based upon address but no size. it's algorithm then later attempts
* to fill any missing/invalid sections corresponding with the document. this
* results in a visual anomaly where "Unable to retrieve disassembly" would be
* populated in the DisassemblyView one block at a time, until there would be a
* large, mostly useless portion of the document view populated with the same
* message repeated once for every byte the user had attempted to scroll to.
* <p>
* the handleError() override implementations that call this utility function
* solve the problem whereby DisassemblyBackendDsf interprets DRM.status as
* "invalid" sections in its the DisassemblyDocument it is associated with.
* <p>
* the point of this re-factored code is to create "fences" on regular
* boundaries so that chunks of failed memory always get placed on similar
* boundaries, thus drastically ameliorating the occurrence of small "invalid"
* chunks in the DisassemblyDocument map between chunks of pseudoInstructions
* that DisassemblyBackendDsf considers "valid".
* <p>
* in user terms, this means that scrolling in the view is more consistent
* and even, with better performance thanks to fewer attempts to re-retrieve
* memory for small "invalid" sections in its map at the boundaries of
* previously inserted pseudo-instructions.
*
* @param size size of the chunk to break up
* @param start location of memory chunk
* @param msg message from the target agent
* @return array containing 1 or more pseudo-instructions, mostly on asmFence boundaries
* @since 2.0
*/
protected static IInstruction[] failedMemoryDsfPseudoInstructions(
IAddress start, final int size, String msg) {
ArrayList<IInstruction> pseudoInstr = new ArrayList<IInstruction>();
int offset = 0, chunkSize = Math.min(size, asmFence - start.getValue().intValue() % asmFence);
do {
pseudoInstr.add(pseudoInstruction(start.add(offset), chunkSize,
msg + "..[length=" + chunkSize + ']'));
offset += chunkSize;
chunkSize = Math.min(asmFence, size-offset);
} while (offset < size);
return pseudoInstr.toArray(new IInstruction[pseudoInstr.size()]);
}
/**
* Creates the array of instructions to be used to fill the disassembly view.
* for a range containing any unreadable instructions, it will create a
* fake instruction consisting of the address, the entire unreadable range,
* and a message to fill in the mnemonics section about the unreadable range.
* @param memBytes the buffer containing the bytes to be disassembled
* @param start starting address corresponding to the buffer
* @param context context for disassembly
* @param disassembler the disassembler object to use
* @param options to be passed to the disassembleInstructions() call
* @return array of IInstruction
* @throws CoreException can be thrown by disassembleInstructions().
* @since 2.0
*/
protected ArrayList<IInstruction> fillDisassemblyViewInstructions(
final List<MemoryByte> memBytes, final IAddress start,
final IDisassemblyDMContext context, final IDisassembler disassembler,
Map<String, Object> options)
throws CoreException {
ArrayList<IInstruction> ret = new ArrayList<IInstruction>();
for (int offset = 0, last = memBytes.size(); offset < last ;) {
ByteBuffer codeBuf = translateMemoryBytes(memBytes.subList(offset, last));
int codeBufSize = codeBuf.limit();
IAddress block = start.add(offset);
MemoryByte firstByte = memBytes.get(offset);
if (firstByte != null && firstByte.isReadable()) {
try {
List<IDisassembledInstruction> insts
= disassembler.disassembleInstructions(block, block.add(codeBufSize),
codeBuf, options, context);
if (insts.size() == 0)
break;
for (int i = 0; i < insts.size(); i++) {
ret.add(new EDCInstruction(insts.get(i)));
offset += insts.get(i).getSize();
}
} catch (CodeBufferUnderflowException e) {
if (offset == 0 && codeBufSize == last) {
// nothing in entire block can be disassembled;
// at least tell the Disassembly view code this much
ret.add(pseudoInstruction(start, codeBufSize,
"Buffer Underflow during disassembly")); //$NON-NLS-1$
}
offset += codeBufSize;
}
} else { // this will only occur when the target supports partial bad blocks
ret.add(pseudoInstruction(block, codeBufSize,
cantReadMemory(block.toHexAddressString(),
((Integer)codeBufSize).toString())));
offset += codeBufSize;
}
}
return ret;
}
/**
* whereas the default implementation of getMixedInstructions() gets and
* processes all the disassembly in one pass, some variants are known to need
* to override the default implementation to break the retrieval into chunks.
* <p>
* this portion of the mixing is still the same within those chunks, though,
* and so has been extracted and protected for use by extending variant classes.
* @param mixedInstructions the growing list of instructions being processed
* @param wholeFunctionName name for whole function; if null, will be calculated per line
* @param module the module for this block of code
* @param codeLines list of ILineEntry containing info for mixing
* @param instructions the instructions to be mixed with the source
* @since 2.0
*/
protected void mixSource(List<IMixedInstruction> mixedInstructions,
final EDCInstructionFunctionInfo wholeBlockInfo,
final IEDCModuleDMContext module,
final List<ILineEntry> codeLines,
final IInstruction[] instructions) {
List<IInstruction> instsForLine = new ArrayList<IInstruction>();
IPath filePath = null;
String osString = null;
EDCInstructionFunctionInfo functionInfo = wholeBlockInfo;
int k = 0, instsCnt = instructions.length, lineCnt = codeLines.size();
for (int i = 0; i < lineCnt && k < instsCnt; i++) {
// Now map the instructions to source lines to generate
// MixedInstructions.
instsForLine.clear();
ILineEntry line = codeLines.get(i);
if (wholeBlockInfo == null && module != null) {
functionInfo = new EDCInstructionFunctionInfo(module, line);
}
while (k < instsCnt) {
EDCInstruction inst = (EDCInstruction)instructions[k];
IAddress linkAddress = module.toLinkAddress(new Addr64(inst.getAdress()));
if (linkAddress.compareTo(line.getHighAddress()) >= 0)
break;
if (functionInfo != null) {
inst.setFunctionName(functionInfo.getFunctionName());
IAddress functionBase = functionInfo.getFunctionStartAddress();
if (functionBase != null) {
inst.setOffset(functionBase.distanceTo(linkAddress).intValue());
}
}
instsForLine.add(inst);
k++;
}
if (line.getFilePath() != filePath) {
filePath = line.getFilePath();
osString = (filePath != null) ? filePath.toOSString() : null;
}
mixedInstructions.add(new EDCMixedInstruction(osString, line.getLineNumber(),
instsForLine.toArray(new IInstruction[instsForLine.size()])));
}
}
/**
* disassembly utility function to find the first address for a line-entry
* for an address contained by that line-entry
* @param symbolsService
* @param modulesService
* @param sym_dmc symbol context used to retrieve LineEntry for address
* @param initialStartAddress the address of interest
* @param endAddress the last address of a range to search
* @return the first address of an associated LineEntry if it can be found, else the initialStartAddress
* @since 2.0
*/
protected static IAddress getStartAddressForLineEntryContainingAddress(final IEDCSymbols symbolsService,
final IEDCModules modulesService, final ISymbolDMContext sym_dmc, final IAddress initialStartAddress,
final IAddress endAddress) {
assert symbolsService != null && modulesService != null;
if (symbolsService == null || modulesService == null)
return initialStartAddress;
if (sym_dmc != null) {
// in case the caller requested a start that falls somewhere other than the
// boundary of an instruction, back up to that boundary for the first instruction
if (symbolsService.getLineEntryForAddress(sym_dmc, initialStartAddress) != null) {
IEDCModuleDMContext module = modulesService.getModuleByAddress(sym_dmc, initialStartAddress);
if (module != null) {
List<ILineEntry> allLines
= symbolsService.getLineEntriesForAddressRange(sym_dmc, initialStartAddress, endAddress);
if (allLines != null && !allLines.isEmpty())
{
return module.toRuntimeAddress(allLines.get(0).getLowAddress());
}
}
}
}
return initialStartAddress;
}
}