| /* |
| * 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] & 0xff; |
| } |
| |
| 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] & 0xff); |
| } else { |
| answer = mBuffer[0] << 8 | (mBuffer[1] & 0xff); |
| } |
| |
| 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] & 0xff); |
| } |
| } |
| |
| 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); |
| } |
| } |