How to use BridJ in 32 bit mode in 64 bit application?

183 views Asked by At

I'm trying to use BridJ in a way that I'm not sure whether it is supported:

I have a 64 bit application. I get a byte array containing the memory of a 32 bit application through a debugger interface.

I'd like to use BridJ to parse the content of this byte array as C datatypes. I do this by using Pointer.pointerToBytes().as(my_struct.class).get(). This technique works in a 32 bit application. But in a 64 bit application it does not, because BridJ uses the word size of the host application (64 bit) and I want to use the word size of the byte array (32 bit).

Can that be done? Is it possible to somehow switch the word size of BridJ manually, so that BridJ uses 32 bit words, even if it is run in a 64 bit application?

Longer example of what I'm trying to do

byte[] objData = debugger.readMemory(remoteStructAddr, BridJ.sizeOf(c_struct.class));
// pointerToBytes only works as expected when objData has the same
// word size as the host system
c_struct s = Pointer.pointerToBytes(objData).as(c_struct.class).get();
int structMem = s.member();

// offsetOfField only works as expected when objData has the same
// word size as the host system
byte[] namePtr = debugger.readMemory(removePtr + StructObject.offsetOfField(new c_struct(), "name"), 4); 
String name = debugger.readString(namePtr);

Some More Details

  • I use JNAerator to generate the BridJ classes
  • I've tried to use the JNAerator -arch flag, but it doesn't seem to do what I want.
  • I don't use a native library together with BridJ, I just use BridJ to read byte arrays that I get with the debugger API.
1

There are 1 answers

0
Lii On

I solved this problem by writing my own code for reading byte arrays with C structs into Java objects. This code is written for 32 bit input, whatever word size the host application is using.

Example:

some_struct s = Bridj32.readObject(some_struct.class, 
    new byte[] { 0x01, 0x23, 0x45, 0x67 });

The Bridj32 class contains the implementation of this. It takes as input classes that are annotated with BridJ annotations (Field, Struct, Ptr etc) and byte arrays with data. In gives as output parsed Java objects that contain the data of from the input array.

The most tricky part of Bridj32 is that it implements the C struct packing algorithm.

Code of Bridj32:

import static java.util.stream.Collectors.toMap;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import org.bridj.BridJ;
import org.bridj.Pointer;
import org.bridj.SizeT;
import org.bridj.StructIO;
import org.bridj.StructObject;
import org.bridj.ann.Array;
import org.bridj.ann.Bits;
import org.bridj.ann.Field;
import org.bridj.ann.Ptr;
import org.bridj.ann.Struct;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Primitives;

/**
 * Code for using {@link BridJ} in 32 bit mode, even in a 64 bit application.
 * Reads byte arrays into objects, gets offset of fields and gets the size of structs.
 * <p>
 * Assumes little-endian byte order for all input.
 * <p>
 * Input struct  types must be annotated with BridJ annotations, as if they where generated
 * with Jnaerator.
 */
public class Bridj32 {
    private static final Map<Class<?>, StructDescription> structCache = new ConcurrentHashMap<>();

    private static class StructDescription {
        public final int byteSize;
        public final int largestMemberSize;
        public final List<MemberDescription> members;
        @SuppressWarnings("unused")
        public final Class<? extends StructObject> structClass;

        public StructDescription(Class<? extends StructObject> structClass) {
            this.structClass = structClass;

            Map<Integer, Method> memMap = structFieldMethods(structClass)
                .collect(toMap(m -> m.getAnnotation(Field.class).value(), e -> e));

            Struct structAnn = structClass.getAnnotation(Struct.class);
            if (structAnn != null && structAnn.pack() > 1) {
                throw new UnsupportedOperationException("Packed structs are not supported. Struct: " + structClass);
            }

            ImmutableList.Builder<MemberDescription> mems = ImmutableList.builder();

            int offset = 0;
            int maxLargestMem = 0;

            for (Entry<Integer, Method> e : memMap.entrySet()) {
                Method memberMethod = e.getValue();

                int alignSize = alignSize(memberMethod);
                maxLargestMem = Math.max(maxLargestMem, alignSize);
                int size = calcByteSize(memberMethod);
                offset += alignPadSize(offset, alignSize);

                mems.add(new MemberDescription(memberMethod, e.getKey(), offset, size));

                offset += size;
            }

            largestMemberSize = maxLargestMem;

            members = mems.build();
            byteSize = offset;
        }

