/******************************************************************************* * Copyright (c) 2007, 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 * Ling Wang (Nokia) bug 201000 *******************************************************************************/ package org.eclipse.cdt.utils.debug.dwarf; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.ISymbolReader; import org.eclipse.cdt.utils.coff.Coff.SectionHeader; import org.eclipse.cdt.utils.coff.PE; import org.eclipse.cdt.utils.debug.IDebugEntryRequestor; import org.eclipse.cdt.utils.elf.Elf; import org.eclipse.cdt.utils.elf.Elf.Section; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; /** * Light-weight parser of Dwarf2 data which is intended for getting only * source files that contribute to the given executable. */ public class DwarfReader extends Dwarf implements ISymbolReader { // These are sections that need be parsed to get the source file list. final static String[] DWARF_SectionsToParse = { DWARF_DEBUG_INFO, DWARF_DEBUG_LINE, DWARF_DEBUG_ABBREV, DWARF_DEBUG_STR // this is optional. Some compilers don't generate it. }; private final Collection<String> m_fileCollection = new ArrayList<String>(); private String[] m_fileNames = null; private boolean m_parsed = false; private final ArrayList<Integer> m_parsedLineTableOffsets = new ArrayList<Integer>(); private int m_parsedLineTableSize = 0; public DwarfReader(String file) throws IOException { super(file); } public DwarfReader(Elf exe) throws IOException { super(exe); } /** * @since 5.1 */ public DwarfReader(PE exe) throws IOException { super(exe); } // Override parent. // @Override public void init(Elf exe) throws IOException { Elf.ELFhdr header = exe.getELFhdr(); isLE = header.e_ident[Elf.ELFhdr.EI_DATA] == Elf.ELFhdr.ELFDATA2LSB; Elf.Section[] sections = exe.getSections(); // Read in sections (and only the sections) we care about. // for (Section section : sections) { String name = section.toString(); for (String element : DWARF_SectionsToParse) { if (name.equals(element)) { // catch out of memory exceptions which might happen trying to // load large sections (like .debug_info). not a fix for that // problem itself, but will at least continue to load the other // sections. try { dwarfSections.put(element, section.mapSectionData()); } catch (Exception e) { CCorePlugin.log(e); } } } } // Don't print during parsing. printEnabled = false; m_parsed = false; } @Override public void init(PE exe) throws IOException { isLE = true; SectionHeader[] sections = exe.getSectionHeaders(); for (int i = 0; i < sections.length; i++) { String name = new String(sections[i].s_name).trim(); if (name.startsWith("/")) //$NON-NLS-1$ { int stringTableOffset = Integer.parseInt(name.substring(1)); name = exe.getStringTableEntry(stringTableOffset); } for (String element : Dwarf.DWARF_SCNNAMES) { if (name.equals(element)) { try { dwarfSections.put(element, sections[i].mapSectionData()); } catch (Exception e) { CCorePlugin.log(e); } } } } // Don't print during parsing. printEnabled = false; m_parsed = false; } /* * Parse line table data of a compilation unit to get names of all source files * that contribute to the compilation unit. */ void parseSourceInCULineInfo( String cuCompDir, // compilation directory of the CU int cuStmtList) // offset of the CU line table in .debug_line section { ByteBuffer data = dwarfSections.get(DWARF_DEBUG_LINE); if (data != null) { try { data.position(cuStmtList); /* Read line table header: * * total_length: 4 bytes (excluding itself) * version: 2 * prologue length: 4 * minimum_instruction_len: 1 * default_is_stmt: 1 * line_base: 1 * line_range: 1 * opcode_base: 1 * standard_opcode_lengths: (value of opcode_base) */ // Remember the CU line tables we've parsed. Integer cuOffset = new Integer(cuStmtList); if (! m_parsedLineTableOffsets.contains(cuOffset)) { m_parsedLineTableOffsets.add(cuOffset); int length = read_4_bytes(data) + 4; m_parsedLineTableSize += length + 4; } else { // Compiler like ARM RVCT may produce several CUs for the // same source files. return; } // Skip the following till "opcode_base" data.position(data.position() + 10); int opcode_base = data.get(); data.position(data.position() + opcode_base - 1); // Read in directories. // ArrayList<String> dirList = new ArrayList<String>(); // Put the compilation directory of the CU as the first dir dirList.add(cuCompDir); String str, fileName; while (true) { str = readString(data); if (str.length() == 0) break; // If the directory is relative, append it to the CU dir IPath dir = new Path(str); if(!dir.isAbsolute()) dir = new Path(cuCompDir).append(str); dirList.add(dir.toString()); } // Read file names // long leb128; while (true) { fileName = readString(data); if (fileName.length() == 0) // no more file entry break; // dir index leb128 = read_unsigned_leb128(data); addSourceFile(dirList.get((int)leb128), fileName); // Skip the followings // // modification time leb128 = read_unsigned_leb128(data); // file size in bytes leb128 = read_unsigned_leb128(data); } } catch (IOException e) { e.printStackTrace(); } } } /* * Check if there are any line tables in .debug_line section that are * not referenced by any TAG_compile_units. If yes, add source files * in those table entries to our "m_fileCollection". * If the compiler/linker is fully dwarf standard compliant, that should * not happen. But that case does exist, hence this workaround. * .................. LWang. 08/24/07 */ private void getSourceFilesFromDebugLineSection() { ByteBuffer data = dwarfSections.get(DWARF_DEBUG_LINE); if (data == null) return; int sectionSize = data.capacity(); int minHeaderSize = 16; // Check if there is data in .debug_line section that is not parsed // yet by parseSourceInCULineInfo(). if (m_parsedLineTableSize >= sectionSize - minHeaderSize) return; // The .debug_line section contains a list of line tables // for compile_units. We'll iterate through all line tables // in the section. /* * Line table header for one compile_unit: * * total_length: 4 bytes (excluding itself) * version: 2 * prologue length: 4 * minimum_instruction_len: 1 * default_is_stmt: 1 * line_base: 1 * line_range: 1 * opcode_base: 1 * standard_opcode_lengths: (value of opcode_base) */ int lineTableStart = 0; // offset in the .debug_line section try { while (lineTableStart < sectionSize - minHeaderSize) { data.position(lineTableStart); Integer currLineTableStart = new Integer(lineTableStart); // Read length of the line table for one compile unit // Note the length does not including the "length" field itself. int tableLength = read_4_bytes(data); // Record start of next CU line table lineTableStart += tableLength + 4; // According to Dwarf standard, the "tableLength" should cover the // the whole CU line table. But some compilers (e.g. ARM RVCT 2.2) // produce extra padding (1 to 3 bytes) beyond that in order for // "lineTableStart" to be aligned at multiple of 4. The padding // bytes are beyond the "tableLength" and not indicated by // any flag, which I believe is not Dwarf2 standard compliant. // How to determine if that type of padding exists ? // I don't have a 100% safe way. But following hacking seems // good enough in practice.........08/26/07 if (lineTableStart < sectionSize - minHeaderSize && (lineTableStart & 0x3) != 0) { int savedPosition = data.position(); data.position(lineTableStart); int ltLength = read_4_bytes(data); int dwarfVer = read_2_bytes(data); int minInstLengh = data.get(data.position() + 4); boolean dataValid = ltLength > minHeaderSize && ltLength < 16*64*1024 && // One source file has that much line data ? dwarfVer > 0 && dwarfVer < 4 && // ver 3 is still draft at present. minInstLengh > 0 && minInstLengh <= 8; if (! dataValid) // padding exists ! lineTableStart = (lineTableStart+3) & ~0x3; data.position(savedPosition); } if (m_parsedLineTableOffsets.contains(currLineTableStart)) // current line table has already been parsed, skip it. continue; // Skip following fields till "opcode_base" data.position(data.position() + 10); int opcode_base = data.get(); data.position(data.position() + opcode_base - 1); // Read in directories. // ArrayList<String> dirList = new ArrayList<String>(); String str, fileName; // first dir should be TAG_comp_dir from CU, which we don't have here. dirList.add(""); //$NON-NLS-1$ while (true) { str = readString(data); if (str.length() == 0) break; dirList.add(str); } // Read file names // long leb128; while (true) { fileName = readString(data); if (fileName.length() == 0) // no more file entry break; // dir index. Note "0" is reserved for compilation directory. leb128 = read_unsigned_leb128(data); addSourceFile(dirList.get((int) leb128), fileName); // Skip the followings // // modification time leb128 = read_unsigned_leb128(data); // file size in bytes leb128 = read_unsigned_leb128(data); } } } catch (IOException e) { e.printStackTrace(); return; } } public String[] getSourceFiles() { if (!m_parsed) { m_fileCollection.clear(); getSourceFilesFromDebugInfoSection(); getSourceFilesFromDebugLineSection(); m_parsed = true; m_fileNames = new String[m_fileCollection.size()]; m_fileCollection.toArray(m_fileNames); } return m_fileNames; } /* * Get source file names from compile units (CU) in .debug_info section, * which will also search line table for the CU in .debug_line section. * * The file names are stored in member "m_fileCollection". */ private void getSourceFilesFromDebugInfoSection() { // This will parse the data in .debug_info section which // will call this->processCompileUnit() to get source files. parse(null); } private void addSourceFile(String dir, String name) { if (name == null || name.length() == 0) return; if (name.charAt(0) == '<') // don't count the entry "<internal>" from GCCE compiler return; String fullName = name; IPath dirPa = new Path(dir); IPath pa = new Path(name); // Combine dir & name if needed. if (!pa.isAbsolute() && dir.length() > 0) pa = dirPa.append(pa); // This convert the path to canonical path (but not necessarily absolute, which // is different from java.io.File.getCanonicalPath()). fullName = pa.toOSString(); if (!m_fileCollection.contains(fullName)) m_fileCollection.add(fullName); } /** * Read a null-ended string from the given "data" stream. * data : IN, byte buffer */ String readString(ByteBuffer data) { String str; StringBuffer sb = new StringBuffer(); while (data.hasRemaining()) { byte c = data.get(); if (c == 0) { break; } sb.append((char) c); } str = sb.toString(); return str; } // Override parent: only handle TAG_Compile_Unit. @Override void processDebugInfoEntry(IDebugEntryRequestor requestor, AbbreviationEntry entry, List<Dwarf.AttributeValue> list) { int tag = (int) entry.tag; switch (tag) { case DwarfConstants.DW_TAG_compile_unit : processCompileUnit(requestor, list); break; default: break; } } // Override parent. // Just get the file name of the CU. // Argument "requestor" is ignored. @Override void processCompileUnit(IDebugEntryRequestor requestor, List<AttributeValue> list) { String cuName, cuCompDir; int stmtList = -1; cuName = cuCompDir = ""; //$NON-NLS-1$ for (int i = 0; i < list.size(); i++) { AttributeValue av = list.get(i); try { int name = (int)av.attribute.name; switch(name) { case DwarfConstants.DW_AT_name: cuName = (String)av.value; break; case DwarfConstants.DW_AT_comp_dir: cuCompDir = (String)av.value; break; case DwarfConstants.DW_AT_stmt_list: stmtList = ((Number)av.value).intValue(); break; default: break; } } catch (ClassCastException e) { } } addSourceFile(cuCompDir, cuName); if (stmtList > -1) // this CU has "stmt_list" attribute parseSourceInCULineInfo(cuCompDir, stmtList); } /** * @since 5.2 */ public String[] getSourceFiles(IProgressMonitor monitor) { return getSourceFiles(); } }