/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.analysis.executables;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.opensolaris.opengrok.analysis.FileAnalyzer;
import org.opensolaris.opengrok.analysis.FileAnalyzerFactory;
import org.opensolaris.opengrok.analysis.XrefWriter;
import org.opensolaris.opengrok.analysis.plain.PlainFullTokenizer;
import org.opensolaris.opengrok.util.IOUtils;
import org.opensolaris.opengrok.web.Util;
/**
* Analyzes ELF (Executable and Linking Format) files.
* Created on September 23, 2005
*
* @author Chandan
* @author Trond Norbye
*/
public class ELFAnalyzer extends FileAnalyzer {
private static final Logger logger = Logger.getLogger(ELFAnalyzer.class.getName());
private StringBuilder content;
private PlainFullTokenizer plainfull;
private static final List<String> READABLE_SECTIONS;
static {
READABLE_SECTIONS = new ArrayList<String>();
READABLE_SECTIONS.add(".debug_str");
READABLE_SECTIONS.add(".comment");
READABLE_SECTIONS.add(".data");
READABLE_SECTIONS.add(".data1");
READABLE_SECTIONS.add(".rodata");
READABLE_SECTIONS.add(".rodata1");
}
/**
* Creates a new instance of ELFAnalyzer
* @param factory the factory to use to obtain default settings
*/
protected ELFAnalyzer(FileAnalyzerFactory factory) {
super(factory);
content = new StringBuilder();
plainfull = new PlainFullTokenizer(new StringReader(""));
}
/**
* {@inheritDoc}
*/
@Override
public void analyze(Document doc, InputStream in) throws IOException {
if (in instanceof FileInputStream) {
parseELF((FileInputStream) in);
if (content.length() > 0) {
doc.add(new Field("full", " ", Field.Store.YES, Field.Index.ANALYZED));
}
} else {
String fullpath = doc.get("fullpath");
@SuppressWarnings("resource")
final FileInputStream fin = new FileInputStream(fullpath);
try {
parseELF(fin);
if (content.length() > 0) {
doc.add(new Field("full", " ", Field.Store.YES, Field.Index.ANALYZED));
}
} finally {
IOUtils.close(fin);
}
}
}
/**
* Parse the given ELF input stream.
* @param f stream to parse
* @throws IOException
*/
@SuppressWarnings({ "resource", "boxing" })
public void parseELF(FileInputStream f) throws IOException {
FileChannel fch = f.getChannel();
try {
MappedByteBuffer fmap =
fch.map(FileChannel.MapMode.READ_ONLY, 0, fch.size());
ELFHeader eh = new ELFHeader(fmap);
if (eh.e_shnum <= 0) {
logger.log(Level.FINE, "Skipping file, no section headers");
return;
}
fmap.position(eh.e_shoff + (eh.e_shstrndx * eh.e_shentsize));
ELFSection stringSection = new ELFSection(fmap);
if (stringSection.sh_size == 0) {
logger.log(Level.FINE, "Skipping file, no section name string table");
return ;
}
HashMap<String, Integer> sectionMap = new HashMap<String, Integer>();
ELFSection[] sections = new ELFSection[eh.e_shnum];
int[] readables = new int[eh.e_shnum];
int ri = 0;
for (int i = 0; i < eh.e_shnum; i++) {
fmap.position(eh.e_shoff + (i * eh.e_shentsize));
sections[i] = new ELFSection(fmap);
String sectionName =
getName(stringSection.sh_offset, sections[i].sh_name, fmap);
if (sectionName != null) {
sectionMap.put(sectionName, sections[i].sh_offset);
}
if (sections[i].sh_type == ELFSection.SHT_STRTAB) {
readables[ri++] = i;
} else if (READABLE_SECTIONS.contains(sectionName)) {
readables[ri++] = i;
}
}
boolean lastPrintable = false;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ri; i++) {
fmap.position(sections[readables[i]].sh_offset);
int size = sections[readables[i]].sh_size;
byte c;
while (size-- > 0) {
c = fmap.get();
if (isReadable(c)) {
lastPrintable = true;
sb.append((char) c);
} else if (lastPrintable) {
lastPrintable = false;
sb.append(' ');
}
}
sb.append('\n');
}
sb.trimToSize();
content = sb;
} finally {
IOUtils.close(fch);
}
}
private static boolean isReadable(int c) {
return c > ' ' && c <= 127;
}
private static String getName(int tab, int stroff, MappedByteBuffer fmap) {
if (tab == 0) {
return null;
}
StringBuilder sb = new StringBuilder(20);
byte c;
int start = tab + stroff;
while ((c = fmap.get(start++)) != 0x00) {
sb.append((char) c);
}
return sb.toString();
}
/**
* {@inheritDoc}
*/
@Override
public TokenStream overridableTokenStream(String fieldName, Reader reader) {
if ("full".equals(fieldName)) {
char[] cs = new char[content.length()];
content.getChars(0, cs.length, cs, 0);
plainfull.reInit(cs, cs.length);
return plainfull;
}
return super.overridableTokenStream(fieldName, reader);
}
/**
* Write a cross referenced HTML file.
* @param out Writer to write
*/
@Override
public void writeXref(XrefWriter out) throws IOException {
out.write("</pre>");
out.write(Util.htmlize(content.toString(), "<br/>"));
out.write("<pre>");
}
private static class ELFHeader {
// Elf32 Addr = readInt
// elf32 half = readUnsignedShort
// Off = int
// Sword = int
// Word = int
// un = unsignedBtye
public EI_Class ei_class;
public EI_Data ei_data;
@SuppressWarnings("unused")
public int ei_version;
public E_Type e_type;
public E_Machine e_machine;
public E_Version e_version;
public int e_entry;
public int e_phoff;
public int e_shoff;
public int e_flags;
public int e_ehsize;
public int e_phentsize;
public int e_phnum;
public int e_shentsize;
public int e_shnum;
public int e_shstrndx;
public ELFHeader(MappedByteBuffer fmap) throws IllegalArgumentException {
if (fmap.get(ELFIdentification.EI_MAG0.value()) != 0x7f ||
fmap.get(ELFIdentification.EI_MAG1.value()) != 'E' ||
fmap.get(ELFIdentification.EI_MAG2.value()) != 'L' ||
fmap.get(ELFIdentification.EI_MAG3.value()) != 'F') {
throw new IllegalArgumentException("Not an ELF file");
}
ei_class = EI_Class.valueOf(fmap.get(ELFIdentification.EI_CLASS.value()));
ei_data = EI_Data.valueOf(fmap.get(ELFIdentification.EI_DATA.value()));
ei_version = fmap.get(ELFIdentification.EI_VERSION.value());
if (ei_data == EI_Data.ELFDATA2LSB) {
fmap.order(ByteOrder.LITTLE_ENDIAN);
} else {
fmap.order(ByteOrder.BIG_ENDIAN);
}
fmap.position(ELFIdentification.EI_NIDENT.value());
e_type = E_Type.valueOf(fmap.getShort());
e_machine = E_Machine.valueOf(fmap.getShort());
e_version = E_Version.valueOf(fmap.getInt());
e_entry = fmap.getInt();
e_phoff = fmap.getInt();
e_shoff = fmap.getInt();
e_flags = fmap.getInt();
e_ehsize = fmap.getShort();
e_phentsize = fmap.getShort();
e_phnum = fmap.getShort();
e_shentsize = fmap.getShort();
e_shnum = fmap.getShort();
e_shstrndx = fmap.getShort();
}
@Override
public String toString() {
return (e_machine.toString() + " " + ei_class.toString() + " "
+ "\ne_type: " + e_type.toString() + "\ne_machine: "
+ e_machine.value() + "\ne_version: " + e_version + "\ne_entry: "
+ e_entry + "\ne_phoff: " + e_phoff + "\ne_shoff: " + e_shoff
+ "\ne_flags: " + e_flags + "\ne_ehsize: " + e_ehsize
+ "\ne_phentsize:" + e_phentsize + "\ne_phnum: " + e_phnum
+ "\ne_shentsize" + e_shentsize + "\ne_shnum: " + e_shnum
+ "\ne_shstrndx: " + e_shstrndx);
}
}
private static class ELFSection {
/** Section header table entry unused */
@SuppressWarnings("unused")
public static final int SHT_NULL = 0;
/** Program data */
@SuppressWarnings("unused")
public static final int SHT_PROGBITS = 1;
/** Symbol table */
@SuppressWarnings("unused")
public static final int SHT_SYMTAB = 2;
/** String table */
public static final int SHT_STRTAB = 3;
/** Relocation entries with addends */
@SuppressWarnings("unused")
public static final int SHT_RELA = 4;
/** Symbol hash table */
@SuppressWarnings("unused")
public static final int SHT_HASH = 5;
/** Dynamic linking information */
@SuppressWarnings("unused")
public static final int SHT_DYNAMIC = 6;
/** Notes */
@SuppressWarnings("unused")
public static final int SHT_NOTE = 7;
/** Program space with no data (bss) */
@SuppressWarnings("unused")
public static final int SHT_NOBITS = 8;
/** Relocation entries, no addends */
@SuppressWarnings("unused")
public static final int SHT_REL = 9;
/** Reserved */
@SuppressWarnings("unused")
public static final int SHT_SHLIB = 10;
/** Dynamic linker symbol table */
@SuppressWarnings("unused")
public static final int SHT_DYNSYM = 11;
/** Array of constructors */
@SuppressWarnings("unused")
public static final int SHT_INIT_ARRAY = 14;
/** Array of destructors */
@SuppressWarnings("unused")
public static final int SHT_FINI_ARRAY = 15;
/** Array of pre-constructors */
@SuppressWarnings("unused")
public static final int SHT_PREINIT_ARRAY = 16;
/** Section group */
@SuppressWarnings("unused")
public static final int SHT_GROUP = 17;
/** Extended section indeces */
@SuppressWarnings("unused")
public static final int SHT_SYMTAB_SHNDX = 18;
/** Number of defined types. */
@SuppressWarnings("unused")
public static final int SHT_NUM = 19;
/** Start OS-specific */
@SuppressWarnings("unused")
public static final int SHT_LOOS = 0x60000000;
/** Prelink library list */
@SuppressWarnings("unused")
public static final int SHT_GNU_LIBLIST = 0x6ffffff7;
/** Checksum for DSO content */
@SuppressWarnings("unused")
public static final int SHT_CHECKSUM = 0x6ffffff8;
/** Sun-specific low bound */
@SuppressWarnings("unused")
public static final int SHT_LOSUNW = 0x6ffffffa;
@SuppressWarnings("unused")
public static final int SHT_SUNW_COMDAT = 0x6ffffffb;
/** Sun-specific high bound */
@SuppressWarnings("unused")
public static final int SHT_HISUNW = 0x6fffffff;
/** End OS-specific type */
@SuppressWarnings("unused")
public static final int SHT_HIOS = 0x6fffffff;
/** Start of processor-specific */
@SuppressWarnings("unused")
public static final int SHT_LOPROC = 0x70000000;
/** End of processor-specific */
@SuppressWarnings("unused")
public static final int SHT_HIPROC = 0x7fffffff;
/** Start of application-specific */
@SuppressWarnings("unused")
public static final int SHT_LOUSER = 0x80000000;
/** End of application-specific */
@SuppressWarnings("unused")
public static final int SHT_HIUSER = 0x8fffffff;
public int sh_name;
public int sh_type;
public int sh_flags;
public int sh_addr;
public int sh_offset;
public int sh_size;
public int sh_link;
public int sh_info;
public int sh_addralign;
public int sh_entsize;
public ELFSection(MappedByteBuffer fmap) {
sh_name = fmap.getInt();
sh_type = fmap.getInt();
sh_flags = fmap.getInt();
sh_addr = fmap.getInt();
sh_offset = fmap.getInt();
sh_size = fmap.getInt();
sh_link = fmap.getInt();
sh_info = fmap.getInt();
sh_addralign = fmap.getInt();
sh_entsize = fmap.getInt();
}
@Override
public String toString() {
return ("\nsh_name : " + sh_name + "\nsh_type : " + sh_type
+ "\nsh_flags: " + sh_flags + "\nsh_addr: " + sh_addr
+ "\nsh_offset: " + sh_offset + "\nsh_size: " + sh_size
+ "\nsh_link: " + sh_link + "\nsh_info: " + sh_info
+ "\nsh_addralign: " + sh_addralign + "\nsh_entsize: "
+ sh_entsize);
}
}
private static enum ELFIdentification {
EI_MAG0(0),
EI_MAG1(1),
EI_MAG2(2),
EI_MAG3(3),
EI_CLASS(4),
EI_DATA(5),
EI_VERSION(6),
EI_PAD(7),
EI_NIDENT(16);
private final int value;
private ELFIdentification(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
private static enum EI_Class {
ELFCLASSNONE(0),
ELFCLASS32(1),
ELFCLASS64(2);
final String[] textual = {
"None", "32", "64"
};
private final int value;
private EI_Class(int value) {
this.value = value;
}
static EI_Class valueOf(byte value) throws IllegalArgumentException {
switch (value) {
case 0: return ELFCLASSNONE;
case 1: return ELFCLASS32;
case 2: return ELFCLASS64;
default:
throw new IllegalArgumentException("Invalid EI_CLASS value:"
+ value);
}
}
@Override
public String toString() {
return textual[value];
}
}
private static enum EI_Data {
ELFDATANONE,
ELFDATA2LSB,
ELFDATA2MSB;
static EI_Data valueOf(byte value) throws IllegalArgumentException {
switch (value) {
case 0: return ELFDATANONE;
case 1: return ELFDATA2LSB;
case 2: return ELFDATA2MSB;
default:
throw new IllegalArgumentException("Invalid EI_DATA value:"
+ value);
}
}
}
private static enum E_Type {
ET_NONE(0),
ET_REL(1),
ET_EXEC(2),
ET_DYN(3),
ET_CORE(4),
ET_UNKNOWN(0xFFFF);
final String[] textual = {
"None", "Relocable", "Executable", "Shared object", "Core"
};
private final int value;
private E_Type(int value) {
this.value = value;
}
static E_Type valueOf(short value) {
switch (value) {
case 0: return ET_NONE;
case 1: return ET_REL;
case 2: return ET_EXEC;
case 3: return ET_DYN;
case 4: return ET_CORE;
default:
return ET_UNKNOWN;
}
}
public int value() {
return this.value;
}
@Override
public String toString() {
if (value == ET_UNKNOWN.value()) {
return "Unknown";
}
return textual[value];
}
}
private static enum E_Machine {
EM_NONE(0),
EM_M32(1),
EM_SPARC(2),
EM_386(3),
EM_68K(4),
EM_88K(5),
EM_860(7),
EM_MIPS(8),
EM_UNKNOWN(0xFFFF);
final String[] textual = {
"No machine", "AT&T WE 32100", "SPARC", "Intel 80386",
"Motorola 68000", "Motorola 88000", null,
"Intel 80860", "MIPS RS3000"
};
private final int value;
private E_Machine(int value) {
this.value = value;
}
static E_Machine valueOf(short value) {
switch (value) {
case 0: return EM_NONE;
case 1: return EM_M32;
case 2: return EM_SPARC;
case 3: return EM_386;
case 4: return EM_68K;
case 5: return EM_88K;
case 7: return EM_860;
case 8: return EM_MIPS;
default:
return EM_UNKNOWN;
}
}
public int value() {
return this.value;
}
@Override
public String toString() {
if (value == EM_UNKNOWN.value()) {
return "Unknown";
}
return textual[value];
}
}
private static enum E_Version {
EV_NONE(0),
EV_CURRENT(1);
final String[] textual = {
"Invalid", "Current"
};
private final int value;
private E_Version(int value) {
this.value = value;
}
static E_Version valueOf(int value) throws IllegalArgumentException {
switch (value) {
case 0: return EV_NONE;
case 1: return EV_CURRENT;
default:
throw new IllegalArgumentException("Illegal (or unknown) "
+ "version number: " + value);
}
}
@Override
public String toString() {
return textual[value];
}
}
}