        public MemberDescription findMember(String memName) {
            for (MemberDescription desc : members) {
                if (desc.method.getName().equals(memName)) {
                    return desc;
                }
            }
            throw new NoSuchElementException(memName);
        }

    }

    static int alignPadSize(int size, int alignment) {
        int p = size % alignment;
        if (p == 0) return 0;
        else return alignment - p;
    }

    private static class MemberDescription {
        Method method;
        int index;
        int byteOffset;
        @SuppressWarnings("unused")
        int byteSize;

        public MemberDescription(Method method, int index, int byteOffset, int byteSize) {
            this.method = method;
            this.index = index;
            this.byteOffset = byteOffset;
            this.byteSize = byteSize;
        }

        public Class<?> getType() {
            return method.getReturnType();
        }
    }


    /**
     * @return The size of the C struct that corresponds to the input class
     *         argument. This size includes trailing padding in the struct.
     */
    public static int paddedSizeOf(Class<?> cls) {
        StructDescription desc = getStructDescription(cls);
        return desc.byteSize + alignPadSize(desc.byteSize, desc.largestMemberSize); 
    }

    private static Stream<Method> structFieldMethods(Class<? extends StructObject> structClass) {
        return Arrays.stream(structClass.getMethods())
            .filter(m -> m.getAnnotation(Field.class) != null)
            .filter(m -> m.getParameterCount() == 0);
    }

    private static StructDescription getStructDescription(Class<?> cls) {
        StructDescription result = structCache.get(cls);
        if (result == null) {
            @SuppressWarnings("unchecked")
            Class<? extends StructObject> structCls = (Class<? extends StructObject>) cls;
            result = new StructDescription(structCls);
            structCache.put(cls, result);
        }
        return result;
    }

    @SuppressWarnings("unused")
    private static int nrFields(Class<? extends StructObject> structClass) {
        int maxFieldNr = -1;
        for (Method f : structClass.getMethods()) {
            Field fieldAnn = f.getAnnotation(Field.class);
            if (fieldAnn != null) {
                maxFieldNr = Math.max(maxFieldNr, fieldAnn.value());
            }
        }
        return maxFieldNr + 1;
    }

    /**
     * @return The size of the C struct that corresponds to the input class
     *         argument. This size does not include trailing padding
     *         in the struct.
     */
    public static int sizeOf(Class<? extends StructObject> structClass) {
        return getStructDescription(structClass).byteSize;
    }

    private static int calcByteSize(Method memMeth) {
        Array arrayAnn = memMeth.getAnnotation(Array.class);
        int mult = arrayAnn == null
            ? 1 : (int) Arrays.stream(arrayAnn.value()).reduce(1, (a, b) -> a * b);

        int sizeSingle = calcByteSizeSingle(memMeth);

        return mult * sizeSingle;
    }

    private static int alignSize(Method memMeth) {
        if (StructObject.class.isAssignableFrom(memMeth.getReturnType())) {
            return getStructDescription(memMeth.getReturnType()).largestMemberSize;
        } else {
            return primitiveByteSize(memMeth);
        }
    }

    private static int primitiveByteSize(Method memMeth) {
        Class<?> cls = Primitives.wrap(memMeth.getReturnType());

        if (memMeth.getAnnotation(Ptr.class) != null) return 4;

        if (memMeth.getAnnotation(Bits.class) != null) {
            throw new UnsupportedOperationException("Bit fields are not supported. Method: " + memMeth);
        }

        if (cls == Boolean.class) return 1;
        if (cls == Byte.class   ) return 1;
        if (cls == Short.class  ) return 2;
        if (cls == Integer.class) return 4;
        if (cls == Long.class   ) return 8;
        if (cls == Float.class  ) return 4;
        if (cls == Double.class ) return 8;

        if (cls == String.class ) return 4;

        if (cls == Pointer.class) return 4;
        if (cls == SizeT.class  ) return 4;

        throw new IllegalArgumentException("Unknown type: " + cls);
    }

