blob: 559cbd04dac6374f0d3e71150b71c66b20bbf7f1 [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.cts.util;
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[] ELFMAG = {
(byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F', };
private static final int EI_NIDENT = 16;
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;
private static final int EM_X86_64 = 62;
// http://en.wikipedia.org/wiki/Qualcomm_Hexagon
private static final int EM_QDSP6 = 164;
private static final int EM_AARCH64 = 183;
private static final int ELFCLASS32 = 1;
private static final int ELFCLASS64 = 2;
private static final int ELFDATA2LSB = 1;
private static final int ELFDATA2MSB = 2;
private static final int EV_CURRENT = 1;
private static final long PT_LOAD = 1;
private static final int SHT_SYMTAB = 2;
private static final int SHT_STRTAB = 3;
private static final int SHT_DYNAMIC = 6;
private static final int SHT_DYNSYM = 11;
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 static final int STT_NOTYPE = 0;
public static final int STT_OBJECT = 1;
public static final int STT_FUNC = 2;
public static final int STT_SECTION = 3;
public static final int STT_FILE = 4;
public static final int STT_COMMON = 5;
public static final int STT_TLS = 6;
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;
}
@Override
public String toString() {
return "Symbol[" + name + "," + toBind() + "," + toType() + "]";
}
private String toBind() {
switch (bind) {
case STB_LOCAL:
return "LOCAL";
case STB_GLOBAL:
return "GLOBAL";
case STB_WEAK:
return "WEAK";
}
return "STB_??? (" + bind + ")";
}
private String toType() {
switch (type) {
case STT_NOTYPE:
return "NOTYPE";
case STT_OBJECT:
return "OBJECT";
case STT_FUNC:
return "FUNC";
case STT_SECTION:
return "SECTION";
case STT_FILE:
return "FILE";
case STT_COMMON:
return "COMMON";
case STT_TLS:
return "TLS";
}
return "STT_??? (" + type + ")";
}
}
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 mAddrSize;
/** 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 static void main(String[] args) throws IOException {
for (String arg : args) {
ReadElf re = new ReadElf(new File(arg));
re.getSymbol("x");
re.getDynamicSymbol("x");
re.close();
}
}
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);
}
readHeader();
}
@Override
public void close() {
try {
mFile.close();
} catch (IOException ignored) {
}
}
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
private void readHeader() throws IOException {
mFile.seek(0);
mFile.readFully(mBuffer, 0, EI_NIDENT);
if (mBuffer[0] != ELFMAG[0] || mBuffer[1] != ELFMAG[1] ||
mBuffer[2] != ELFMAG[2] || mBuffer[3] != ELFMAG[3]) {
throw new IllegalArgumentException("Invalid ELF file: " + mPath);
}
int elfClass = mBuffer[EI_CLASS];
if (elfClass == ELFCLASS32) {
mAddrSize = 4;
} else if (elfClass == ELFCLASS64) {
mAddrSize = 8;
} 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);
}
mType = readHalf();
int e_machine = readHalf();
if (e_machine != EM_386 && e_machine != EM_X86_64 &&
e_machine != EM_AARCH64 && e_machine != EM_ARM &&
e_machine != EM_MIPS &&
e_machine != EM_QDSP6) {
throw new IOException("Invalid ELF e_machine: " + e_machine + ": " + mPath);
}
// AbiTest relies on us rejecting any unsupported combinations.
if ((e_machine == EM_386 && elfClass != ELFCLASS32) ||
(e_machine == EM_X86_64 && elfClass != ELFCLASS64) ||
(e_machine == EM_AARCH64 && elfClass != ELFCLASS64) ||
(e_machine == EM_ARM && elfClass != ELFCLASS32) ||
(e_machine == EM_QDSP6 && elfClass != ELFCLASS32)) {
throw new IOException("Invalid e_machine/EI_CLASS ELF combination: " +
e_machine + "/" + elfClass + ": " + mPath);
}
long e_version = readWord();
if (e_version != EV_CURRENT) {
throw new IOException("Invalid e_version: " + e_version + ": " + mPath);
}
long e_entry = readAddr();
long ph_off = readOff();
long sh_off = readOff();
long e_flags = readWord();
int e_ehsize = readHalf();
int e_phentsize = readHalf();
int e_phnum = readHalf();
int e_shentsize = readHalf();
int e_shnum = readHalf();
int e_shstrndx = readHalf();
readSectionHeaders(sh_off, e_shnum, e_shentsize, e_shstrndx);
readProgramHeaders(ph_off, e_phnum, e_phentsize);
}
private void readSectionHeaders(long sh_off, int e_shnum, int e_shentsize, int e_shstrndx)
throws IOException {
// Read the Section Header String Table offset first.
{
mFile.seek(sh_off + e_shstrndx * e_shentsize);
long sh_name = readWord();
long sh_type = readWord();
long sh_flags = readX(mAddrSize);
long sh_addr = readAddr();
long sh_offset = readOff();
long sh_size = readX(mAddrSize);
// ...
if (sh_type == SHT_STRTAB) {
mShStrTabOffset = sh_offset;
mShStrTabSize = sh_size;
}
}
for (int i = 0; i < e_shnum; ++i) {
// Don't bother to re-read the Section Header StrTab.
if (i == e_shstrndx) {
continue;
}
mFile.seek(sh_off + i * e_shentsize);
long sh_name = readWord();
long sh_type = readWord();
long sh_flags = readX(mAddrSize);
long sh_addr = readAddr();
long sh_offset = readOff();
long sh_size = readX(mAddrSize);
if (sh_type == SHT_SYMTAB || sh_type == SHT_DYNSYM) {
final String symTabName = readShStrTabEntry(sh_name);
if (".symtab".equals(symTabName)) {
mSymTabOffset = sh_offset;
mSymTabSize = sh_size;
} else if (".dynsym".equals(symTabName)) {
mDynSymOffset = sh_offset;
mDynSymSize = sh_size;
}
} else if (sh_type == SHT_STRTAB) {
final String strTabName = readShStrTabEntry(sh_name);
if (".strtab".equals(strTabName)) {
mStrTabOffset = sh_offset;
mStrTabSize = sh_size;
} else if (".dynstr".equals(strTabName)) {
mDynStrOffset = sh_offset;
mDynStrSize = sh_size;
}
} else if (sh_type == SHT_DYNAMIC) {
mIsDynamic = true;
}
}
}
private void readProgramHeaders(long ph_off, int e_phnum, int e_phentsize) throws IOException {
for (int i = 0; i < e_phnum; ++i) {
mFile.seek(ph_off + i * e_phentsize);
long p_type = readWord();
if (p_type == PT_LOAD) {
if (mAddrSize == 8) {
// Only in Elf64_phdr; in Elf32_phdr p_flags is at the end.
long p_flags = readWord();
}
long p_offset = readOff();
long p_vaddr = readAddr();
// ...
if (p_vaddr == 0) {
mIsPIE = true;
}
}
}
}
private HashMap<String, Symbol> readSymbolTable(long symStrOffset, long symStrSize,
long tableOffset, long tableSize) throws IOException {
HashMap<String, Symbol> result = new HashMap<String, Symbol>();
mFile.seek(tableOffset);
while (mFile.getFilePointer() < tableOffset + tableSize) {
long st_name = readWord();
int st_info;
if (mAddrSize == 8) {
st_info = readByte();
int st_other = readByte();
int st_shndx = readHalf();
long st_value = readAddr();
long st_size = readX(mAddrSize);
} else {
long st_value = readAddr();
long st_size = readWord();
st_info = readByte();
int st_other = readByte();
int st_shndx = readHalf();
}
if (st_name == 0) {
continue;
}
final String symName = readStrTabEntry(symStrOffset, symStrSize, st_name);
if (symName != null) {
Symbol s = new Symbol(symName, st_info);
result.put(symName, s);
}
}
return result;
}
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 readHalf() throws IOException {
return (int) readX(2);
}
private long readWord() throws IOException {
return readX(4);
}
private long readOff() throws IOException {
return readX(mAddrSize);
}
private long readAddr() throws IOException {
return readX(mAddrSize);
}
private long readX(int byteCount) throws IOException {
mFile.readFully(mBuffer, 0, byteCount);
int answer = 0;
if (mEndian == ELFDATA2LSB) {
for (int i = byteCount - 1; i >= 0; i--) {
answer = (answer << 8) | (mBuffer[i] & 0xff);
}
} else {
final int N = byteCount - 1;
for (int i = 0; i <= N; ++i) {
answer = (answer << 8) | (mBuffer[i] & 0xff);
}
}
return answer;
}
private String readString(long offset) throws IOException {
long originalOffset = mFile.getFilePointer();
mFile.seek(offset);
mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset));
mFile.seek(originalOffset);
for (int i = 0; i < mBuffer.length; ++i) {
if (mBuffer[i] == 0) {
return new String(mBuffer, 0, i);
}
}
return null;
}
private int readByte() throws IOException {
return mFile.read() & 0xff;
}
public Symbol getSymbol(String name) {
if (mSymbols == null) {
try {
mSymbols = readSymbolTable(mStrTabOffset, mStrTabSize, mSymTabOffset, mSymTabSize);
} catch (IOException e) {
return null;
}
}
return mSymbols.get(name);
}
public Symbol getDynamicSymbol(String name) {
if (mDynamicSymbols == null) {
try {
mDynamicSymbols = readSymbolTable(
mDynStrOffset, mDynStrSize, mDynSymOffset, mDynSymSize);
} catch (IOException e) {
return null;
}
}
return mDynamicSymbols.get(name);
}
}