sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.instr/src/com/nokia/carbide/cpp/pi/instr/MapFile.java
author Toni Pulkkinen <ext-toni.p.pulkkinen@nokia.com>
Wed, 23 Jun 2010 15:05:09 +0300
changeset 12 ae255c9aa552
parent 5 844b047e260d
permissions -rw-r--r--
Performance Investigator Carbide extension 2.4.0

/*
 * Copyright (c) 2009 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 com.nokia.carbide.cpp.pi.instr;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.nokia.carbide.cpp.internal.pi.model.Function;
import com.nokia.carbide.cpp.pi.util.GeneralMessages;


public class MapFile 
{
	private boolean parsedMapFile = false;
	
	private String name;
	private File mapFile;
	private String referencePath;
	private long referenceLineNumber;
	private LinkedList<Function> functionData;
	private ArrayList<Function> sortedFunctionData;
	private Long currentGccLibEndingOffset = Long.valueOf(0);
	private Function lastGccFunction = null;
	
	// RVCT/RVDS map file line
	private static final Pattern RVCT_LINE_PATTERN = Pattern.compile("^\\p{Blank}*((?!\\d)\\S.+)\\p{Blank}+(0[x|X]\\p{XDigit}+|\\d+)\\p{Blank}+(?:ARM Code|Thumb Code)\\p{Blank}+(0x\\p{XDigit}+|\\d+)\\p{Blank}.*\\(\\.text\\)$");	//$NON-NLS-1$

	// a GCC map file line looks like this:
	// <%x|%d> <symbol name> for function symbols
	// symbol name cannot be number (e.g. first non space is not a digit)
	private static final Pattern GCC_FUNC_LINE_PATTERN  = Pattern.compile("\\p{Blank}*(0[x|X]\\p{XDigit}+|\\d+)\\p{Blank}+((?!\\d)\\S.+)");	//$NON-NLS-1$

	// <section> <%x|%d> <%x|%d> <symbol name>	for whole library
	// *fill* <%x|%d> <%x|%d> 00000000 for filler
	private static final Pattern GCC_LIB_OR_FILLER_LINE_PATTERN = Pattern.compile("\\p{Blank}*(?:\\S*)\\p{Blank}*(0[x|X]\\p{XDigit}+|\\d+)\\p{Blank}+(0[x|X]\\p{XDigit}+|\\d+)\\p{Blank}+(\\S.+)"); //$NON-NLS-1$

	private static final String EXPORTED = " (EXPORTED)";

	public MapFile(File file, String referencePath, long referenceLineNumber)
	{
		if (!file.exists())
		{
			  // .map not found
			  if(file.getName().endsWith(".exe.map") || //$NON-NLS-1$
					  file.getName().endsWith(".dll.map") || //$NON-NLS-1$
					  file.getName().endsWith(".ldd.map") || //$NON-NLS-1$
					  file.getName().endsWith(".pdd.map") || //$NON-NLS-1$
					  file.getName().endsWith(".app.map"))  //$NON-NLS-1$
			  {
				  flagFileNotFound(file, referencePath, referenceLineNumber);
			  }
		}
		//System.out.println("Creating MAP file "+file.getAbsolutePath());
		
		this.name = file.getName();
		this.functionData = new LinkedList<Function>();
		this.sortedFunctionData = new ArrayList<Function>();
		
		this.mapFile = file;
		this.referencePath = referencePath;
		this.referenceLineNumber = referenceLineNumber;
		
		/*
		if (file.getName().indexOf("wserv.exe") != -1)
		{
			this.printSortedFunctions();
			System.out.println("Map file "+file.getName()+" processed");
		}*/
	}
	
	public String getName()
	{
		return this.name;
	}
	
	public String getFunctionNameForOffset(long offset)
	{
		if (!this.parsedMapFile) {
			this.parseMapFile();
			this.copyToSortedFunctionData();
		}
		
		for (Function f : sortedFunctionData)
		{
			// is the asked offset within the area of this function
			// test first is the offset equal or past the first
			// instruction of the function
			if (offset >= f.getOffsetFromBinaryStart())
			{
				// if so, make sure that the offset is less
				// or equal to the last instruction within 
				// this function
				if (offset < (f.getOffsetFromBinaryStart()+f.getLength()))
				{
					return f.getFunctionName();
				}
			}
		}
		
		return Messages.getString("MapFile.functionWithOffsetNotFound1")+offset+Messages.getString("MapFile.functionWithOffsetNotFound2")+this.name+ //$NON-NLS-1$ //$NON-NLS-2$
				Messages.getString("MapFile.functionWithOffsetNotFound3")+ //$NON-NLS-1$
				(this.sortedFunctionData.get(this.sortedFunctionData.size() - 1)).getOffsetFromBinaryStart(); 		 //$NON-NLS-1$
	}
	
	public Function getFunctionForOffset(long offset)
	{
		if (!this.parsedMapFile) {
			this.parseMapFile();
			this.copyToSortedFunctionData();
		}

		for (Function f : sortedFunctionData)
		{
			// is the asked offset within the area of this function
			// test first is the offset equal or past the first
			// instruction of the function
			if (offset >= f.getOffsetFromBinaryStart())
			{
				// if so, make sure that the offset is less
				// or equal to the last instruction within 
				// this function
				if (offset < (f.getOffsetFromBinaryStart()+f.getLength()))
				{
					return f;
				}
			}
		}
		
		return null; 		
	}
	
	public long getFunctionLengthForOffset(long offset)
	{
		if (!this.parsedMapFile) {
			this.parseMapFile();
			this.copyToSortedFunctionData();
		}

		for (Function f : sortedFunctionData)
		{
			// is the asked offset within the area of this function
			// test first is the offset equal or past the first
			// instruction of the function
			if (offset >= f.getOffsetFromBinaryStart())
			{
				// if so, make sure that the offset is less
				// or equal to the last instruction within 
				// this function
				if (offset < (f.getOffsetFromBinaryStart()+f.getLength()))
				{
					return f.getLength();
				}
			}
		}
		return 0; 		
	}
	
	public long getOffsetFromBinaryStartForFunction(String functionName)
	{
		if (!this.parsedMapFile) {
			this.parseMapFile();
			this.copyToSortedFunctionData();
		}

		for (Function f : sortedFunctionData)
		{
			if (f.getFunctionName().equals(functionName))
			{
				return f.getOffsetFromBinaryStart();
			}
		}
		return -1;
	}
	
	private Function functionFromTokens(String funcNameToken, String funcOffsetToken, String funcLengthToken)
	{	
		Function f = new Function(funcNameToken,Long.valueOf(0),null);
		// look for length, set it tentatively
		// we may adjust it later
		f.setLength(Long.decode(funcLengthToken));
		f.setOffsetFromBinaryStart( Long.decode(funcOffsetToken));

		return f;
	}
	
	private void parseMapFile()
	{
		boolean isRVCT = false;
		
		System.out.println(Messages.getString("MapFile.parsingMapFile") + this.name); //$NON-NLS-1$
		this.parsedMapFile = true;
		
		// read into the map and see if it's RVCT built
		FileInputStream fis;
		try {
			fis = new FileInputStream(mapFile);
			BufferedReader bufReader = new BufferedReader(new InputStreamReader(fis));
			
			String line = bufReader.readLine();
			
			if (line != null && line.length() > 0) {
				if (line.toLowerCase().indexOf("arm linker,") != -1) //$NON-NLS-1$
					isRVCT = true;
				else
					isRVCT = false;
			} else {
				// empty file, do nothing
				return;
			}
			
			bufReader.close();
			fis.close();	
			
			fis = new FileInputStream(mapFile);		
			bufReader = new BufferedReader(new InputStreamReader(fis));

			if (isRVCT || mapFile.getAbsolutePath().toLowerCase().indexOf("armv5") != -1) //$NON-NLS-1$
			{
				parseRVCT(bufReader);
			}
			else
			{
				parseGCC(bufReader);
			}

			bufReader.close();
			fis.close();	
		} catch (FileNotFoundException e) {
			flagFileNotFound(mapFile, referencePath, referenceLineNumber);
		} catch (IOException e) {
			flagIOException(mapFile, referencePath, referenceLineNumber);
		}		
	}
	
	private void parseGCC(BufferedReader bufReader) throws IOException
	{
		String line = bufReader.readLine();

		while(line != null)
		{
			this.processLineGCC(line);
			line = bufReader.readLine();
		}
	}
	
	private void parseRVCT(BufferedReader bufReader) throws IOException
	{
		String line = bufReader.readLine();

		// find the global symbols section
		//while(line.indexOf("Global Symbols") == -1)
		// line = bufReader.readLine();		
		
		line = bufReader.readLine();
		
		while(line != null)
		{
			this.processLineRVCT(line);
			line = bufReader.readLine();
		}
		bufReader.close();
	}
	
	private void processLineRVCT(String line)
	{
		// a RVCT symbol line looks like this:
		// <symbol name> <%x|%d> <ARM Code|Thumb Code> <%x|%d> <object>
		// symbol name cannot be number (e.g. first non space is not a digit)
		Matcher rvctLineMatcher = RVCT_LINE_PATTERN.matcher(line);
		
		if (rvctLineMatcher.matches())
		{
			String funcNameToken = rvctLineMatcher.group(1).trim();
			// internal symbol, not a function
			if (funcNameToken.indexOf(Messages.getString("MapFile.dollarSign")) != -1) //$NON-NLS-1$
				return;
			if(funcNameToken.endsWith(EXPORTED)){
				funcNameToken = funcNameToken.substring(0, funcNameToken.lastIndexOf(EXPORTED));
			}
			
			String funcOffsetToken = rvctLineMatcher.group(2).trim();
			
			if (funcOffsetToken.equalsIgnoreCase("0x00000001") && line.contains("Thumb Code")) //$NON-NLS-1$ //$NON-NLS-2$
				return;
			
			String funcLengthToken = rvctLineMatcher.group(3).trim();
			
			Function f = functionFromTokens(funcNameToken, funcOffsetToken, funcLengthToken);
			
			this.insertToFunctionData(f);
		}
	}
		
	// our current parser picked up too many trash, let's try our best here
	private boolean qualifyGCCSymbol(long address, String symbol) {
		if (symbol == null || symbol.length() <= 0)
			return false;
		
		// zero address on ARM, you must be kidding. This isn't a real program address
		if (address <= 0) {
			return false;
		}
		if (symbol.contains("(size before relaxing")) { //$NON-NLS-1$
			return false;
		}
		if (symbol.contains("PROVIDE (")) { //$NON-NLS-1$
			return false;
		}
		// you better be kidding if this is a C symbol, it's linker symbol
		if (symbol.charAt(0) == '.') {
			return false;
		}
		if (symbol.equals("_DYNAMIC")) { //$NON-NLS-1$
			return false;
		}
		if (symbol.contains("vtable ") || symbol.contains("typeinfo ")) { //$NON-NLS-1$ //$NON-NLS-2$
			return false;
		}
		if (symbol.contains("= .")) { //$NON-NLS-1$
			return false;
		}
		return true;
	}
	
	private void processLineGCC(String line)
	{	
		// a GCC symbol line looks like this:
		// <%x|%d> <symbol name> for function symbols
		// symbol name cannot be number (e.g. first non space is not a digit)
		Matcher gccFuncLineMatcher  = GCC_FUNC_LINE_PATTERN.matcher(line);	//$NON-NLS-1$
		// <section> <%x|%d> <%x|%d> <symbol name>	for whole library
		// *fill* <%x|%d> <%x|%d> 00000000 for filler
		Matcher gccLibOrFillerLineMatcher = GCC_LIB_OR_FILLER_LINE_PATTERN.matcher(line); //$NON-NLS-1$
		
		Function f = null;
		Long currentLineOffset = currentGccLibEndingOffset;
		

		if (gccFuncLineMatcher.matches())
		{
			String funcNameToken = gccFuncLineMatcher.group(2).trim();
			String funcOffsetToken = gccFuncLineMatcher.group(1).trim();
			String funcLengthToken = Messages.getString("MapFile.zero"); //$NON-NLS-1$
			
			f = functionFromTokens(funcNameToken, funcOffsetToken, funcLengthToken);
			
			// Some GCC symbol may be bogus
			if (qualifyGCCSymbol(f.getOffsetFromBinaryStart(), funcNameToken)) {
				this.insertToFunctionData(f);
			} 
			
			if (lastGccFunction != null){
				// calculate size of last function with offset from current line
				if (f.getOffsetFromBinaryStart() > lastGccFunction.getOffsetFromBinaryStart() &&
						f.getOffsetFromBinaryStart() < currentGccLibEndingOffset) {
					currentLineOffset = f.getOffsetFromBinaryStart();
				}
			}
			
		} else if (gccLibOrFillerLineMatcher.matches()) {
			String libOffsetToken = gccLibOrFillerLineMatcher.group(1).trim();
			String libLengthToken = gccLibOrFillerLineMatcher.group(2).trim();
			// next time around we will use the new library offset
			currentGccLibEndingOffset = Long.decode(libLengthToken) + Long.decode(libOffsetToken);				
		} else {
			// next time around we will use the new library offset
			currentGccLibEndingOffset = Long.valueOf(0);
		}
		
		// update last function's size if needed
		if (lastGccFunction != null)
		{
			if (currentLineOffset > lastGccFunction.getOffsetFromBinaryStart()) {
				lastGccFunction.setLength(currentLineOffset - lastGccFunction.getOffsetFromBinaryStart());
			}
		}
		
		// track function on this line as last function, or null if this line is not a function
		lastGccFunction = f;

	}
	
	private void insertToFunctionData(Function function)
	{
		if (functionData.size() == 0) 
		{
			functionData.addFirst(function);
		}
		else if ((functionData.getFirst()).getOffsetFromBinaryStart() 
					< function.getOffsetFromBinaryStart())
		{
			functionData.addFirst(function);
		}
		else if ((functionData.getLast()).getOffsetFromBinaryStart() 
					> function.getOffsetFromBinaryStart())
		{
			functionData.addLast(function);
		}
		else
		{
			for (int i=0;i<functionData.size();i++)
			{
				if ((functionData.get(i)).getOffsetFromBinaryStart() 
						< function.getOffsetFromBinaryStart())
				{
					functionData.add(i,function);
					break;
				}
			}
		}
	}
	
	private void copyToSortedFunctionData()
	{
		if (this.functionData.size() <= 0) {
			return;
		}
		this.sortedFunctionData.clear();
		long start = (this.functionData.getLast()).getOffsetFromBinaryStart();
		Function previous = null;
		boolean reallyAdded = false;
		
		for (int i=this.functionData.size()-1;i>=0;i--)
		{
			//System.out.println(i);
			Function f = this.functionData.get(i);
			f.setOffsetFromBinaryStart(f.getOffsetFromBinaryStart() - start);
			
			if (this.sortedFunctionData.size() == 0)
			{
				// add the function if the vector is empty
				this.sortedFunctionData.add(f);
				reallyAdded = true;
			}
			else if ( (this.sortedFunctionData.get(this.sortedFunctionData.size()-1)).getOffsetFromBinaryStart() != f.getOffsetFromBinaryStart())
			{
				// add the function if the offset is not the same as with the previous line
				this.sortedFunctionData.add(f);
				reallyAdded = true;
			}	
			else if ( (this.sortedFunctionData.get(this.sortedFunctionData.size()-1)).getFunctionName().startsWith("_")) //$NON-NLS-1$
			{	
				// if there is a key with this offset, discard the previous with prefix "_"
				this.sortedFunctionData.remove(this.sortedFunctionData.get(this.sortedFunctionData.size()-1));
				// add the new function with the same key
				this.sortedFunctionData.add(f);
				reallyAdded = true;
			}
			
			// do this only if we really added the function to the sorted list
			if (reallyAdded == true)
			{
				// store the length of the previous function
				if (previous != null)
					previous.setLength(f.getOffsetFromBinaryStart() - previous.getOffsetFromBinaryStart());
				previous = f;	
				reallyAdded = false;
			}
			
		}
		this.functionData.clear();
		this.functionData = null;
	}
	
	/* internal test function: comment out so code coverage looks
	 * good quantitatively
	private void printSortedFunctions()
	{ 
		long totalLength = 0;
		for (Function f : sortedFunctionData)
		{	
			System.out.println( f.offsetFromBinaryStart+Messages.getString("MapFile.openParenthesis")+totalLength+Messages.getString("MapFile.closeParenthesis")+Long.toHexString(f.length)+Messages.getString("MapFile.dashDash")+f.functionName); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			totalLength += f.length;
		}
	}
	*/
	
	private void flagFileNotFound(File file, String referencePath, long referenceLineNumber) {
		  String myMessage = Messages.getString("MapFile.map.file") +  file.getAbsoluteFile() + Messages.getString("MapFile.not.found"); //$NON-NLS-1$ //$NON-NLS-2$
		  myMessage += Messages.getString("MapFile.map.check.project"); //$NON-NLS-1$
		  if (referencePath != null && referencePath.length() > 0) {
			  myMessage += Messages.getString("MapFile.referenced.by") + referencePath; //$NON-NLS-1$
		  }
		  if (referenceLineNumber > 0) {
			  myMessage += Messages.getString("MapFile.line.number") + referenceLineNumber; //$NON-NLS-1$
		  }
		  
    	  GeneralMessages.piLog(myMessage, GeneralMessages.WARNING);
	}

	private void flagIOException(File file, String referencePath, long referenceLineNumber) {
		  String myMessage = Messages.getString("MapFile.map.file") + file.getAbsoluteFile().getName() + Messages.getString("MapFile.ioexception"); //$NON-NLS-1$ //$NON-NLS-2$
		  if (referencePath != null && referencePath.length() > 0) {
			  myMessage += Messages.getString("MapFile.referenced.by") + referencePath; //$NON-NLS-1$
		  }
		  if (referenceLineNumber > 0) {
			  myMessage += Messages.getString("MapFile.line.number") + referenceLineNumber; //$NON-NLS-1$
		  }

		  GeneralMessages.showErrorMessage(myMessage);
		  GeneralMessages.piLog(myMessage, GeneralMessages.ERROR);
	}
}