/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.tinker.loader.shareutil;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* Created by tangyinsheng on 2017/3/13.
*/
public class ShareElfFile implements Closeable {
public static final int FILE_TYPE_OTHERS = -1;
public static final int FILE_TYPE_ODEX = 0;
public static final int FILE_TYPE_ELF = 1;
private final FileInputStream fis;
private final Map<String, SectionHeader> sectionNameToHeaderMap = new HashMap<>();
public ElfHeader elfHeader = null;
public ProgramHeader[] programHeaders = null;
public SectionHeader[] sectionHeaders = null;
public ShareElfFile(File file) throws IOException {
fis = new FileInputStream(file);
final FileChannel channel = fis.getChannel();
elfHeader = new ElfHeader(channel);
final ByteBuffer headerBuffer = ByteBuffer.allocate(128);
headerBuffer.limit(elfHeader.ePhEntSize);
headerBuffer.order(elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
channel.position(elfHeader.ePhOff);
programHeaders = new ProgramHeader[elfHeader.ePhNum];
for (int i = 0; i < programHeaders.length; ++i) {
readUntilLimit(channel, headerBuffer, "failed to read phdr.");
programHeaders[i] = new ProgramHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]);
}
channel.position(elfHeader.eShOff);
headerBuffer.limit(elfHeader.eShEntSize);
sectionHeaders = new SectionHeader[elfHeader.eShNum];
for (int i = 0; i < sectionHeaders.length; ++i) {
readUntilLimit(channel, headerBuffer, "failed to read shdr.");
sectionHeaders[i] = new SectionHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]);
}
if (elfHeader.eShStrNdx > 0) {
final SectionHeader shStrTabSectionHeader = sectionHeaders[elfHeader.eShStrNdx];
final ByteBuffer shStrTab = getSection(shStrTabSectionHeader);
for (SectionHeader shdr : sectionHeaders) {
shStrTab.position(shdr.shName);
shdr.shNameStr = readCString(shStrTab);
sectionNameToHeaderMap.put(shdr.shNameStr, shdr);
}
}
}
private static void assertInRange(int b, int lb, int ub, String errMsg) throws IOException {
if (b < lb || b > ub) {
throw new IOException(errMsg);
}
}
public static int getFileTypeByMagic(File file) throws IOException {
InputStream is = null;
try {
final byte[] magicBuf = new byte[4];
is = new FileInputStream(file);
is.read(magicBuf);
if (magicBuf[0] == 'd' && magicBuf[1] == 'e' && magicBuf[2] == 'y' && magicBuf[3] == '\n') {
return FILE_TYPE_ODEX;
} else if (magicBuf[0] == 0x7F && magicBuf[1] == 'E' && magicBuf[2] == 'L' && magicBuf[3] == 'F') {
return FILE_TYPE_ELF;
} else {
return FILE_TYPE_OTHERS;
}
} finally {
if (is != null) {
try {
is.close();
} catch (Throwable thr) {
// Ignored.
}
}
}
}
public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException {
bufferOut.rewind();
int bytesRead = channel.read(bufferOut);
if (bytesRead != bufferOut.limit()) {
throw new IOException(errMsg + " Rest bytes insufficient, expect to read "
+ bufferOut.limit() + " bytes but only "
+ bytesRead + " bytes were read.");
}
bufferOut.flip();
}
public static String readCString(ByteBuffer buffer) {
final byte[] rawBuffer = buffer.array();
int begin = buffer.position();
while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) {
buffer.position(buffer.position() + 1);
}
// Move to the start of next cstring.
buffer.position(buffer.position() + 1);
return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName("ASCII"));
}
public FileChannel getChannel() {
return fis.getChannel();
}
public boolean is32BitElf() {
return (elfHeader.eIndent[ElfHeader.EI_CLASS] == ElfHeader.ELFCLASS32);
}
public ByteOrder getDataOrder() {
return (elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
public SectionHeader getSectionHeaderByName(String name) {
return sectionNameToHeaderMap.get(name);
}
public ByteBuffer getSection(SectionHeader sectionHeader) throws IOException {
final ByteBuffer result = ByteBuffer.allocate((int) sectionHeader.shSize);
fis.getChannel().position(sectionHeader.shOffset);
readUntilLimit(fis.getChannel(), result, "failed to read section: " + sectionHeader.shNameStr);
return result;
}
public ByteBuffer getSegment(ProgramHeader programHeader) throws IOException {
final ByteBuffer result = ByteBuffer.allocate((int) programHeader.pFileSize);
fis.getChannel().position(programHeader.pOffset);
readUntilLimit(fis.getChannel(), result, "failed to read segment (type: " + programHeader.pType + ").");
return result;
}
@Override
public void close() throws IOException {
fis.close();
sectionNameToHeaderMap.clear();
programHeaders = null;
sectionHeaders = null;
}
public static class ElfHeader {
// Elf indent field index.
public static final int EI_CLASS = 4;
public static final int EI_DATA = 5;
public static final int EI_VERSION = 6;
// Elf classes.
public static final int ELFCLASS32 = 1;
public static final int ELFCLASS64 = 2;
// Elf data encoding.
public static final int ELFDATA2LSB = 1;
public static final int ELFDATA2MSB = 2;
// Elf types.
public static final int ET_NONE = 0;
public static final int ET_REL = 1;
public static final int ET_EXEC = 2;
public static final int ET_DYN = 3;
public static final int ET_CORE = 4;
public static final int ET_LOPROC = 0xff00;
public static final int ET_HIPROC = 0xffff;
// Elf indent version.
public static final int EV_CURRENT = 1;
private static final int EI_NINDENT = 16;
public final byte[] eIndent = new byte[EI_NINDENT];
public final short eType;
public final short eMachine;
public final int eVersion;
public final long eEntry;
public final long ePhOff;
public final long eShOff;
public final int eFlags;
public final short eEhSize;
public final short ePhEntSize;
public final short ePhNum;
public final short eShEntSize;
public final short eShNum;
public final short eShStrNdx;
private ElfHeader(FileChannel channel) throws IOException {
channel.position(0);
channel.read(ByteBuffer.wrap(eIndent));
if (eIndent[0] != 0x7F || eIndent[1] != 'E' || eIndent[2] != 'L' || eIndent[3] != 'F') {
throw new IOException(String.format("bad elf magic: %x %x %x %x.", eIndent[0], eIndent[1], eIndent[2], eIndent[3]));
}
assertInRange(eIndent[EI_CLASS], ELFCLASS32, ELFCLASS64, "bad elf class: " + eIndent[EI_CLASS]);
assertInRange(eIndent[EI_DATA], ELFDATA2LSB, ELFDATA2MSB, "bad elf data encoding: " + eIndent[EI_DATA]);
final ByteBuffer restBuffer = ByteBuffer.allocate(eIndent[EI_CLASS] == ELFCLASS32 ? 36 : 48);
restBuffer.order(eIndent[EI_DATA] == ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
readUntilLimit(channel, restBuffer, "failed to read rest part of ehdr.");
eType = restBuffer.getShort();
eMachine = restBuffer.getShort();
eVersion = restBuffer.getInt();
assertInRange(eVersion, EV_CURRENT, EV_CURRENT, "bad elf version: " + eVersion);
switch (eIndent[EI_CLASS]) {
case ELFCLASS32:
eEntry = restBuffer.getInt();
ePhOff = restBuffer.getInt();
eShOff = restBuffer.getInt();
break;
case ELFCLASS64:
eEntry = restBuffer.getLong();
ePhOff = restBuffer.getLong();
eShOff = restBuffer.getLong();
break;
default:
throw new IOException("Unexpected elf class: " + eIndent[EI_CLASS]);
}
eFlags = restBuffer.getInt();
eEhSize = restBuffer.getShort();
ePhEntSize = restBuffer.getShort();
ePhNum = restBuffer.getShort();
eShEntSize = restBuffer.getShort();
eShNum = restBuffer.getShort();
eShStrNdx = restBuffer.getShort();
}
}
public static class ProgramHeader {
// Segment types.
public static final int PT_NULL = 0;
public static final int PT_LOAD = 1;
public static final int PT_DYNAMIC = 2;
public static final int PT_INTERP = 3;
public static final int PT_NOTE = 4;
public static final int PT_SHLIB = 5;
public static final int PT_PHDR = 6;
public static final int PT_LOPROC = 0x70000000;
public static final int PT_HIPROC = 0x7fffffff;
// Segment flags.
public static final int PF_R = 0x04;
public static final int PF_W = 0x02;
public static final int PF_X = 0x01;
public final int pType;
public final int pFlags;
public final long pOffset;
public final long pVddr;
public final long pPddr;
public final long pFileSize;
public final long pMemSize;
public final long pAlign;
private ProgramHeader(ByteBuffer buffer, int elfClass) throws IOException {
switch (elfClass) {
case ElfHeader.ELFCLASS32:
pType = buffer.getInt();
pOffset = buffer.getInt();
pVddr = buffer.getInt();
pPddr = buffer.getInt();
pFileSize = buffer.getInt();
pMemSize = buffer.getInt();
pFlags = buffer.getInt();
pAlign = buffer.getInt();
break;
case ElfHeader.ELFCLASS64:
pType = buffer.getInt();
pFlags = buffer.getInt();
pOffset = buffer.getLong();
pVddr = buffer.getLong();
pPddr = buffer.getLong();
pFileSize = buffer.getLong();
pMemSize = buffer.getLong();
pAlign = buffer.getLong();
break;
default:
throw new IOException("Unexpected elf class: " + elfClass);
}
}
}
public static class SectionHeader {
// Special section indexes.
public static final int SHN_UNDEF = 0;
public static final int SHN_LORESERVE = 0xff00;
public static final int SHN_LOPROC = 0xff00;
public static final int SHN_HIPROC = 0xff1f;
public static final int SHN_ABS = 0xfff1;
public static final int SHN_COMMON = 0xfff2;
public static final int SHN_HIRESERVE = 0xffff;
// Section types.
public static final int SHT_NULL = 0;
public static final int SHT_PROGBITS = 1;
public static final int SHT_SYMTAB = 2;
public static final int SHT_STRTAB = 3;
public static final int SHT_RELA = 4;
public static final int SHT_HASH = 5;
public static final int SHT_DYNAMIC = 6;
public static final int SHT_NOTE = 7;
public static final int SHT_NOBITS = 8;
public static final int SHT_REL = 9;
public static final int SHT_SHLIB = 10;
public static final int SHT_DYNSYM = 11;
public static final int SHT_LOPROC = 0x70000000;
public static final int SHT_HIPROC = 0x7fffffff;
public static final int SHT_LOUSER = 0x80000000;
public static final int SHT_HIUSER = 0xffffffff;
// Section flags.
public static final int SHF_WRITE = 0x1;
public static final int SHF_ALLOC = 0x2;
public static final int SHF_EXECINSTR = 0x4;
public static final int SHF_MASKPROC = 0xf0000000;
public final int shName;
public final int shType;
public final long shFlags;
public final long shAddr;
public final long shOffset;
public final long shSize;
public final int shLink;
public final int shInfo;
public final long shAddrAlign;
public final long shEntSize;
public String shNameStr;
private SectionHeader(ByteBuffer buffer, int elfClass) throws IOException {
switch (elfClass) {
case ElfHeader.ELFCLASS32:
shName = buffer.getInt();
shType = buffer.getInt();
shFlags = buffer.getInt();
shAddr = buffer.getInt();
shOffset = buffer.getInt();
shSize = buffer.getInt();
shLink = buffer.getInt();
shInfo = buffer.getInt();
shAddrAlign = buffer.getInt();
shEntSize = buffer.getInt();
break;
case ElfHeader.ELFCLASS64:
shName = buffer.getInt();
shType = buffer.getInt();
shFlags = buffer.getLong();
shAddr = buffer.getLong();
shOffset = buffer.getLong();
shSize = buffer.getLong();
shLink = buffer.getInt();
shInfo = buffer.getInt();
shAddrAlign = buffer.getLong();
shEntSize = buffer.getLong();
break;
default:
throw new IOException("Unexpected elf class: " + elfClass);
}
shNameStr = null;
}
}
}