blob: c2da318d5849b4ceeed3d2ea4b3a718ab2e4da79 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os.cts;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
/**
* A poor man's implementation of the readelf command. This program is
* designed to parse ELF (Executable and Linkable Format) files.
*/
public class ReadElf implements AutoCloseable {
/** The magic values for the ELF identification. */
private static final byte[] ELF_IDENT = {
(byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F',
};
private static final int EI_CLASS = 4;
private static final int EI_DATA = 5;
private static final int EM_386 = 3;
private static final int EM_MIPS = 8;
private static final int EM_ARM = 40;
// http://en.wikipedia.org/wiki/Qualcomm_Hexagon
private static final int EM_QDSP6 = 164;
/** Size of the e_ident[] structure in the ELF header. */
private static final int EI_NIDENT = 16;
/** Offset from end of ident structure in half-word sizes. */
private static final int OFFSET_TYPE = 0;
/** Machine type. */
private static final int OFFSET_MACHINE = 1;
/** ELF version. */
private static final int OFFSET_VERSION = 2;
/**
* The offset to which the system transfers control. e.g., the first thing
* executed.
*/
private static final int OFFSET_ENTRY = 4;
/** Program header offset in bytes. */
private static final int OFFSET_PHOFF = 6;
/** Segment header offset in bytes. */
private static final int OFFSET_SHOFF = 8;
/** Processor-specific flags for binary. */
private static final int OFFSET_FLAGS = 10;
/** ELF header size in bytes. */
private static final int OFFSET_EHSIZE = 12;
/** All program headers entry size in bytes. */
private static final int OFFSET_PHENTSIZE = 13;
/** Number of program headers in ELF. */
private static final int OFFSET_PHNUM = 14;
/** All segment headers entry size in bytes. */
private static final int OFFSET_SHENTSIZE = 15;
/** Number of segment headers in ELF. */
private static final int OFFSET_SHNUM = 16;
/** The section header index that refers to string table. */
private static final int OFFSET_SHSTRNDX = 17;
/** Program header offset for type of this program header. */
private static final int PHOFF_TYPE = 0;
/** Program header offset for absolute offset in file. */
private static final int PHOFF_OFFSET = 2;
/** Program header offset for virtual address. */
private static final int PHOFF_VADDR = 4;
/** Program header offset for physical address. */
private static final int PHOFF_PADDR = 6;
/** Program header offset for file size in bytes. */
private static final int PHOFF_FILESZ = 8;
/** Program header offset for memory size in bytes. */
private static final int PHOFF_MEMSZ = 10;
/** Program header offset for flags. */
private static final int PHOFF_FLAGS = 12;
/**
* Program header offset for required alignment. 0 or 1 means no alignment
* necessary.
*/
private static final int PHOFF_ALIGN = 14;
/** Index into string pool for segment name. */
private static final long SHOFF_NAME = 0;
/** Segment header offset for type (half-words) */
private static final long SHOFF_TYPE = 2;
/** Segment header offset for offset (meta!) (half-words) */
private static final long SHOFF_OFFSET = 8;
/** Segment header offset for size (half-words) */
private static final long SHOFF_SIZE = 10;
/** Data is presented in LSB format. */
private static final int ELFDATA2LSB = 1;
/** Date is presented in MSB format. */
private static final int ELFDATA2MSB = 2;
private static final int ELFCLASS32 = 1;
private static final int ELFCLASS64 = 2;
private static final long PT_LOAD = 1;
/** Section Type: Symbol Table */
private static final int SHT_SYMTAB = 2;
/** Section Type: String Table */
private static final int SHT_STRTAB = 3;
/** Section Type: Dynamic **/
private static final int SHT_DYNAMIC = 6;
/** Section Type: Dynamic Symbol Table */
private static final int SHT_DYNSYM = 11;
/** Symbol Table Entry: Name offset */
private static final int SYMTAB_NAME = 0;
/** Symbol Table Entry: SymTab Info */
private static final int SYMTAB_ST_INFO = 6;
/** Symbol Table Entry size (half-words) */
private static final int SYMTAB_ENTRY_HALFWORD_SIZE = 7;
/**
* Symbol Table Entry size (extra in bytes) to cover "st_info" and
* "st_other"
*/
private static final int SYMTAB_ENTRY_BYTE_EXTRA_SIZE = 2;
public static class Symbol {
public static final int STB_LOCAL = 0;
public static final int STB_GLOBAL = 1;
public static final int STB_WEAK = 2;
public static final int STB_LOPROC = 13;
public static final int STB_HIPROC = 15;
public final String name;
public final int bind;
public final int type;
Symbol(String name, int st_info) {
this.name = name;
this.bind = (st_info >> 4) & 0x0F;
this.type = st_info & 0x0F;
}
};
private final String mPath;
private final RandomAccessFile mFile;
private final byte[] mBuffer = new byte[512];
private int mEndian;
private boolean mIsDynamic;
private boolean mIsPIE;
private int mType;
private int mWordSize;
private int mHalfWordSize;
/** Symbol Table offset */
private long mSymTabOffset;
/** Symbol Table size */
private long mSymTabSize;
/** Dynamic Symbol Table offset */
private long mDynSymOffset;
/** Dynamic Symbol Table size */
private long mDynSymSize;
/** Section Header String Table offset */
private long mShStrTabOffset;
/** Section Header String Table size */
private long mShStrTabSize;
/** String Table offset */
private long mStrTabOffset;
/** String Table size */
private long mStrTabSize;
/** Dynamic String Table offset */
private long mDynStrOffset;
/** Dynamic String Table size */
private long mDynStrSize;
/** Symbol Table symbol names */
private Map<String, Symbol> mSymbols;
/** Dynamic Symbol Table symbol names */
private Map<String, Symbol> mDynamicSymbols;
public static ReadElf read(File file) throws IOException {
return new ReadElf(file);
}
public boolean isDynamic() {
return mIsDynamic;
}
public int getType() {
return mType;
}
public boolean isPIE() {
return mIsPIE;
}
private ReadElf(File file) throws IOException {
mPath = file.getPath();
mFile = new RandomAccessFile(file, "r");
if (mFile.length() < EI_NIDENT) {
throw new IllegalArgumentException("Too small to be an ELF file: " + file);
}
readIdent();
readHeader();
}
public void close() {
try {
mFile.close();
} catch (IOException ignored) {
}
}
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
private void readHeader() throws IOException {
mType = readHalf(getHeaderOffset(OFFSET_TYPE));
int e_machine = readHalf(getHeaderOffset(OFFSET_MACHINE));
if (e_machine != EM_386 && e_machine != EM_MIPS && e_machine != EM_ARM &&
e_machine != EM_QDSP6) {
throw new IOException("Invalid ELF e_machine: " + e_machine + ": " + mPath);
}
final long shOffset = readWord(getHeaderOffset(OFFSET_SHOFF));
final int shNumber = readHalf(getHeaderOffset(OFFSET_SHNUM));
final int shSize = readHalf(getHeaderOffset(OFFSET_SHENTSIZE));
final int shStrIndex = readHalf(getHeaderOffset(OFFSET_SHSTRNDX));
readSectionHeaders(shOffset, shNumber, shSize, shStrIndex);
final long phOffset = readWord(getHeaderOffset(OFFSET_PHOFF));
final int phNumber = readHalf(getHeaderOffset(OFFSET_PHNUM));
final int phSize = readHalf(getHeaderOffset(OFFSET_PHENTSIZE));
readProgramHeaders(phOffset, phNumber, phSize);
}
private void readSectionHeaders(long tableOffset, int shNumber, int shSize, int shStrIndex)
throws IOException {
// Read the Section Header String Table offset first.
{
final long shStrTabShOffset = tableOffset + shStrIndex * shSize;
final long type = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_TYPE);
if (type == SHT_STRTAB) {
mShStrTabOffset = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_OFFSET);
mShStrTabSize = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_SIZE);
}
}
for (int i = 0; i < shNumber; i++) {
// Don't bother to re-read the Section Header StrTab.
if (i == shStrIndex) {
continue;
}
final long shOffset = tableOffset + i * shSize;
final long type = readWord(shOffset + mHalfWordSize * SHOFF_TYPE);
if ((type == SHT_SYMTAB) || (type == SHT_DYNSYM)) {
final long nameOffset = readWord(shOffset + mHalfWordSize * SHOFF_NAME);
final long offset = readWord(shOffset + mHalfWordSize * SHOFF_OFFSET);
final long size = readWord(shOffset + mHalfWordSize * SHOFF_SIZE);
final String symTabName = readShStrTabEntry(nameOffset);
if (".symtab".equals(symTabName)) {
mSymTabOffset = offset;
mSymTabSize = size;
} else if (".dynsym".equals(symTabName)) {
mDynSymOffset = offset;
mDynSymSize = size;
}
} else if (type == SHT_STRTAB) {
final long nameOffset = readWord(shOffset + mHalfWordSize * SHOFF_NAME);
final long offset = readWord(shOffset + mHalfWordSize * SHOFF_OFFSET);
final long size = readWord(shOffset + mHalfWordSize * SHOFF_SIZE);
final String strTabName = readShStrTabEntry(nameOffset);
if (".strtab".equals(strTabName)) {
mStrTabOffset = offset;
mStrTabSize = size;
} else if (".dynstr".equals(strTabName)) {
mDynStrOffset = offset;
mDynStrSize = size;
}
} else if (type == SHT_DYNAMIC) {
mIsDynamic = true;
}
}
}
private void readProgramHeaders(long phOffset, int phNumber, int phSize) throws IOException {
for (int i = 0; i < phNumber; i++) {
final long baseOffset = phOffset + i * phSize;
final long type = readWord(baseOffset);
if (type == PT_LOAD) {
final long virtAddress = readWord(baseOffset + mHalfWordSize * PHOFF_VADDR);
if (virtAddress == 0) {
mIsPIE = true;
}
}
}
}
private void readSymbolTable(Map<String, Symbol> symbolMap, long symStrOffset, long symStrSize,
long symOffset, long symSize) throws IOException {
final long symEnd = symOffset + symSize;
for (long off = symOffset; off < symEnd; off += SYMTAB_ENTRY_HALFWORD_SIZE * mHalfWordSize
+ SYMTAB_ENTRY_BYTE_EXTRA_SIZE) {
long strOffset = readWord(off + SYMTAB_NAME);
if (strOffset == 0) {
continue;
}
final String symName = readStrTabEntry(symStrOffset, symStrSize, strOffset);
if (symName != null) {
final int st_info = readByte(off + SYMTAB_ST_INFO);
symbolMap.put(symName, new Symbol(symName, st_info));
}
}
}
private String readShStrTabEntry(long strOffset) throws IOException {
if ((mShStrTabOffset == 0) || (strOffset < 0) || (strOffset >= mShStrTabSize)) {
return null;
}
return readString(mShStrTabOffset + strOffset);
}
private String readStrTabEntry(long tableOffset, long tableSize, long strOffset)
throws IOException {
if ((tableOffset == 0) || (strOffset < 0) || (strOffset >= tableSize)) {
return null;
}
return readString(tableOffset + strOffset);
}
private int getHeaderOffset(int halfWorldOffset) {
return EI_NIDENT + halfWorldOffset * mHalfWordSize;
}
private int readByte(long offset) throws IOException {
mFile.seek(offset);
mFile.readFully(mBuffer, 0, 1);
return mBuffer[0];
}
private int readHalf(long offset) throws IOException {
mFile.seek(offset);
mFile.readFully(mBuffer, 0, mWordSize);
final int answer;
if (mEndian == ELFDATA2LSB) {
answer = mBuffer[1] << 8 | mBuffer[0];
} else {
answer = mBuffer[0] << 8 | mBuffer[1];
}
return answer;
}
private long readWord(long offset) throws IOException {
mFile.seek(offset);
mFile.readFully(mBuffer, 0, mWordSize);
int answer = 0;
if (mEndian == ELFDATA2LSB) {
for (int i = mWordSize - 1; i >= 0; i--) {
answer = (answer << 8) | (mBuffer[i] & 0xFF);
}
} else {
final int N = mWordSize - 1;
for (int i = 0; i <= N; i++) {
answer = (answer << 8) | mBuffer[i];
}
}
return answer;
}
private String readString(long offset) throws IOException {
mFile.seek(offset);
mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset));
for (int i = 0; i < mBuffer.length; i++) {
if (mBuffer[i] == 0) {
return new String(mBuffer, 0, i);
}
}
return null;
}
private void readIdent() throws IOException {
mFile.seek(0);
mFile.readFully(mBuffer, 0, EI_NIDENT);
if ((mBuffer[0] != ELF_IDENT[0]) || (mBuffer[1] != ELF_IDENT[1])
|| (mBuffer[2] != ELF_IDENT[2]) || (mBuffer[3] != ELF_IDENT[3])) {
throw new IllegalArgumentException("Invalid ELF file: " + mPath);
}
int elfClass = mBuffer[EI_CLASS];
if (elfClass == ELFCLASS32) {
mWordSize = 4;
mHalfWordSize = 2;
} else if (elfClass == ELFCLASS64) {
throw new IOException("Unsupported ELFCLASS64 file: " + mPath);
} else {
throw new IOException("Invalid ELF EI_CLASS: " + elfClass + ": " + mPath);
}
mEndian = mBuffer[EI_DATA];
if (mEndian == ELFDATA2LSB) {
} else if (mEndian == ELFDATA2MSB) {
throw new IOException("Unsupported ELFDATA2MSB file: " + mPath);
} else {
throw new IOException("Invalid ELF EI_DATA: " + mEndian + ": " + mPath);
}
}
public Symbol getSymbol(String name) {
if ((mSymTabOffset == 0) && (mSymTabSize == 0)) {
return null;
}
if (mSymbols == null) {
mSymbols = new HashMap<String, Symbol>();
try {
readSymbolTable(mSymbols, mStrTabOffset, mStrTabSize, mSymTabOffset, mSymTabSize);
} catch (IOException e) {
return null;
}
}
return mSymbols.get(name);
}
public Symbol getDynamicSymbol(String name) {
if ((mDynSymOffset == 0) && (mDynSymSize == 0)) {
return null;
}
if (mDynamicSymbols == null) {
mDynamicSymbols = new HashMap<String, Symbol>();
try {
readSymbolTable(mDynamicSymbols, mDynStrOffset, mDynStrSize, mDynSymOffset,
mDynSymSize);
} catch (IOException e) {
return null;
}
}
return mDynamicSymbols.get(name);
}
}