    private static int calcByteSizeSingle(Method memMeth) {
        Class<?> cls = memMeth.getReturnType();
        if (StructObject.class.isAssignableFrom(cls)) {
            return paddedSizeOf(cls);
        } else {
            return primitiveByteSize(memMeth);
        }
    }

    /**
     * Reads one object (and all its members) from the bytes array.
     */
    public static <T extends StructObject> T readObject(Class<T> structClass, byte[] bytes) {
        ByteBuffer buff = ByteBuffer.wrap(bytes, 0, bytes.length).order(ByteOrder.LITTLE_ENDIAN);
        return readObject(structClass, buff, 0);
    }

    private static <T extends StructObject> T readObject(Class<T> structClass, ByteBuffer buff, int offset) {
        StructDescription desc = getStructDescription(structClass);
        Preconditions.checkArgument(buff.capacity() - offset >= desc.byteSize);
        StructIO io = StructIO.getInstance(structClass);

        T struct;
        try {
            struct = structClass.getConstructor().newInstance();
        } catch (ReflectiveOperationException exc) {
            throw new RuntimeException(exc);
        }

        for (MemberDescription memDesc : desc.members) {
            setField(memDesc, struct, io, buff, offset);
        }

        return struct;
    }

    public static int offsetOfField(Class<? extends StructObject> structClass, String memName) {
        return getStructDescription(structClass).findMember(memName).byteOffset;
    }

    private static void setField(MemberDescription desc, StructObject struct, StructIO io, ByteBuffer bytes, int structOffset) {
        Class<?> cls = Primitives.wrap(desc.getType());
        int offset = desc.byteOffset + structOffset;
        int ix = desc.index;

             if (cls == Boolean.class) io.setBooleanField(struct, ix, bytes.get(offset) != 0);
        else if (cls == Byte.class   ) io.setByteField   (struct, ix, bytes.get(offset));
        else if (cls == Short.class  ) io.setShortField  (struct, ix, bytes.getShort(offset));
        else if (cls == Integer.class) io.setIntField    (struct, ix, bytes.getInt(offset));
        else if (cls == Long.class   ) io.setLongField   (struct, ix, bytes.getLong(offset));
        else if (cls == Float.class  ) io.setFloatField  (struct, ix, bytes.getFloat(offset));
        else if (cls == Double.class ) io.setDoubleField (struct, ix, bytes.getDouble(offset));

        else if (cls == String.class) throw new UnsupportedOperationException();

        else if (SizeT.class.isAssignableFrom(cls)) {
            io.setSizeTField(struct, offset, Integer.toUnsignedLong(bytes.getInt(offset)));
        } else if (Pointer.class.isAssignableFrom(cls)) {
            @SuppressWarnings("deprecation")
            Pointer<?> p = Pointer.pointerToAddress(Integer.toUnsignedLong(bytes.getInt(offset)));
            io.setPointerField(struct, ix, p);
        } else if (StructObject.class.isAssignableFrom(cls)) {
            @SuppressWarnings("unchecked")
            StructObject o = readObject((Class<? extends StructObject>) cls, bytes, offset);
            io.setNativeObjectField(struct, ix, o);
        }
    }

    /**
     * Reads all objects of type objCls form arrData and returns them. The number of objects
     * depend on the binary size of objCls objects. 
     * <p>
     * If the size of arrData is not evenly devisible by the size of objCls
     *  
     */
    public static <T extends StructObject> List<T> readAllObjects(Class<T> objCls, byte[] arrData) {
        int strideSize = paddedSizeOf(objCls);
        List<T> result = new ArrayList<>();
        ByteBuffer buff = ByteBuffer.wrap(arrData).order(ByteOrder.LITTLE_ENDIAN);

        for (int offset = 0; offset < arrData.length; offset += strideSize) {
            result.add(readObject(objCls, buff, offset));
        }
        return result;
    }
}