package com.googlecode.d2j.ai;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* Minimal, dependency-free ELF reader that extracts the defined exported
* symbols from the dynamic symbol table ({@code .dynsym} / {@code .dynstr}).
*
*
For a JNI {@code .so} the interesting exports are {@code JNI_OnLoad} and the
* {@code Java___} bridge functions — together they reveal which
* native entry points the Java side can reach. This reader parses both ELF32 and
* ELF64, little- and big-endian, via the section-header table.
*
* Format reference: System V ABI, ELF specification.
*/
public final class ElfSymbols {
/** Parsed identity + symbol summary of one ELF object. */
public static final class Info {
public String elfClass = "?"; // ELF32 / ELF64
public String dataOrder = "?"; // LE / BE
public String machine = "?"; // arm / aarch64 / x86 / x86_64 / ...
public boolean isElf;
public final List jniSymbols = new ArrayList<>();
public boolean hasJniOnLoad;
public int dynsymCount;
}
private static final int SHT_DYNSYM = 11;
private ElfSymbols() {
}
public static Info read(byte[] data) {
Info info = new Info();
if (data.length < 64
|| (data[0] & 0xFF) != 0x7F || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') {
return info;
}
info.isElf = true;
boolean is64 = data[4] == 2;
boolean isLE = data[5] == 1;
info.elfClass = is64 ? "ELF64" : "ELF32";
info.dataOrder = isLE ? "LE" : "BE";
ByteBuffer b = ByteBuffer.wrap(data).order(isLE ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
info.machine = machineName(b.getShort(18) & 0xFFFF);
long shoff;
int shentsize;
int shnum;
if (is64) {
shoff = b.getLong(40);
shentsize = b.getShort(58) & 0xFFFF;
shnum = b.getShort(60) & 0xFFFF;
} else {
shoff = b.getInt(32) & 0xFFFFFFFFL;
shentsize = b.getShort(46) & 0xFFFF;
shnum = b.getShort(48) & 0xFFFF;
}
if (shoff == 0 || shnum == 0) {
return info;
}
// Find the .dynsym section and its linked string table.
for (int i = 0; i < shnum; i++) {
long sh = shoff + (long) i * shentsize;
if (sh + shentsize > data.length) {
break;
}
int type = b.getInt((int) (sh + 4));
if (type != SHT_DYNSYM) {
continue;
}
long symOff;
long symSize;
int link;
long entSize;
if (is64) {
symOff = b.getLong((int) (sh + 24));
symSize = b.getLong((int) (sh + 32));
link = b.getInt((int) (sh + 40));
entSize = b.getLong((int) (sh + 56));
} else {
symOff = b.getInt((int) (sh + 16)) & 0xFFFFFFFFL;
symSize = b.getInt((int) (sh + 20)) & 0xFFFFFFFFL;
link = b.getInt((int) (sh + 24));
entSize = b.getInt((int) (sh + 36)) & 0xFFFFFFFFL;
}
if (entSize == 0) {
entSize = is64 ? 24 : 16;
}
// The .dynstr is the section pointed to by sh_link.
long strSh = shoff + (long) link * shentsize;
long strOff;
long strSize;
if (is64) {
strOff = b.getLong((int) (strSh + 24));
strSize = b.getLong((int) (strSh + 32));
} else {
strOff = b.getInt((int) (strSh + 16)) & 0xFFFFFFFFL;
strSize = b.getInt((int) (strSh + 20)) & 0xFFFFFFFFL;
}
int count = (int) (symSize / entSize);
info.dynsymCount = count;
for (int s = 0; s < count; s++) {
long sym = symOff + (long) s * entSize;
if (sym + entSize > data.length) {
break;
}
int nameIdx;
int shndx;
if (is64) {
nameIdx = b.getInt((int) sym);
shndx = b.getShort((int) (sym + 6)) & 0xFFFF;
} else {
nameIdx = b.getInt((int) sym);
shndx = b.getShort((int) (sym + 14)) & 0xFFFF;
}
if (shndx == 0) {
continue; // SHN_UNDEF — imported, not exported
}
String name = readCStr(data, (int) (strOff + nameIdx), (int) strSize - nameIdx);
if (name == null || name.isEmpty()) {
continue;
}
if (name.equals("JNI_OnLoad") || name.equals("JNI_OnUnload")) {
info.hasJniOnLoad = info.hasJniOnLoad || name.equals("JNI_OnLoad");
info.jniSymbols.add(name);
} else if (name.startsWith("Java_")) {
info.jniSymbols.add(name);
}
}
break; // one .dynsym is enough
}
return info;
}
private static String readCStr(byte[] data, int off, int maxLen) {
if (off < 0 || off >= data.length) {
return null;
}
int end = off;
int limit = Math.min(data.length, off + Math.max(0, maxLen));
while (end < limit && data[end] != 0) {
end++;
}
return new String(data, off, end - off);
}
private static String machineName(int em) {
switch (em) {
case 3:
return "x86";
case 40:
return "arm";
case 62:
return "x86_64";
case 183:
return "aarch64";
case 8:
return "mips";
default:
return "machine(" + em + ")";
}
}
}