blob: 4214582b82208dd34a2577d1cbba5e3ce214a1c8 [file] [log] [blame]
/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
package org.eclipse.cdt.debug.edc.tests;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.debug.edc.internal.PathUtils;
import org.eclipse.cdt.debug.edc.internal.services.dsf.LineEntryMapper;
import org.eclipse.cdt.debug.edc.internal.services.dsf.Symbols;
import org.eclipse.cdt.debug.edc.symbols.ICompileUnitScope;
import org.eclipse.cdt.debug.edc.symbols.IEDCSymbolReader;
import org.eclipse.cdt.debug.edc.symbols.IFunctionScope;
import org.eclipse.cdt.debug.edc.symbols.ILineEntry;
import org.eclipse.cdt.debug.edc.symbols.IModuleLineEntryProvider;
import org.eclipse.cdt.dsf.debug.service.IModules.AddressRange;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.junit.Test;
/**
* Test use of the IEDCSymbolReader to map source file/line to addresses.
*/
public class TestSourceToAddressMapping extends BaseDwarfTestCase {
protected static final String[] symFilesToTest = {
"SimpleCpp_rvct_22.sym",
"SimpleCpp_rvct_40.sym",
"SimpleCpp_gcce_432.sym",
"SimpleCpp_gcc_x86.exe",
"BlackFlagMinGW.exe",
"BlackFlag_gcce.sym",
"BlackFlag_linuxgcc.exe",
"BlackFlag_rvct.sym",
"HelloWorld_rvct_2_2.exe.sym",
"HelloWorld_rvct_4_0.exe.sym",
};
/** Bag of data for testing sym files. The key is 'symFile' and other
* elements are used by specific tests.
*/
protected static class TestInfo {
IPath symFile;
Map<LineInfo, List<String>> lineToFunctionMap = new LinkedHashMap<LineInfo, List<String>>();
}
protected static Map<String, TestInfo> testInfos = new LinkedHashMap<String, TestInfo>();
static {
for (String sym : symFilesToTest) {
TestInfo info = new TestInfo();
info.symFile = getFile(sym);
testInfos.put(sym, info);
}
}
protected static TestInfo lookupInfo(String sym) {
return testInfos.get(sym);
}
protected static class LineInfo {
public LineInfo(IPath srcFile, int line) {
sourceFile = srcFile;
this.line = line;
}
IPath sourceFile;
int line;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return sourceFile.lastSegment() +":" + line;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + line;
result = prime * result
+ ((sourceFile == null) ? 0 : sourceFile.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LineInfo other = (LineInfo) obj;
if (line != other.line)
return false;
if (sourceFile == null) {
if (other.sourceFile != null)
return false;
} else if (!sourceFile.equals(other.sourceFile))
return false;
return true;
}
}
protected static void registerFunctionMapping(String symFileRx, String srcFile, int line, String function) {
for (TestInfo info : testInfos.values()) {
if (info.symFile.lastSegment().matches(symFileRx)) {
IPath srcFilePath = new Path(srcFile);
LineInfo l = new LineInfo(srcFilePath, line);
List<String> funcs = info.lineToFunctionMap.get(l);
if (funcs == null) {
funcs = new ArrayList<String>();
info.lineToFunctionMap.put(l, funcs);
}
funcs.add(function);
}
}
}
static {
registerFunctionMapping("SimpleCpp.*", "src/SimpleCpp.cpp", 19, "doit"); // decl line
registerFunctionMapping("SimpleCpp.*", "src/SimpleCpp.cpp", 25, "doit"); // return line
registerFunctionMapping("SimpleCpp.*", "src/SimpleCpp.cpp", 25, "length"); // inline site
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 14, "List"); // decl line
// templates are defined in one place but expanded a lot, especially as inlined functions
registerFunctionMapping("SimpleCpp_gcce_432.sym", "inc/Templates.h", 15, "List"); // open brace
registerFunctionMapping("SimpleCpp_rvct_22.sym", "inc/Templates.h", 15, "List"); // open brace
registerFunctionMapping("SimpleCpp_rvct_40.sym", "inc/Templates.h", 15, "List"); // open brace
registerFunctionMapping("SimpleCpp_gcce_432.sym", "inc/Templates.h", 15, "makelist"); // when inlined
registerFunctionMapping("SimpleCpp_rvct_22.sym", "inc/Templates.h", 15, "makelist"); // when inlined
registerFunctionMapping("SimpleCpp_rvct_490.sym", "inc/Templates.h", 15, "makelist"); // when inlined
// GCC 4.x moves the code to the brace
registerFunctionMapping("SimpleCpp_gcc_x86.sym", "inc/Templates.h", 16, "List"); // open brace
registerFunctionMapping("SimpleCpp_gcc_x86.sym", "inc/Templates.h", 16, "makelist"); // when inlined
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 17, "length"); // decl line
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 18, "length"); // return line
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 18, "doit"); // inline site
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 21, "operator[]"); // decl line
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 21, "makelist"); // inlined here
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 30, "add"); // decl line
registerFunctionMapping("SimpleCpp.*", "inc/Templates.h", 43, "add"); // last code line
// this one is tricky: the header may reference the subprogram, but
// the content is placed in the cpp file.
registerFunctionMapping("SimpleCpp.*", "inc/mycode.h", 11, "myfunc"); // decl line
registerFunctionMapping("SimpleCpp.*", "inc/mycode.h", 14, "myfunc"); // assignment
registerFunctionMapping("SimpleCpp.*", "inc/mycode.h", 16, "myfunc"); // final }
}
@Test
public void testLineLookupRVCT22() throws Exception {
doTestLineLookup(lookupInfo("SimpleCpp_rvct_22.sym"));
}
@Test
public void testLineLookupRVCT40() throws Exception {
doTestLineLookup(lookupInfo("SimpleCpp_rvct_40.sym"));
}
@Test
public void testLineLookupGCCE432() throws Exception {
doTestLineLookup(lookupInfo("SimpleCpp_gcce_432.sym"));
}
@Test
public void testLineLookupGCCLinux() throws Exception {
doTestLineLookup(lookupInfo("SimpleCpp_gcc_x86.exe"));
}
/**
* @param reader
* @param sourceFile
* @return
*/
private IPath findSource(IEDCSymbolReader reader, IPath sourceFile) {
for (String file : reader.getSourceFiles()) {
IPath path = PathUtils.createPath(file);
boolean matched = false;
loop: for (int i = 0; i <sourceFile.segmentCount(); i++) {
String pathSeg = path.segment(path.segmentCount() - i - 1);
String sourceSeg = sourceFile.segment(sourceFile.segmentCount() - i - 1);
if (pathSeg == null)
break loop;
if ( !pathSeg.equals(sourceSeg))
break;
matched = true;
}
if (matched)
return path;
}
return sourceFile;
}
private void doTestLineLookup(TestInfo info) throws Exception {
IEDCSymbolReader reader = Symbols.getSymbolReader(info.symFile);
assertNotNull(reader);
IModuleLineEntryProvider moduleLineEntryProvider = reader.getModuleScope().getModuleLineEntryProvider();
assertNotNull(moduleLineEntryProvider);
StringBuilder errors = new StringBuilder();
for (Map.Entry<LineInfo, List<String>> entry : info.lineToFunctionMap.entrySet()) {
try {
checkLineMapping(info, reader, moduleLineEntryProvider, entry.getKey(), entry.getValue());
} catch (AssertionError e) {
errors.append(e.getMessage());
errors.append('\n');
}
}
if (errors.length() > 0)
fail(errors.toString());
}
/**
* Check that we can find address information for the given line,
* and then map that to a real function. These are two separate tasks.
* @param info
* @param reader
* @param moduleLineEntryProvider
* @param entry
*/
private void checkLineMapping(TestInfo info, IEDCSymbolReader reader,
IModuleLineEntryProvider moduleLineEntryProvider,
LineInfo lineinfo, List<String> funcNames) {
String curinfo = info.symFile.lastSegment() + ":" + lineinfo.toString();
// find the actual filename
IPath lookupPath = findSource(reader, lineinfo.sourceFile);
Collection<AddressRange> addresses = LineEntryMapper.getAddressRangesAtSource(
moduleLineEntryProvider,
lookupPath, lineinfo.line);
assertNotNull(addresses);
assertTrue(curinfo, addresses.size() > 0);
List<String> functionsFound = new ArrayList<String>();
// make sure duplicates don't exist
Set<AddressRange> foundRanges = new HashSet<AddressRange>();
for (AddressRange range : addresses) {
assertFalse(range+"", foundRanges.contains(range));
foundRanges.add(range);
checkFunction(reader, range, curinfo, functionsFound);
}
StringBuilder foundString = new StringBuilder();
for (String func : functionsFound) {
if (foundString.length() > 0)
foundString.append(',');
foundString.append(func);
}
StringBuilder wantedString = new StringBuilder();
boolean found = false;
for (String name : funcNames) {
if (wantedString.length() > 0)
wantedString.append(',');
wantedString.append(name);
if (functionsFound.contains(name)) {
found = true;
}
}
assertTrue("address range did not match anything expected at " + curinfo + ": wanted " + wantedString+" but got " + foundString, found);
}
/**
* Make sure the address maps to a function, and that the function is one listed in funcNames
* @param reader
* @param range
* @param info
* @param functionsFound
*/
private void checkFunction(IEDCSymbolReader reader, AddressRange range, String info, List<String> functionsFound) {
ICompileUnitScope cu = reader.getModuleScope().getCompileUnitForAddress(range.getStartAddress());
// TODO: when we add line entries for _decl_file/_decl_line/etc., we may point to an
// address currently outside the expected scope of a module (which is currently built only on
// the functions it is known to contain). Thus, cu may be null here in some cases.
// The debugger doesn't care about the CU, though.
if (cu != null) {
assertNotNull(info, cu);
IFunctionScope function = cu.getFunctionAtAddress(range.getStartAddress());
assertNotNull(info, function);
functionsFound.add(function.getName());
}
}
/**
* Make sure we find multiple ranges for lines when multiple statements live there
* @throws Exception
*/
@Test
public void testMultiRangeLines() throws Exception {
TestInfo info = lookupInfo("SimpleCpp_gcce_432.sym");
IEDCSymbolReader reader = Symbols.getSymbolReader(info.symFile);
assertNotNull(reader);
IModuleLineEntryProvider moduleLineEntryProvider = reader.getModuleScope().getModuleLineEntryProvider();
assertNotNull(moduleLineEntryProvider);
IPath lookupPath = findSource(reader, new Path("inc/Templates.h"));
// "for", init, test for two template instances
Collection<ILineEntry> entries = moduleLineEntryProvider.getLineEntriesForLines(lookupPath, 37, 37);
assertEquals(4, entries.size());
}
}