/******************************************************************************* | |
* Copyright (c) 2009, 2010, 2011 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 | |
* Broadcom - Refactored ForwardTypeReference to separate top-level class | |
* Optimized fetchCompileUnitHeader() | |
*******************************************************************************/ | |
package org.eclipse.cdt.debug.edc.internal.symbols.dwarf; | |
import java.io.IOException; | |
import java.lang.reflect.Array; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.SortedMap; | |
import java.util.TreeMap; | |
import java.util.WeakHashMap; | |
import org.eclipse.cdt.core.IAddress; | |
import org.eclipse.cdt.debug.edc.IStreamBuffer; | |
import org.eclipse.cdt.debug.edc.internal.EDCDebugger; | |
import org.eclipse.cdt.debug.edc.internal.symbols.IForwardTypeReference; | |
import org.eclipse.cdt.debug.edc.internal.symbols.Scope; | |
import org.eclipse.cdt.debug.edc.internal.symbols.dwarf.DwarfFrameRegisterProvider.CommonInformationEntry; | |
import org.eclipse.cdt.debug.edc.internal.symbols.dwarf.DwarfFrameRegisterProvider.FrameDescriptionEntry; | |
import org.eclipse.cdt.debug.edc.services.IFrameRegisterProvider; | |
import org.eclipse.cdt.debug.edc.symbols.ICompileUnitScope; | |
import org.eclipse.cdt.debug.edc.symbols.IDebugInfoProvider; | |
import org.eclipse.cdt.debug.edc.symbols.IExecutableSymbolicsReader; | |
import org.eclipse.cdt.debug.edc.symbols.IFunctionScope; | |
import org.eclipse.cdt.debug.edc.symbols.IModuleScope; | |
import org.eclipse.cdt.debug.edc.symbols.IRangeList; | |
import org.eclipse.cdt.debug.edc.symbols.IRangeList.Entry; | |
import org.eclipse.cdt.debug.edc.symbols.IScope; | |
import org.eclipse.cdt.debug.edc.symbols.IType; | |
import org.eclipse.cdt.debug.edc.symbols.IVariable; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
/** | |
* This class handles the low-level aspects of reading DWARF data. | |
* There exists one provider per symbol file. | |
*/ | |
public class DwarfDebugInfoProvider implements IDebugInfoProvider { | |
static public class CompilationUnitHeader { | |
int length; | |
short version; | |
int abbreviationOffset; | |
byte addressSize; | |
int debugInfoOffset; | |
DwarfCompileUnit scope; | |
@Override | |
public String toString() { | |
StringBuffer sb = new StringBuffer(); | |
sb.append("Offset: " + debugInfoOffset).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
sb.append("Length: " + length).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
sb.append("Version: " + version).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
sb.append("Abbreviation: " + abbreviationOffset).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
sb.append("Address size: " + addressSize).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
return sb.toString(); | |
} | |
} | |
static class AbbreviationEntry { | |
short tag; | |
ArrayList<Attribute> attributes; | |
boolean hasChildren; | |
AbbreviationEntry(long code, short tag, boolean hasChildren) { | |
// abbreviation code not stored | |
this.tag = tag; | |
this.hasChildren = hasChildren; | |
attributes = new ArrayList<Attribute>(); | |
} | |
} | |
static class Attribute { | |
short tag; | |
byte form; | |
Attribute(short tag, byte form) { | |
this.tag = tag; | |
this.form = form; | |
} | |
@Override | |
public String toString() { | |
StringBuffer sb = new StringBuffer(); | |
sb.append("tag: " + Long.toHexString(tag)); //$NON-NLS-1$ | |
sb.append(" form: " + Long.toHexString(form)); //$NON-NLS-1$ | |
return sb.toString(); | |
} | |
} | |
static class AttributeValue { | |
private Object value; | |
// for indirect form, this is the actual form | |
private byte actualForm; | |
AttributeValue(byte form, IStreamBuffer in, byte addressSize, IStreamBuffer debugStrings) { | |
actualForm = form; | |
try { | |
value = readAttribute(in, addressSize, debugStrings); | |
} catch (IOException e) { | |
EDCDebugger.getMessageLogger().logError(null, e); | |
} | |
} | |
@Override | |
public String toString() { | |
StringBuffer sb = new StringBuffer(); | |
if (value != null) { | |
Class<? extends Object> clazz = value.getClass(); | |
if (clazz.isArray()) { | |
int len = Array.getLength(value); | |
sb.append(len).append(' '); | |
sb.append(clazz.getComponentType().toString()); | |
sb.append(':'); | |
for (int i = 0; i < len; i++) { | |
byte b = Array.getByte(value, i); | |
sb.append(' ').append(Integer.toHexString(b)); | |
} | |
} else { | |
if (value instanceof Number) { | |
Number n = (Number) value; | |
sb.append(Long.toHexString(n.longValue())); | |
} else if (value instanceof String) { | |
sb.append(value); | |
} else { | |
sb.append(value); | |
} | |
} | |
} | |
return sb.toString(); | |
} | |
private Object readAttribute(IStreamBuffer in, byte addressSize, IStreamBuffer debugStrings) throws IOException { | |
Object obj = null; | |
switch (actualForm) { | |
case DwarfConstants.DW_FORM_addr: | |
case DwarfConstants.DW_FORM_ref_addr: | |
obj = DwarfInfoReader.readAddress(in, addressSize); | |
break; | |
case DwarfConstants.DW_FORM_block: { | |
int size = (int) DwarfInfoReader.read_unsigned_leb128(in); | |
byte[] bytes = new byte[size]; | |
in.get(bytes); | |
obj = bytes; | |
} | |
break; | |
case DwarfConstants.DW_FORM_block1: { | |
int size = in.get() & 0xff; | |
byte[] bytes = new byte[size]; | |
in.get(bytes); | |
obj = bytes; | |
} | |
break; | |
case DwarfConstants.DW_FORM_block2: { | |
int size = in.getShort(); | |
byte[] bytes = new byte[size]; | |
in.get(bytes); | |
obj = bytes; | |
} | |
break; | |
case DwarfConstants.DW_FORM_block4: { | |
int size = in.getInt(); | |
byte[] bytes = new byte[size]; | |
in.get(bytes); | |
obj = bytes; | |
} | |
break; | |
case DwarfConstants.DW_FORM_data1: | |
obj = new Byte(in.get()); | |
break; | |
case DwarfConstants.DW_FORM_data2: | |
obj = new Short(in.getShort()); | |
break; | |
case DwarfConstants.DW_FORM_data4: | |
obj = new Integer(in.getInt()); | |
break; | |
case DwarfConstants.DW_FORM_data8: | |
obj = new Long(in.getLong()); | |
break; | |
case DwarfConstants.DW_FORM_sdata: | |
obj = new Long(DwarfInfoReader.read_signed_leb128(in)); | |
break; | |
case DwarfConstants.DW_FORM_udata: | |
obj = new Long(DwarfInfoReader.read_unsigned_leb128(in)); | |
break; | |
case DwarfConstants.DW_FORM_string: { | |
int c; | |
StringBuffer sb = new StringBuffer(); | |
while ((c = (in.get() & 0xff)) != -1) { | |
if (c == 0) { | |
break; | |
} | |
sb.append((char) c); | |
} | |
obj = sb.toString(); | |
} | |
break; | |
case DwarfConstants.DW_FORM_flag: | |
obj = new Byte(in.get()); | |
break; | |
case DwarfConstants.DW_FORM_strp: { | |
int offset = in.getInt(); | |
if (debugStrings == null) { | |
obj = new String(); | |
} else if (offset < 0 || offset > debugStrings.capacity()) { | |
obj = new String(); | |
} else { | |
debugStrings.position(offset); | |
obj = DwarfInfoReader.readString(debugStrings); | |
} | |
} | |
break; | |
case DwarfConstants.DW_FORM_ref1: | |
obj = new Integer(in.get() & 0xff); | |
break; | |
case DwarfConstants.DW_FORM_ref2: | |
obj = new Integer(in.getShort() & 0xffff); | |
break; | |
case DwarfConstants.DW_FORM_ref4: | |
obj = new Integer(in.getInt()); | |
break; | |
case DwarfConstants.DW_FORM_ref8: | |
obj = new Long(in.getLong()); | |
break; | |
case DwarfConstants.DW_FORM_ref_udata: | |
obj = new Long(DwarfInfoReader.read_unsigned_leb128(in)); | |
break; | |
case DwarfConstants.DW_FORM_indirect: { | |
actualForm = (byte) DwarfInfoReader.read_unsigned_leb128(in); | |
return readAttribute(in, addressSize, debugStrings); | |
} | |
default: | |
assert (false); | |
break; | |
} | |
return obj; | |
} | |
/** | |
* @param attr | |
* @param in | |
* @param addressSize | |
* @param debugStrings | |
*/ | |
public static void skipAttributeValue(short form, IStreamBuffer in, | |
byte addressSize) throws IOException { | |
switch (form) { | |
case DwarfConstants.DW_FORM_addr: | |
case DwarfConstants.DW_FORM_ref_addr: | |
in.position(in.position() + addressSize); | |
break; | |
case DwarfConstants.DW_FORM_block: { | |
int size = (int) DwarfInfoReader.read_unsigned_leb128(in); | |
in.position(in.position() + size); | |
} | |
break; | |
case DwarfConstants.DW_FORM_block1: { | |
int size = in.get() & 0xff; | |
in.position(in.position() + size); | |
} | |
break; | |
case DwarfConstants.DW_FORM_block2: { | |
int size = in.getShort(); | |
in.position(in.position() + size); | |
} | |
break; | |
case DwarfConstants.DW_FORM_block4: { | |
int size = in.getInt(); | |
in.position(in.position() + size); | |
} | |
break; | |
case DwarfConstants.DW_FORM_data1: | |
in.position(in.position() + 1); | |
break; | |
case DwarfConstants.DW_FORM_data2: | |
in.position(in.position() + 2); | |
break; | |
case DwarfConstants.DW_FORM_data4: | |
in.position(in.position() + 4); | |
break; | |
case DwarfConstants.DW_FORM_data8: | |
in.position(in.position() + 8); | |
break; | |
case DwarfConstants.DW_FORM_sdata: | |
DwarfInfoReader.read_signed_leb128(in); | |
break; | |
case DwarfConstants.DW_FORM_udata: | |
DwarfInfoReader.read_unsigned_leb128(in); | |
break; | |
case DwarfConstants.DW_FORM_string: { | |
int c; | |
while ((c = (in.get() & 0xff)) != -1) { | |
if (c == 0) { | |
break; | |
} | |
} | |
} | |
break; | |
case DwarfConstants.DW_FORM_flag: | |
in.position(in.position() + 1); | |
break; | |
case DwarfConstants.DW_FORM_strp: | |
in.position(in.position() + 4); | |
break; | |
case DwarfConstants.DW_FORM_ref1: | |
in.position(in.position() + 1); | |
break; | |
case DwarfConstants.DW_FORM_ref2: | |
in.position(in.position() + 2); | |
break; | |
case DwarfConstants.DW_FORM_ref4: | |
in.position(in.position() + 4); | |
break; | |
case DwarfConstants.DW_FORM_ref8: | |
in.position(in.position() + 8); | |
break; | |
case DwarfConstants.DW_FORM_ref_udata: | |
DwarfInfoReader.read_unsigned_leb128(in); | |
break; | |
case DwarfConstants.DW_FORM_indirect: { | |
form = (short) DwarfInfoReader.read_unsigned_leb128(in); | |
skipAttributeValue(form, in, addressSize); | |
break; | |
} | |
default: | |
assert (false); | |
break; | |
} | |
} | |
/** | |
* Parse attributes and then skip to sibling, if any | |
* | |
* @param names array to hold up to two names | |
* @param entry debug info entry | |
* @param in buffer stream of debug info | |
* @param addressSize | |
* @param debugStrings | |
* @return DW_AT_name value, or null if there is no or invalid DW_AT_name attribute | |
*/ | |
public static void skipAttributesToSibling(AbbreviationEntry entry, IStreamBuffer in, byte addressSize) { | |
long sibling = -1; | |
// go through the attributes and throw away everything except the sibling | |
int len = entry.attributes.size(); | |
for (int i = 0; i < len; i++) { | |
Attribute attr = entry.attributes.get(i); | |
try { | |
if (attr.tag == DwarfConstants.DW_AT_sibling) { | |
if (attr.form == DwarfConstants.DW_FORM_ref_udata) { | |
sibling = DwarfInfoReader.read_unsigned_leb128(in); | |
} else if (attr.form == DwarfConstants.DW_FORM_ref4) { | |
sibling = in.getInt(); | |
} else { | |
// TODO: allow other forms for sibling value | |
AttributeValue.skipAttributeValue(attr.form, in, addressSize); | |
} | |
} else { | |
AttributeValue.skipAttributeValue(attr.form, in, addressSize); | |
} | |
} catch (IOException e) { | |
EDCDebugger.getMessageLogger().logError(null, e); | |
break; | |
} | |
} | |
if (sibling != -1) | |
in.position(sibling); | |
} | |
public byte getActualForm() { | |
return actualForm; | |
} | |
/** | |
* Get the value as a 64-bit signed long, sign-extending any shorter attribute | |
* @return value as signed long | |
*/ | |
public long getValueAsSignedLong() { | |
if (value instanceof Number) { | |
return ((Number) value).longValue(); | |
} | |
return 0; | |
} | |
/** | |
* Get the value as a 64-bit long. | |
* | |
* A Byte, Short, or Integer is zero-extended. | |
* | |
* @return value as long | |
*/ | |
public long getValueAsLong() { | |
if (value instanceof Byte) { | |
return ((Byte) value).byteValue() & 0xff; | |
} | |
if (value instanceof Short) { | |
return ((Short) value).shortValue() & 0xffff; | |
} | |
if (value instanceof Integer) { | |
return ((Integer) value).intValue() & 0xffffffff; | |
} | |
// fallthrough | |
if (value instanceof Number) { | |
return ((Number) value).longValue(); | |
} | |
return 0; | |
} | |
/** | |
* Get the value as a 32-bit int. | |
* | |
* A Byte or Short is zero-extended. | |
* | |
* @return value as int | |
*/ | |
public int getValueAsInt() { | |
if (value instanceof Byte) { | |
return ((Byte) value).byteValue() & 0xff; | |
} | |
if (value instanceof Short) { | |
return ((Short) value).shortValue() & 0xffff; | |
} | |
// fallthrough | |
if (value instanceof Number) { | |
return ((Number) value).intValue(); | |
} | |
return 0; | |
} | |
/** | |
* Get the value as a string | |
* @return String or "" if not a string | |
*/ | |
public String getValueAsString() { | |
if (value != null) | |
return value.toString(); | |
return null; | |
} | |
/** | |
* Get the byte array value (which is empty if this is not a byte array) | |
* @return array | |
*/ | |
public byte[] getValueAsBytes() { | |
if (value instanceof byte[]) | |
return (byte[]) value; | |
return new byte[0]; | |
} | |
} | |
static public class AttributeList { | |
Map<Short, AttributeValue> attributeMap; | |
AttributeList(AbbreviationEntry entry, IStreamBuffer in, byte addressSize, IStreamBuffer debugStrings) { | |
int len = entry.attributes.size(); | |
attributeMap = new HashMap<Short, AttributeValue>(len); | |
for (int i = 0; i < len; i++) { | |
Attribute attr = entry.attributes.get(i); | |
attributeMap.put(Short.valueOf(attr.tag), new AttributeValue(attr.form, in, addressSize, debugStrings)); | |
} | |
} | |
public static void skipAttributes(AbbreviationEntry entry, IStreamBuffer in, byte addressSize) { | |
int len = entry.attributes.size(); | |
for (int i = 0; i < len; i++) { | |
Attribute attr = entry.attributes.get(i); | |
try { | |
AttributeValue.skipAttributeValue(attr.form, in, addressSize); | |
} catch (IOException e) { | |
EDCDebugger.getMessageLogger().logError(null, e); | |
break; | |
} | |
} | |
} | |
public long getAttributeValueAsLong(short attributeName) { | |
AttributeValue attr = attributeMap.get(Short.valueOf(attributeName)); | |
if (attr != null) { | |
return attr.getValueAsLong(); | |
} | |
return 0; | |
} | |
public int getAttributeValueAsInt(short attributeName) { | |
AttributeValue attr = attributeMap.get(Short.valueOf(attributeName)); | |
if (attr != null) { | |
return attr.getValueAsInt(); | |
} | |
return 0; | |
} | |
public long getAttributeValueAsSignedLong(short attributeName) { | |
AttributeValue attr = attributeMap.get(Short.valueOf(attributeName)); | |
if (attr != null) { | |
return attr.getValueAsSignedLong(); | |
} | |
return 0; | |
} | |
public String getAttributeValueAsString(short attributeName) { | |
AttributeValue attr = attributeMap.get(Short.valueOf(attributeName)); | |
if (attr != null) { | |
return attr.getValueAsString(); | |
} | |
return ""; //$NON-NLS-1$ | |
} | |
public byte[] getAttributeValueAsBytes(short attributeName) { | |
AttributeValue attr = attributeMap.get(Short.valueOf(attributeName)); | |
if (attr != null) { | |
return attr.getValueAsBytes(); | |
} | |
return new byte[0]; | |
} | |
public AttributeValue getAttribute(short attributeName) { | |
return attributeMap.get(Short.valueOf(attributeName)); | |
} | |
/** | |
* Tell whether the attributes do not have a code range. | |
* <p> | |
* Note: a singular DW_AT_low_pc means an entry point | |
* <p> | |
* Also note: a compile unit can have code represented by DW_AT_stmt_list | |
* @return true if the attributes represent code | |
*/ | |
public boolean hasCodeRangeAttributes() { | |
return attributeMap.containsKey(DwarfConstants.DW_AT_high_pc) | |
|| attributeMap.containsKey(DwarfConstants.DW_AT_ranges); | |
} | |
} | |
static public class PublicNameInfo { | |
public final String nameWithNameSpace; | |
public final CompilationUnitHeader cuHeader; | |
public final short tag; // DW_TAG_xxx | |
public PublicNameInfo(String nameWithNameSpace, CompilationUnitHeader cuHeader, short tag) { | |
this.nameWithNameSpace = nameWithNameSpace; | |
this.cuHeader = cuHeader; | |
this.tag = tag; | |
} | |
} | |
// list of compilation units per source file | |
protected HashMap<IPath, List<ICompileUnitScope>> compileUnitsPerFile = new HashMap<IPath, List<ICompileUnitScope>>(); | |
// list of compile units in .debug_info order | |
protected ArrayList<DwarfCompileUnit> compileUnits = new ArrayList<DwarfCompileUnit>(); | |
// list of compile units with code (non-zero high address), sorted by low address | |
protected ArrayList<DwarfCompileUnit> sortedCompileUnitsWithCode = new ArrayList<DwarfCompileUnit>(); | |
// function and type declarations can be referenced by offsets relative to | |
// the compile unit or to the entire .debug_info section. therefore we keep | |
// maps by .debug_info offset, and for compile unit relative offsets, we | |
// just add the compile unit offset into the .debug_info section. | |
protected Map<Long, AttributeList> functionsByOffset = new HashMap<Long, AttributeList>(); | |
protected Map<Long, IType> typesByOffset = Collections.synchronizedMap(new HashMap<Long, IType>()); | |
// These references may eventually become pointless so weak reference them | |
protected Map<Long, IForwardTypeReference> referenceTypesByOffset | |
= Collections.synchronizedMap(new WeakHashMap<Long, IForwardTypeReference>()); | |
// for casting to a type, keep certain types by name | |
protected Map<String, List<IType>> typesByName = new HashMap<String, List<IType>>(); | |
// for casting to a type, track whether the cast name includes an aggregate designator | |
enum TypeAggregate { Class, Struct, Union, None }; | |
// map of entities which created scopes | |
protected Map<Long, Scope> scopesByOffset = new HashMap<Long, Scope>(); | |
// entry points for CUs in the .debug_info section, used to dynamically parse CUs as needed | |
protected TreeMap<Long, CompilationUnitHeader> debugOffsetsToCompileUnits = new TreeMap<Long, CompilationUnitHeader>(); | |
// forward references for tags we have not parsed yet. (These will go into typesByOffset once handled) | |
//Map<Long, ForwardDwarfDefinition> forwardDwarfDefinitions = new HashMap<Long, ForwardDwarfDefinition>(); | |
// these are just for faster lookups | |
protected Map<String, List<IFunctionScope>> functionsByName = new HashMap<String, List<IFunctionScope>>(); | |
protected Map<String, List<IVariable>> variablesByName = new HashMap<String, List<IVariable>>(); | |
protected Map<String, List<PublicNameInfo>> publicFunctions = new HashMap<String, List<PublicNameInfo>>(); | |
protected Map<String, List<PublicNameInfo>> publicVariables = new HashMap<String, List<PublicNameInfo>>(); | |
// abbreviation tables (lists of abbrev entries), mapped by .debug_abbrev offset | |
protected Map<Integer, Map<Long, AbbreviationEntry>> abbreviationMaps = new HashMap<Integer, Map<Long, AbbreviationEntry>>(); | |
// mapping of PC range to frame description entries | |
protected TreeMap<IRangeList.Entry, FrameDescriptionEntry> frameDescEntries = new TreeMap<IRangeList.Entry, FrameDescriptionEntry>(); | |
// mapping of CIE offsets to parsed common info entries | |
protected Map<Long, CommonInformationEntry> commonInfoEntries = new HashMap<Long, CommonInformationEntry>(); | |
protected Set<String> referencedFiles = new HashSet<String>(); | |
protected boolean buildReferencedFilesList = true; | |
private IPath symbolFilePath; | |
private long symbolFileLastModified; | |
private boolean parsedInitially = false; | |
private boolean parsedForVarsAndAddresses = false; | |
private boolean parsedForScopesAndAddresses = false; | |
private boolean parsedForTypes = false; | |
private boolean parsedForGlobalVars = false; | |
private final IExecutableSymbolicsReader exeReader; | |
private final DwarfModuleScope moduleScope; | |
final DwarfFileHelper fileHelper; | |
private IFrameRegisterProvider frameRegisterProvider; | |
private static String SOURCE_FILES_CACHE = "_source_files"; //$NON-NLS-1$ | |
public DwarfDebugInfoProvider(IExecutableSymbolicsReader exeReader) { | |
this.exeReader = exeReader; | |
this.symbolFilePath = exeReader.getSymbolFile(); | |
this.symbolFileLastModified = symbolFilePath.toFile().lastModified(); | |
this.moduleScope = new DwarfModuleScope(this); | |
this.fileHelper = new DwarfFileHelper(symbolFilePath); | |
this.frameRegisterProvider = new DwarfFrameRegisterProvider(this); | |
} | |
/* (non-Javadoc) | |
* @see java.lang.Object#toString() | |
*/ | |
@Override | |
public String toString() { | |
return DwarfMessages.DwarfDebugInfoProvider_DwarfProviderFor + symbolFilePath; | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.cdt.debug.edc.internal.symbols.files.IDebugInfoProvider#dispose() | |
*/ | |
public void dispose() { | |
// several views in DSF hold onto all our debug info, | |
// so go through and explicitly break links | |
if (moduleScope != null) { | |
moduleScope.dispose(); | |
} | |
// help GC | |
compileUnitsPerFile.clear(); | |
compileUnits.clear(); | |
functionsByOffset.clear(); | |
typesByOffset.clear(); | |
referenceTypesByOffset.clear(); | |
scopesByOffset.clear(); | |
debugOffsetsToCompileUnits.clear(); | |
functionsByName.clear(); | |
variablesByName.clear(); | |
publicFunctions.clear(); | |
publicVariables.clear(); | |
abbreviationMaps.clear(); | |
referencedFiles.clear(); | |
parsedInitially = false; | |
parsedForTypes = false; | |
parsedForVarsAndAddresses = false; | |
parsedForScopesAndAddresses = false; | |
fileHelper.dispose(); | |
frameRegisterProvider.dispose(); | |
} | |
void ensureParsedInitially() { | |
if (!parsedInitially) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
parsedInitially = true; | |
reader.parseInitial(); | |
} | |
} | |
void ensureParsedForScopes() { | |
if (!parsedForScopesAndAddresses) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
if (!parsedInitially) { | |
parsedInitially = true; | |
reader.parseInitial(); | |
} | |
parsedForScopesAndAddresses = true; | |
reader.parseForAddresses(false); | |
} | |
} | |
void ensureParsedForScope(IAddress linkAddress) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
if (!parsedInitially) { | |
parsedInitially = true; | |
reader.parseInitial(); | |
} | |
reader.parseForAddress(linkAddress); | |
} | |
void ensureParsedForVariables() { | |
if (!parsedForVarsAndAddresses) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
if (!parsedInitially) { | |
parsedInitially = true; | |
reader.parseInitial(); | |
} | |
parsedForVarsAndAddresses = true; | |
reader.parseForAddresses(true); | |
} | |
} | |
private void ensureParsedForGlobalVariables() { | |
if (parsedForGlobalVars) | |
return; | |
parsedForGlobalVars = true; | |
if (publicVariables.size() == 0) | |
return; | |
// determine compilation units containing globals | |
HashSet<CompilationUnitHeader> cuWithGlobalsArray = new HashSet<CompilationUnitHeader>(publicVariables.size()); | |
for (List<PublicNameInfo> infoList : publicVariables.values()) { | |
for (PublicNameInfo info : infoList) { | |
cuWithGlobalsArray.add(info.cuHeader); | |
} | |
} | |
// parse compilation units containing global variables | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
for (CompilationUnitHeader cuHeader : cuWithGlobalsArray) | |
reader.parseCompilationUnitForAddresses(cuHeader.scope); | |
} | |
void ensureParsedForTypes() { | |
if (!parsedForTypes) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
if (!parsedInitially) { | |
parsedInitially = true; | |
reader.parseInitial(); | |
} | |
parsedForTypes = true; | |
reader.parseForTypes(); | |
} | |
} | |
public void setParsedInitially() { | |
parsedInitially = true; | |
} | |
public void setParsedForAddresses() { | |
parsedForVarsAndAddresses = true; | |
} | |
public IPath getSymbolFile() { | |
return symbolFilePath; | |
} | |
public IModuleScope getModuleScope() { | |
return moduleScope; | |
} | |
public IAddress getBaseLinkAddress() { | |
return exeReader.getBaseLinkAddress(); | |
} | |
public Collection<IFunctionScope> getFunctionsByName(String name) { | |
List<IFunctionScope> result; | |
ensureParsedInitially(); | |
String baseName = name; | |
/* use same semantics as before, where qualified name lookups would fail | |
// pubnames uses qualified names but is indexed by basename | |
if (name != null) { | |
int baseStart = name.lastIndexOf("::"); | |
if (baseStart != -1) | |
baseName = name.substring(baseStart + 2); | |
} | |
*/ | |
// first, match against public function names | |
if (publicFunctions.size() > 0) { | |
if (name != null) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
List<PublicNameInfo> nameMatches = publicFunctions.get(baseName); | |
if (nameMatches != null) { | |
// parse the compilation units that have matches | |
if (nameMatches.size() == 1) { // quick usual case | |
reader.parseCompilationUnitForAddresses(nameMatches.get(0).cuHeader.scope); | |
} else { | |
ArrayList<DwarfCompileUnit> cuList = new ArrayList<DwarfCompileUnit>(); | |
for (PublicNameInfo info : nameMatches) { | |
if (!cuList.contains(info.cuHeader.scope)) { | |
cuList.add(info.cuHeader.scope); | |
} | |
} | |
for (DwarfCompileUnit cu : cuList) { | |
reader.parseCompilationUnitForAddresses(cu); | |
} | |
} | |
} else { | |
// not a public name, so parse all compilation units looking for functions | |
ensureParsedForScopes(); | |
} | |
} else { | |
// name is null, so parse all compilation units looking for functions | |
ensureParsedForScopes(); | |
} | |
} else { | |
// no public names, so parse all compilation units looking for functions | |
ensureParsedForScopes(); | |
} | |
if (name != null) { | |
result = functionsByName.get(baseName); | |
if (result == null) | |
return new ArrayList<IFunctionScope>(0); | |
} else { | |
result = new ArrayList<IFunctionScope>(functionsByName.size()); // at least this big | |
for (List<IFunctionScope> functions : functionsByName.values()) | |
result.addAll(functions); | |
((ArrayList<IFunctionScope>) result).trimToSize(); | |
} | |
return Collections.unmodifiableCollection(result); | |
} | |
public Collection<IVariable> getVariablesByName(String name, boolean globalsOnly) { | |
List<IVariable> result; | |
ensureParsedInitially(); | |
if (name == null) { | |
if (publicVariables.size() > 0) { | |
// name is null, so parse all compilation units looking for variables | |
if (globalsOnly) | |
ensureParsedForGlobalVariables(); | |
else | |
ensureParsedForVariables(); | |
} | |
result = new ArrayList<IVariable>(variablesByName.size()); // at least this big | |
for (List<IVariable> variables : variablesByName.values()) | |
result.addAll(variables); | |
} else { | |
String baseName = name; | |
int baseNameStart = name.lastIndexOf("::"); //$NON-NLS-1$ | |
if (baseNameStart != -1) | |
baseName = name.substring(baseNameStart + 2); | |
// match against public variable names, which the initial parse populated | |
if (publicVariables.size() > 0) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
List<PublicNameInfo> nameMatches = publicVariables.get(baseName); | |
if (nameMatches != null) { | |
// parse the compilation units that have matches | |
if (nameMatches.size() == 1) { // quick usual case | |
reader.parseCompilationUnitForAddresses(nameMatches.get(0).cuHeader.scope); | |
} else { | |
ArrayList<DwarfCompileUnit> cuList = new ArrayList<DwarfCompileUnit>(); | |
for (PublicNameInfo info : nameMatches) { | |
if (!cuList.contains(info.cuHeader.scope)) { | |
cuList.add(info.cuHeader.scope); | |
} | |
} | |
for (DwarfCompileUnit cu : cuList) { | |
reader.parseCompilationUnitForAddresses(cu); | |
} | |
} | |
} else { | |
// not a public name, so parse all compilation units looking for variables | |
if (!globalsOnly) | |
ensureParsedForVariables(); | |
} | |
} | |
result = variablesByName.get(name); | |
// check against unqualified name because RVCT 2.x did not include namespace | |
// info for globals that are inside namespaces | |
if (result == null && baseNameStart != -1) | |
result = variablesByName.get(baseName); | |
if (result == null) | |
return new ArrayList<IVariable>(0); | |
} | |
if (globalsOnly) { | |
filterOutLocalVariables(result); | |
} | |
return Collections.unmodifiableCollection(result); | |
} | |
private void filterOutLocalVariables(List<IVariable> variables) { | |
List<IVariable> allVariables = new ArrayList<IVariable>(variables); | |
for (IVariable var : allVariables) { | |
if (!(var.getScope() instanceof ICompileUnitScope)) { | |
variables.remove(var); | |
} | |
} | |
} | |
/** | |
* @return the publicFunctions | |
*/ | |
public Map<String, List<PublicNameInfo>> getPublicFunctions() { | |
ensureParsedInitially(); | |
return publicFunctions; | |
} | |
/** | |
* @return the publicVariables | |
*/ | |
public Map<String, List<PublicNameInfo>> getPublicVariables() { | |
ensureParsedInitially(); | |
return publicVariables; | |
} | |
public ICompileUnitScope getCompileUnitForAddress(IAddress linkAddress) { | |
ensureParsedForScope(linkAddress); | |
IScope scope = moduleScope.getScopeAtAddress(linkAddress); | |
while (scope != null && !(scope instanceof ICompileUnitScope)) { | |
scope = scope.getParent(); | |
} | |
return (ICompileUnitScope) scope; | |
} | |
public List<ICompileUnitScope> getCompileUnitsForFile(IPath filePath) { | |
ensureParsedInitially(); | |
List<ICompileUnitScope> cuList = compileUnitsPerFile.get(filePath); | |
if (cuList != null) | |
return cuList; | |
// FIXME: we need a looser check here: on Windows, we added drive letters to all | |
// paths before populating compileUnitsPerFile, even if there is not really a | |
// drive (see DwarFileHelper). | |
for (Map.Entry<IPath, List<ICompileUnitScope>> entry : compileUnitsPerFile.entrySet()) { | |
if (entry.getKey().setDevice(null).equals(filePath.setDevice(null))) { | |
return entry.getValue(); | |
} | |
} | |
return Collections.emptyList(); | |
} | |
@SuppressWarnings("unchecked") | |
public String[] getSourceFiles(IProgressMonitor monitor) { | |
if (referencedFiles.isEmpty()) { | |
// Check the persistent cache | |
String cacheKey = getSymbolFile().toOSString() + SOURCE_FILES_CACHE; | |
Set<String> cachedFiles = EDCDebugger.getDefault().getCache().getCachedData(cacheKey, Set.class, symbolFileLastModified); | |
if (cachedFiles == null) | |
{ | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
reader.quickParseDebugInfo(monitor); | |
assert referencedFiles.size() > 0; | |
EDCDebugger.getDefault().getCache().putCachedData(cacheKey, new HashSet<String>(referencedFiles), symbolFileLastModified); | |
} | |
else | |
referencedFiles = cachedFiles; | |
} | |
return referencedFiles.toArray(new String[referencedFiles.size()]); | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.cdt.debug.edc.internal.symbols.files.IDebugInfoProvider#getTypes() | |
*/ | |
public Collection<IType> getTypes() { | |
ensureParsedForTypes(); | |
ArrayList<IType> types = new ArrayList<IType>(typesByOffset.values()); | |
return types; | |
} | |
///////////////// | |
// Lazy evaluation methods | |
/** | |
* Fetch a type lazily. Either we've already parsed the type, or we have | |
* a reference to it, or we can find its compilation unit and parse its types. | |
* We do not fix up cross references until someone asks for | |
* it (e.g. from an IType or IVariable implementation). | |
*/ | |
public IType readType(long offset_) { | |
Long offset = Long.valueOf(offset_); | |
IType type = typesByOffset.get(offset); | |
if (type == null) { | |
// make sure we've parsed it | |
CompilationUnitHeader header = fetchCompileUnitHeader(offset_); | |
if (header != null) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
reader.parseCompilationUnitForTypes(header.scope); | |
type = typesByOffset.get(offset); | |
// may be unhandled currently | |
if (type == null) { | |
// workaround for GCC-E 3.x bug where some, but not all, type offsets are off by 4 | |
// assume if you hit this null case that the problem may be the GCC-E bug | |
type = typesByOffset.get(offset - 4); | |
if (type == null) | |
EDCDebugger.getMessageLogger().logError(DwarfMessages.DwarfDebugInfoProvider_NotParsingType1 + Long.toHexString(offset_) + | |
DwarfMessages.DwarfDebugInfoProvider_NotParsingType2 + symbolFilePath, null); | |
} | |
} else { | |
// may be unhandled currently | |
EDCDebugger.getMessageLogger().logError(DwarfMessages.DwarfDebugInfoProvider_CannotResolveCompUnit1 + Long.toHexString(offset_) + | |
DwarfMessages.DwarfDebugInfoProvider_CannotResolveCompUnit2 + symbolFilePath, null); | |
} | |
} | |
return type; | |
} | |
/** | |
* Fetch a referenced type lazily. | |
* @param scope | |
*/ | |
IType resolveTypeReference(ForwardTypeReference ref) { | |
IType type = typesByOffset.get(ref.offset); | |
if (type == null) { | |
type = readType(ref.offset); | |
} | |
return type; | |
} | |
/** | |
* @return | |
*/ | |
public IExecutableSymbolicsReader getExecutableSymbolicsReader() { | |
return exeReader; | |
} | |
/** | |
* Remember where a compilation unit header lives in the debug info. | |
* @param debugInfoOffset | |
* @param currentCUHeader | |
*/ | |
public void registerCompileUnitHeader(int debugInfoOffset, | |
CompilationUnitHeader currentCUHeader) { | |
debugOffsetsToCompileUnits.put((long) debugInfoOffset, currentCUHeader); | |
} | |
/** | |
* Get a compilation unit header that contains the given offset. | |
* @param debugInfoOffset an offset which is on or after a compilation unit's debug offset | |
* @return {@link CompilationUnitHeader} containing the offset | |
*/ | |
public CompilationUnitHeader fetchCompileUnitHeader(long debugInfoOffset) { | |
CompilationUnitHeader match = debugOffsetsToCompileUnits.get(debugInfoOffset); | |
if (match != null) { | |
return match; | |
} | |
// it's inside one | |
SortedMap<Long,CompilationUnitHeader> headMap = debugOffsetsToCompileUnits.headMap(debugInfoOffset); | |
if (!headMap.isEmpty()) { | |
match = headMap.get(headMap.lastKey()); | |
return match; | |
} | |
// shouldn't get here; most callers will handle null rather badly | |
return null; | |
} | |
/** | |
* Get the frame description entry for the given PC | |
* @param framePC | |
* @return FDE or <code>null</code> | |
*/ | |
public FrameDescriptionEntry findFrameDescriptionEntry(IAddress framePC) { | |
DwarfInfoReader reader = new DwarfInfoReader(this); | |
if (frameDescEntries.isEmpty()) { | |
reader.parseForFrameIndices(); | |
} | |
long pc = framePC.getValue().longValue(); | |
SortedMap<Entry, FrameDescriptionEntry> tailMap = frameDescEntries.tailMap(new IRangeList.Entry(pc, pc)); | |
if (tailMap.isEmpty()) | |
return null; | |
FrameDescriptionEntry entry = tailMap.values().iterator().next(); | |
if (entry.getCIE() == null) { | |
CommonInformationEntry cie = null; | |
if (!commonInfoEntries.containsKey(entry.ciePtr)) { | |
try { | |
cie = reader.parseCommonInfoEntry(entry.ciePtr, entry.addressSize, framePC); | |
} catch (IOException e) { | |
EDCDebugger.getMessageLogger().logError(DwarfMessages.DwarfDebugInfoProvider_FailedToReadCIE + entry.ciePtr, e); | |
} | |
commonInfoEntries.put(entry.ciePtr, cie); | |
} else { | |
cie = commonInfoEntries.get(entry.ciePtr); | |
} | |
entry.setCIE(cie); | |
} | |
return entry; | |
} | |
/** | |
* @return | |
*/ | |
public IFrameRegisterProvider getFrameRegisterProvider() { | |
return frameRegisterProvider; | |
} | |
/* | |
* (non-Javadoc) | |
* @see org.eclipse.cdt.debug.edc.symbols.IDebugInfoProvider#getTypesByName(java.lang.String) | |
*/ | |
public Collection<IType> getTypesByName(String name) { | |
// is name has "struct", "class" or "union", search without that | |
name = name.trim(); | |
String baseName = name; | |
TypeAggregate aggregate = TypeAggregate.None; | |
if (baseName.startsWith("class ")) { //$NON-NLS-1$ | |
aggregate = TypeAggregate.Class; | |
baseName = baseName.replace("class ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
} else if (baseName.startsWith("struct ")) { //$NON-NLS-1$ | |
aggregate = TypeAggregate.Struct; | |
baseName = baseName.replace("struct ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
} else if (baseName.startsWith("union ")) { //$NON-NLS-1$ | |
aggregate = TypeAggregate.Union; | |
baseName = baseName.replace("union ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
} | |
Collection<IType> types = typesByName.get(baseName); | |
String templateName = null; | |
String templateName2 = null; | |
if (types == null) { | |
// if we didn't match and this is a template name, | |
// remove extra spaces and composite type names | |
if (baseName.indexOf('<') != -1) { | |
templateName = baseName; | |
while (templateName.contains(" ")) //$NON-NLS-1$ | |
templateName = templateName.replaceAll(" ", " "); //$NON-NLS-1$ //$NON-NLS-2$ | |
templateName = templateName.replaceAll(", ", ","); //$NON-NLS-1$ //$NON-NLS-2$ | |
templateName = templateName.replaceAll("class ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
templateName = templateName.replaceAll("struct ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
templateName = templateName.replaceAll("union ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
types = typesByName.get(templateName); | |
// template name without "<...>", rather than with "<...>", might match | |
if (types == null) { | |
templateName2 = templateName.substring(0, templateName.indexOf('<')); | |
types = typesByName.get(templateName2); | |
// screen out types whose template list does not match | |
if (types != null) { | |
ArrayList<IType> matchingTypes = null; | |
for (Iterator<IType> it = types.iterator(); it.hasNext(); ) { | |
IType nextType = it.next(); | |
String match = nextType.getName(); | |
// for templates, remove composite type names (e.g., "class") | |
match = match.replaceAll("class ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
match = match.replaceAll("struct ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
match = match.replaceAll("union ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
if (match.equals(templateName)) { | |
if (matchingTypes == null) | |
matchingTypes = new ArrayList<IType>(types.size()); | |
matchingTypes.add(nextType); | |
} | |
} | |
types = matchingTypes; // may be null | |
} | |
} | |
} | |
if (types == null) { | |
// Maybe we optimistically searched for relevant types; | |
// if that fails, do the full parse of types now | |
if (!parsedForTypes) { | |
ensureParsedForTypes(); | |
types = getTypesByName(baseName); | |
if (types != null) | |
return types; // non-template return | |
if (baseName.indexOf('<') != -1) { | |
types = typesByName.get(templateName); | |
if (types == null) | |
types = typesByName.get(templateName2); | |
else | |
templateName2 = null; // did not match name without "<...>" | |
} | |
} | |
if (types == null) | |
return new ArrayList<IType>(0); | |
} | |
} | |
// screen out types whose template list does not match | |
if (templateName2 != null) { | |
ArrayList<IType> matchingTypes = new ArrayList<IType>(types.size()); | |
for (Iterator<IType> it = types.iterator(); it.hasNext(); ) { // types can't be null | |
IType nextType = it.next(); | |
String match = nextType.getName(); | |
// for templates, remove composite type names (e.g., "class") | |
match = match.replaceAll("class ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
match = match.replaceAll("struct ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
match = match.replaceAll("union ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
if (match.equals(templateName)) | |
matchingTypes.add(nextType); | |
} | |
types = matchingTypes; | |
} | |
// make sure that the aggregate type matches as well as the name | |
if (aggregate == TypeAggregate.None) | |
return Collections.unmodifiableCollection(types); | |
Iterator<IType> itr = types.iterator(); | |
while (itr.hasNext()) { | |
IType nextType = itr.next(); | |
if ((aggregate == TypeAggregate.Class && !nextType.getName().contains("class ")) || //$NON-NLS-1$ | |
(aggregate == TypeAggregate.Struct && !nextType.getName().contains("struct ")) || //$NON-NLS-1$ | |
(aggregate == TypeAggregate.Union && !nextType.getName().contains("union "))) //$NON-NLS-1$ | |
types.remove(nextType); | |
} | |
if (types.isEmpty()) | |
return new ArrayList<IType>(0); | |
return Collections.unmodifiableCollection(types); | |
} | |
} |