/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.soloader; import java.io.FileInputStream; import java.io.IOException; import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; /** * Extract SoLoader boottsrap information from an ELF file. This is not a general purpose ELF * library. * * See specification at http://www.sco.com/developers/gabi/latest/contents.html. You will not be * able to verify the operation of the functions below without having read the ELF specification. */ public final class MinElf { public static final int ELF_MAGIC = 0x464c457f; public static final int DT_NULL = 0; public static final int DT_NEEDED = 1; public static final int DT_STRTAB = 5; public static final int PT_LOAD = 1; public static final int PT_DYNAMIC = 2; public static final int PN_XNUM = 0xFFFF; public static String[] extract_DT_NEEDED(File elfFile) throws IOException { FileInputStream is = new FileInputStream(elfFile); try { return extract_DT_NEEDED(is.getChannel()); } finally { is.close(); // Won't throw } } /** * Treating {@code bb} as an ELF file, extract all the DT_NEEDED entries from its dynamic section. * * @param fc FileChannel referring to ELF file * @return Array of strings, one for each DT_NEEDED entry, in file order */ public static String[] extract_DT_NEEDED(FileChannel fc) throws IOException { // // All constants below are fixed by the ELF specification and are the offsets of fields within // the elf.h data structures. // ByteBuffer bb = ByteBuffer.allocate(8 /* largest read unit */); // Read ELF header. bb.order(ByteOrder.LITTLE_ENDIAN); if (getu32(fc, bb, Elf32_Ehdr.e_ident) != ELF_MAGIC) { throw new ElfError("file is not ELF"); } boolean is32 = (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x4) == 1); if (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x5) == 2) { bb.order(ByteOrder.BIG_ENDIAN); } // Offsets above are identical in 32- and 64-bit cases. // Find the offset of the dynamic linking information. long e_phoff = is32 ? getu32(fc, bb, Elf32_Ehdr.e_phoff) : get64(fc, bb, Elf64_Ehdr.e_phoff); long e_phnum = is32 ? getu16(fc, bb, Elf32_Ehdr.e_phnum) : getu16(fc, bb, Elf64_Ehdr.e_phnum); int e_phentsize = is32 ? getu16(fc, bb, Elf32_Ehdr.e_phentsize) : getu16(fc, bb, Elf64_Ehdr.e_phentsize); if (e_phnum == PN_XNUM) { // Overflowed into section[0].sh_info long e_shoff = is32 ? getu32(fc, bb, Elf32_Ehdr.e_shoff) : get64(fc, bb, Elf64_Ehdr.e_shoff); long sh_info = is32 ? getu32(fc, bb, e_shoff + Elf32_Shdr.sh_info) : getu32(fc, bb, e_shoff + Elf64_Shdr.sh_info); e_phnum = sh_info; } long dynStart = 0; long phdr = e_phoff; for (long i = 0; i < e_phnum; ++i) { long p_type = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_type) : getu32(fc, bb, phdr + Elf64_Phdr.p_type); if (p_type == PT_DYNAMIC) { long p_offset = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_offset) : get64(fc, bb, phdr + Elf64_Phdr.p_offset); dynStart = p_offset; break; } phdr += e_phentsize; } if (dynStart == 0) { throw new ElfError("ELF file does not contain dynamic linking information"); } // Walk the items in the dynamic section, counting the DT_NEEDED entries. Also remember where // the string table for those entries lives. That table is a pointer, which we translate to an // offset below. long d_tag; int nr_DT_NEEDED = 0; long dyn = dynStart; long ptr_DT_STRTAB = 0; do { d_tag = is32 ? getu32(fc, bb, dyn + Elf32_Dyn.d_tag) : get64(fc, bb, dyn + Elf64_Dyn.d_tag); if (d_tag == DT_NEEDED) { if (nr_DT_NEEDED == Integer.MAX_VALUE) { throw new ElfError("malformed DT_NEEDED section"); } nr_DT_NEEDED += 1; } else if (d_tag == DT_STRTAB) { ptr_DT_STRTAB = is32 ? getu32(fc, bb, dyn + Elf32_Dyn.d_un) : get64(fc, bb, dyn + Elf64_Dyn.d_un); } dyn += is32 ? 8 : 16; } while (d_tag != DT_NULL); if (ptr_DT_STRTAB == 0) { throw new ElfError("Dynamic section string-table not found"); } // Translate the runtime string table pointer we found above to a file offset. long off_DT_STRTAB = 0; phdr = e_phoff; for (int i = 0; i < e_phnum; ++i) { long p_type = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_type) : getu32(fc, bb, phdr + Elf64_Phdr.p_type); if (p_type == PT_LOAD) { long p_vaddr = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_vaddr) : get64(fc, bb, phdr + Elf64_Phdr.p_vaddr); long p_memsz = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_memsz) : get64(fc, bb, phdr + Elf64_Phdr.p_memsz); if (p_vaddr <= ptr_DT_STRTAB && ptr_DT_STRTAB < p_vaddr + p_memsz) { long p_offset = is32 ? getu32(fc, bb, phdr + Elf32_Phdr.p_offset) : get64(fc, bb, phdr + Elf64_Phdr.p_offset); off_DT_STRTAB = p_offset + (ptr_DT_STRTAB - p_vaddr); break; } } phdr += e_phentsize; } if (off_DT_STRTAB == 0) { throw new ElfError("did not find file offset of DT_STRTAB table"); } String[] needed = new String[nr_DT_NEEDED]; nr_DT_NEEDED = 0; dyn = dynStart; do { d_tag = is32 ? getu32(fc, bb, dyn + Elf32_Dyn.d_tag) : get64(fc, bb, dyn + Elf64_Dyn.d_tag); if (d_tag == DT_NEEDED) { long d_val = is32 ? getu32(fc, bb, dyn + Elf32_Dyn.d_un) : get64(fc, bb, dyn + Elf64_Dyn.d_un); needed[nr_DT_NEEDED] = getSz(fc, bb, off_DT_STRTAB + d_val); if (nr_DT_NEEDED == Integer.MAX_VALUE) { throw new ElfError("malformed DT_NEEDED section"); } nr_DT_NEEDED += 1; } dyn += is32 ? 8 : 16; } while (d_tag != DT_NULL); if (nr_DT_NEEDED != needed.length) { throw new ElfError("malformed DT_NEEDED section"); } return needed; } private static String getSz(FileChannel fc, ByteBuffer bb, long offset) throws IOException { StringBuilder sb = new StringBuilder(); short b; while ((b = getu8(fc, bb, offset++)) != 0) { sb.append((char) b); } return sb.toString(); } private static void read(FileChannel fc, ByteBuffer bb, int sz, long offset) throws IOException { bb.position(0); bb.limit(sz); if (fc.read(bb, offset) != sz) { throw new ElfError("ELF file truncated"); } bb.position(0); } private static long get64(FileChannel fc, ByteBuffer bb, long offset) throws IOException { read(fc, bb, 8, offset); return bb.getLong(); } private static long getu32(FileChannel fc, ByteBuffer bb, long offset) throws IOException { read(fc, bb, 4, offset); return bb.getInt() & 0xFFFFFFFFL; // signed -> unsigned } private static int getu16(FileChannel fc, ByteBuffer bb, long offset) throws IOException { read(fc, bb, 2, offset); return bb.getShort() & (int) 0xFFFF; // signed -> unsigned } private static short getu8(FileChannel fc, ByteBuffer bb, long offset) throws IOException { read(fc, bb, 1, offset); return (short) (bb.get() & 0xFF); // signed -> unsigned } private static class ElfError extends RuntimeException { ElfError(String why) { super(why); } } }