AR
object from the contents of the given file.
+ *
+ * @param aFile
+ * The AR archive file to process.
+ * @throws IllegalArgumentException
+ * in case the given file was null
;
+ * @throws IOException
+ * if the given file is not a valid AR archive.
+ */
+ public AR( final File aFile ) throws IOException
+ {
+ if ( aFile == null )
+ {
+ throw new IllegalArgumentException( "Parameter File cannot be null!" );
+ }
+
+ this.path = aFile.getAbsolutePath();
+ this.efile = new RandomAccessFile( aFile, "r" );
+
+ final byte[] hdrBytes = new byte[7];
+ this.efile.readFully( hdrBytes );
+
+ if ( !AREntry.isARHeader( hdrBytes ) )
+ {
+ this.efile.close();
+ this.efile = null;
+
+ throw new IOException( "Invalid AR archive! No header found." );
+ }
+
+ this.efile.readLine();
+ }
+
+ // METHODS
+
+ /**
+ * Disposes all resources for this AR archive.
+ */
+ public void dispose()
+ {
+ try
+ {
+ if ( this.efile != null )
+ {
+ this.efile.close();
+ this.efile = null;
+ }
+ }
+ catch ( final IOException exception )
+ {
+ // Ignored...
+ }
+ }
+
+ /**
+ * Extracts all files from this archive matching the given names.
+ *
+ * @param aOutputDir
+ * the output directory to extract the files to, cannot be
+ * null
;
+ * @param aNames
+ * the names of the files to extract, if omitted all files will be
+ * extracted.
+ * @throws IllegalArgumentException
+ * in case the given output directory was null
;
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ public void extractFiles( final File aOutputDir, final String... aNames ) throws IOException
+ {
+ if ( aOutputDir == null )
+ {
+ throw new IllegalArgumentException( "Parameter OutputDir cannot be null!" );
+ }
+
+ for ( final AREntry header : getEntries() )
+ {
+ String fileName = header.getFileName();
+ if ( ( aNames != null ) && !stringInStrings( aNames, fileName ) )
+ {
+ continue;
+ }
+
+ this.efile.seek( header.getFileOffset() );
+
+ extractFile( header, new File( aOutputDir, fileName ) );
+ }
+ }
+
+ /**
+ * Returns the name of this archive.
+ *
+ * @return an archive name, never null
.
+ */
+ public String getArchiveName()
+ {
+ return this.path;
+ }
+
+ /**
+ * Get an array of all the object file headers for this archive.
+ *
+ * @throws IOException
+ * Unable to process the archive file.
+ * @return An array of headers, one for each object within the archive.
+ * @see AREntry
+ */
+ public Collectionnull
.
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ public Collectionnull
;
+ * @param aName
+ * the name of the file to read, cannot be null
.
+ * @return true
if the file was successfully read,
+ * false
otherwise.
+ * @throws IllegalArgumentException
+ * in case either one of the given arguments was null
;
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ public boolean readFile( final Writer aWriter, final String aName ) throws IOException
+ {
+ if ( aWriter == null )
+ {
+ throw new IllegalArgumentException( "Parameter Writer cannot be null!" );
+ }
+ if ( aName == null )
+ {
+ throw new IllegalArgumentException( "Parameter Name cannot be null!" );
+ }
+
+ for ( final AREntry header : getEntries() )
+ {
+ String name = header.getFileName();
+ if ( aName.equals( name ) )
+ {
+ this.efile.seek( header.getFileOffset() );
+
+ extractFile( header, aWriter );
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Look up the name stored in the archive's string table based on the offset
+ * given. Maintains efile
file location.
+ *
+ * @param aOffset
+ * Offset into the string table for first character of the name.
+ * @throws IOException
+ * offset
not in string table bounds.
+ */
+ final String nameFromStringTable( final long aOffset ) throws IOException
+ {
+ if ( this.stringTableOffset < 0 )
+ {
+ throw new IOException( "Invalid AR archive! No string table read yet?!" );
+ }
+
+ final StringBuilder name = new StringBuilder();
+
+ final long originalPos = this.efile.getFilePointer();
+
+ try
+ {
+ this.efile.seek( this.stringTableOffset + aOffset );
+
+ byte temp;
+ while ( ( temp = this.efile.readByte() ) != '\n' )
+ {
+ name.append( ( char )temp );
+ }
+ }
+ finally
+ {
+ this.efile.seek( originalPos );
+ }
+
+ return name.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void finalize() throws Throwable
+ {
+ try
+ {
+ dispose();
+ }
+ finally
+ {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Extracts the given entry and writes its data to a given file.
+ *
+ * @param aEntry
+ * the entry to extract;
+ * @param aFile
+ * the file to write the entry data to.
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ private void extractFile( final AREntry aEntry, final File aFile ) throws IOException
+ {
+ final FileWriter fw = new FileWriter( aFile );
+
+ try
+ {
+ extractFile( aEntry, fw );
+ }
+ finally
+ {
+ try
+ {
+ fw.close();
+ }
+ catch ( final IOException exception )
+ {
+ // Ignore...
+ }
+ }
+ }
+
+ /**
+ * Extracts the given entry and writes its data to a given file.
+ *
+ * @param aEntry
+ * the entry to extract;
+ * @param aWriter
+ * the {@link Writer} to write the entry data to.
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ private void extractFile( final AREntry aEntry, final Writer aWriter ) throws IOException
+ {
+ try
+ {
+ long bytesToRead = aEntry.getSize();
+
+ while ( bytesToRead > 0 )
+ {
+ final int byteRead = this.efile.read();
+ if ( ( byteRead < 0 ) && ( bytesToRead != 0 ) )
+ {
+ throw new IOException( "Invalid AR archive! Premature end of archive?!" );
+ }
+
+ aWriter.write( byteRead );
+ bytesToRead--;
+ }
+ }
+ finally
+ {
+ try
+ {
+ aWriter.flush();
+ }
+ catch ( final IOException exception )
+ {
+ // Ignore...
+ }
+ }
+ }
+
+ /**
+ * Load the entries from the archive (if required).
+ *
+ * @return the read entries, never null
.
+ */
+ private Collectiontrue
if the given subject was found in the given set,
+ * false
otherwise.
+ */
+ private boolean stringInStrings( final String[] aSet, final String aSubject )
+ {
+ for ( final String element : aSet )
+ {
+ if ( aSubject.equals( element ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/ar/AREntry.java b/app/src/main/java/nl/lxtreme/binutils/ar/AREntry.java
new file mode 100644
index 00000000..454b7c40
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/ar/AREntry.java
@@ -0,0 +1,366 @@
+/*******************************************************************************
+ * Copyright (c) 2011 - J.W. Janssen
+ *
+ * Copyright (c) 2000, 2008 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX Software Systems - Initial API and implementation
+ * Abeer Bagul (Tensilica) - bug 102434
+ * Anton Leherbauer (Wind River Systems)
+ * J.W. Janssen - Cleanup and some small API changes.
+ *******************************************************************************/
+package nl.lxtreme.binutils.ar;
+
+
+import java.io.*;
+
+
+/**
+ * The ARHeader
class is used to store the per-object file
+ * archive headers. It can also create an Elf object for inspecting
+ * the object file data.
+ */
+public class AREntry
+{
+ // CONSTANTS
+
+ private static final int NAME_IDX = 0;
+ private static final int NAME_LEN = 16;
+ private static final int MTIME_IDX = 16;
+ private static final int MTIME_LEN = 12;
+ private static final int UID_IDX = 28;
+ private static final int UID_LEN = 6;
+ private static final int GID_IDX = 34;
+ private static final int GID_LEN = 6;
+ private static final int MODE_IDX = 40;
+ private static final int MODE_LEN = 8;
+ private static final int SIZE_IDX = 48;
+ private static final int SIZE_LEN = 10;
+ private static final int MAGIC_IDX = 58;
+ @SuppressWarnings("unused")
+ private static final int MAGIC_LEN = 2;
+ private static final int HEADER_LEN = 60;
+
+ // VARIABLES
+
+ private String fileName;
+ private long fileOffset;
+ private long modificationTime;
+ private int uid;
+ private int gid;
+ private int mode;
+ private long size;
+
+ // CONSTRUCTORS
+
+ /**
+ * Creates a new archive header object.
+ */
+ private AREntry()
+ {
+ super();
+ }
+
+ // METHODS
+
+ /**
+ * Factory method to create a {@link AREntry} for the given {@link AR}
+ * archive.
+ *
+ * @param aArchive
+ * the archive to read the header for;
+ * @param aFile
+ * the file of the archive to read the header from.
+ * @return the newly read header, never null
.
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ static AREntry create(final AR aArchive, final RandomAccessFile aFile) throws IOException
+ {
+ final AREntry result = new AREntry();
+
+ byte[] buf = new byte[HEADER_LEN];
+
+ // Read in the archive header data. Fixed sizes.
+ aFile.readFully(buf);
+
+ // Save this location so we can create the Elf object later.
+ result.fileOffset = aFile.getFilePointer();
+
+ // Convert the raw bytes into strings and numbers.
+ result.fileName = new String(buf, NAME_IDX, NAME_LEN).trim();
+ result.size = Long.parseLong(new String(buf, SIZE_IDX, SIZE_LEN).trim());
+
+ if (!result.isSpecial())
+ {
+ result.modificationTime = Long.parseLong(new String(buf, MTIME_IDX, MTIME_LEN).trim());
+ result.uid = Integer.parseInt(new String(buf, UID_IDX, UID_LEN).trim());
+ result.gid = Integer.parseInt(new String(buf, GID_IDX, GID_LEN).trim());
+ result.mode = Integer.parseInt(new String(buf, MODE_IDX, MODE_LEN).trim(), 8);
+
+ if ((buf[MAGIC_IDX] != 0x60) && (buf[MAGIC_IDX + 1] != 0x0A))
+ {
+ throw new IOException("Not a valid AR archive! No file header magic found.");
+ }
+ }
+
+ // If the name is something like "#1/true
if the given byte array contains the AR header,
+ * false
otherwise.
+ */
+ static boolean isARHeader(final byte[] aIdent)
+ {
+ if ((aIdent.length < 7)
+ || (aIdent[0] != '!')
+ || (aIdent[1] != '<')
+ || (aIdent[2] != 'a')
+ || (aIdent[3] != 'r')
+ || (aIdent[4] != 'c')
+ || (aIdent[5] != 'h')
+ || (aIdent[6] != '>'))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object aObject)
+ {
+ if (this == aObject)
+ {
+ return true;
+ }
+ if ((aObject == null) || (getClass() != aObject.getClass()))
+ {
+ return false;
+ }
+
+ final AREntry other = (AREntry) aObject;
+ if (this.fileName == null)
+ {
+ if (other.fileName != null)
+ {
+ return false;
+ }
+ }
+ else if (!this.fileName.equals(other.fileName))
+ {
+ return false;
+ }
+ if (this.fileOffset != other.fileOffset)
+ {
+ return false;
+ }
+ if (this.gid != other.gid)
+ {
+ return false;
+ }
+ if (this.mode != other.mode)
+ {
+ return false;
+ }
+ if (this.modificationTime != other.modificationTime)
+ {
+ return false;
+ }
+ if (this.size != other.size)
+ {
+ return false;
+ }
+ if (this.uid != other.uid)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns UNIX file mode, containing the permissions of the file.
+ *
+ * @return the mode, should be interpreted as octal value.
+ */
+ public int getFileMode()
+ {
+ return this.mode;
+ }
+
+ /**
+ * Get the name of the object file
+ */
+ public String getFileName()
+ {
+ return this.fileName;
+ }
+
+ /**
+ * Returns the group ID of the file.
+ *
+ * @return the group ID, as integer value, >= 0.
+ */
+ public int getGID()
+ {
+ return this.gid;
+ }
+
+ /**
+ * Returns the timestamp of the file.
+ *
+ * @return the timestamp, as epoch time.
+ */
+ public long getModificationTime()
+ {
+ return this.modificationTime;
+ }
+
+ /**
+ * Get the size of the object file .
+ */
+ public long getSize()
+ {
+ return this.size;
+ }
+
+ /**
+ * Returns the user ID of the file.
+ *
+ * @return the user ID, as integer value, >= 0.
+ */
+ public int getUID()
+ {
+ return this.uid;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (this.modificationTime ^ (this.modificationTime >>> 32));
+ result = prime * result + ((this.fileName == null) ? 0 : this.fileName.hashCode());
+ result = prime * result + (int) (this.fileOffset ^ (this.fileOffset >>> 32));
+ result = prime * result + (int) (this.size ^ (this.size >>> 32));
+ result = prime * result + this.gid;
+ result = prime * result + this.mode;
+ result = prime * result + this.uid;
+ return result;
+ }
+
+ /**
+ * Returns whether this header represents a special file.
+ *
+ * @return true
if this header represents a special file,
+ * false
otherwise.
+ */
+ public boolean isSpecial()
+ {
+ return this.fileName.charAt(0) == '/';
+ }
+
+ /**
+ * Returns whether this header represents the string table section.
+ *
+ * @return true
if this header represents a string table section,
+ * false
otherwise.
+ */
+ public boolean isStringTableSection()
+ {
+ return this.fileName.equals("//");
+ }
+
+ /**
+ * Returns the file offset in the complete binary.
+ *
+ * @return a file offset (in bytes), >= 0.
+ */
+ final long getFileOffset()
+ {
+ return this.fileOffset;
+ }
+
+ /**
+ * Returns whether this header is created by BSD ar, and represents an
+ * extended filename.
+ *
+ * @return true
if this header is an extended filename created by
+ * BSD ar, false
otherwise.
+ */
+ final boolean isBSDArExtendedFileName()
+ {
+ return this.fileName.matches("^#1/\\d+$");
+ }
+
+ /**
+ * Returns whether this header is created by GNU ar, and represents an
+ * extended filename.
+ *
+ * @return true
if this header is an extended filename created by
+ * GNU ar, false
otherwise.
+ */
+ final boolean isGNUArExtendedFileName()
+ {
+ return this.fileName.matches("^/\\d+$");
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/Coff.java b/app/src/main/java/nl/lxtreme/binutils/coff/Coff.java
new file mode 100644
index 00000000..87a61ed5
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/Coff.java
@@ -0,0 +1,239 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+//import java.nio.file.*;
+
+
+/**
+ * Represents a COFF file.
+ * + * This class is not thread-safe! + *
+ */ +public class Coff implements Closeable +{ + public final FileHeader fileHeader; + public final OptionalHeader optHeader; + public final SectionHeader[] sectionHeaders; + public final Symbol[] symbols; + + private FileChannel channel; + + public Coff( File file ) throws IOException + { + this((new RandomAccessFile(file.getAbsolutePath(), "r").getChannel()));/* FileChannel.open( file.toPath(), StandardOpenOption.READ */ + } + + public Coff( FileChannel channel ) throws IOException + { + this.channel = channel; + this.fileHeader = new FileHeader( channel ); + + ByteBuffer buf = ByteBuffer.allocate( Math.max( 40, fileHeader.optionalHeaderSize ) ); + buf.limit( fileHeader.optionalHeaderSize ); + buf.order( fileHeader.getByteOrder() ); + readFully( channel, buf, "Unable to read optional header!" ); + + this.optHeader = new OptionalHeader( buf ); + + buf.clear(); + buf.limit( 40 ); + + this.sectionHeaders = new SectionHeader[fileHeader.sectionCount]; + for ( int i = 0; i < sectionHeaders.length; i++ ) + { + readFully( channel, buf, "Unable to read section header #" + ( i + 1 ) ); + + sectionHeaders[i] = new SectionHeader( buf ); + } + + byte[] stringTable = new byte[0]; + + long stringPos = fileHeader.symbolFilePtr + ( fileHeader.symbolCount * 18 ); + if ( stringPos > 0 && stringPos < channel.size() ) + { + channel.position( stringPos ); + + buf.clear(); + buf.limit( 4 ); + readFully( channel, buf, "Unable to read string table size!" ); + + int size = buf.getInt(); + + ByteBuffer stringBuf = ByteBuffer.allocate( size ); + readFully( channel, stringBuf, "Unable to read string table!" ); + + stringTable = stringBuf.array(); + } + + buf.clear(); + buf.limit( 18 ); + + this.symbols = new Symbol[fileHeader.symbolCount]; + for ( int i = 0; i < symbols.length; i++ ) + { + readFully( channel, buf, "Unable to read symbol #" + ( i + 1 ) ); + + symbols[i] = new Symbol( buf, stringTable ); + } + } + + static void readFully( ReadableByteChannel ch, ByteBuffer buf, String errMsg ) throws IOException + { + buf.rewind(); + int read = ch.read( buf ); + if ( read != buf.limit() ) + { + throw new IOException( errMsg + " Read only " + read + " of " + buf.limit() + " bytes!" ); + } + buf.flip(); + } + + static String getZString( byte[] buf, int offset ) + { + int end = offset; + while ( end < buf.length && buf[end] != 0 ) + { + end++; + } + return new String( buf, offset, ( end - offset ) ); + } + + @Override + public void close() throws IOException + { + if ( channel != null ) + { + channel.close(); + channel = null; + } + } + + /** + * Returns the line number information for the given section. The section + * should represent a ".text" or other code section. + * + * @return an array of line number information, or an empty array if no such + * information is present. + */ + public LineNumber[] getLineNumbers( SectionHeader shdr ) throws IOException + { + if ( shdr == null ) + { + throw new IllegalArgumentException( "Header cannot be null!" ); + } + if ( channel == null ) + { + throw new IOException( "ELF file is already closed!" ); + } + if ( shdr.lineNumberOffset == 0 || shdr.lineNumberSize == 0 ) + { + // Nothing to do... + return new LineNumber[0]; + } + + ByteBuffer buf = ByteBuffer.allocate( 10 ); + buf.order( fileHeader.getByteOrder() ); + + channel.position( shdr.lineNumberOffset ); + + LineNumber[] result = new LineNumber[shdr.lineNumberSize]; + for ( int i = 0; i < result.length; i++ ) + { + readFully( channel, buf, "Unable to read line number information!" ); + + result[i] = new LineNumber( buf ); + } + + return result; + } + + /** + * Returns the relocation information for the given section. The section + * should represent a ".text" or other code section. + * + * @return an array of relocation information, or an empty array if no such + * information is present. + */ + public RelocationInfo[] getRelocationInfo( SectionHeader shdr ) throws IOException + { + if ( shdr == null ) + { + throw new IllegalArgumentException( "Header cannot be null!" ); + } + if ( channel == null ) + { + throw new IOException( "ELF file is already closed!" ); + } + if ( shdr.relocTableOffset == 0 || shdr.relocTableSize == 0 ) + { + // Nothing to do... + return new RelocationInfo[0]; + } + + ByteBuffer buf = ByteBuffer.allocate( 10 ); + buf.order( fileHeader.getByteOrder() ); + + channel.position( shdr.relocTableOffset ); + + RelocationInfo[] result = new RelocationInfo[shdr.relocTableSize]; + for ( int i = 0; i < result.length; i++ ) + { + readFully( channel, buf, "Unable to read relocation information!" ); + + result[i] = new RelocationInfo( buf ); + } + + return result; + } + + /** + * Returns the actual section data (= executable code + initialized data) for + * the given section header. + * + * @return a byte buffer from which the data can be read, never + *null
.
+ */
+ public ByteBuffer getSectionData( SectionHeader shdr ) throws IOException
+ {
+ if ( shdr == null )
+ {
+ throw new IllegalArgumentException( "Header cannot be null!" );
+ }
+ if ( channel == null )
+ {
+ throw new IOException( "ELF file is already closed!" );
+ }
+
+ ByteBuffer buf = ByteBuffer.allocate( ( int )shdr.size );
+ buf.order( fileHeader.getByteOrder() );
+
+ channel.position( shdr.dataOffset );
+ readFully( channel, buf, "Unable to read section completely!" );
+
+ return buf;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder( "COFF " );
+ sb.append( fileHeader ).append( "; " );
+ sb.append( optHeader ).append( "\n" );
+ for ( int i = 0; i < sectionHeaders.length; i++ )
+ {
+ sb.append( "\t" ).append( sectionHeaders[i] ).append( "\n" );
+ }
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/CoffMagic.java b/app/src/main/java/nl/lxtreme/binutils/coff/CoffMagic.java
new file mode 100644
index 00000000..1917115a
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/CoffMagic.java
@@ -0,0 +1,49 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+/**
+ * Identifies the state of the image file.
+ */
+public enum CoffMagic
+{
+ /** */
+ STMAGIC( 0401 ),
+ /** */
+ OMAGIC( 0404 ),
+ /** */
+ JMAGIC( 0407 ),
+ /** */
+ DMAGIC( 0410 ),
+ /** Also PE32 */
+ ZMAGIC( 0413 ),
+ /** */
+ SHMAGIC( 0443 ),
+ /** PE32+ */
+ PE32_PLUS( 01013 );
+
+ private final int no;
+
+ private CoffMagic( int no )
+ {
+ this.no = no;
+ }
+
+ public static CoffMagic valueOf( int no )
+ {
+ for ( CoffMagic entry : values() )
+ {
+ if ( entry.no == no )
+ {
+ return entry;
+ }
+ }
+ throw new IllegalArgumentException( "Invalid CoffMagic: 0" + Integer.toOctalString( no ) );
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/FileHeader.java b/app/src/main/java/nl/lxtreme/binutils/coff/FileHeader.java
new file mode 100644
index 00000000..a6c0fe6b
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/FileHeader.java
@@ -0,0 +1,120 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import static nl.lxtreme.binutils.coff.Coff.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+
+
+/**
+ * Represents a COFF file header.
+ */
+public class FileHeader
+{
+ // relocation info stripped from file
+ public static final int F_RELFLG = 0x0001;
+ // file is executable (no unresolved external references)
+ public static final int F_EXEC = 0x0002;
+ // line numbers stripped from file
+ public static final int F_LNNO = 0x0004;
+ // local symbols stripped from file
+ public static final int F_LSYMS = 0x0008;
+ // Aggressively trim working set
+ public static final int F_AGGRESSIVE_WS_TRIM = 0x0010;
+ // App can handle >2GB addresses.
+ public static final int F_LARGE_ADDRESS_AWARE = 0x0020;
+ // Use of this flag is reserved for future use.
+ public static final int F_FILE_16BIT_MACHINE = 0x0040;
+ // file is 16-bit little-endian
+ public static final int F_AR16WR = 0x0080;
+ // file is 32-bit little-endian
+ public static final int F_AR32WR = 0x0100;
+ // file is 32-bit big-endian or debug information stripped.
+ public static final int F_AR32W = 0x0200;
+ // If image is on removable media, copy and run from swap file.
+ public static final int F_REMOVABLE_RUN_FROM_SWAP = 0x0400;
+ // rs/6000 aix: dynamically loadable w/imports & exports
+ public static final int F_DYNLOAD = 0x1000;
+ // rs/6000 aix: file is a shared object or PE format DLL.
+ public static final int F_SHROBJ = 0x2000;
+ // File should be run only on a UP machine.
+ public static final int F_UP_SYSTEM_ONLY = 0x4000;
+ // Big endian: MSB precedes LSB in memory.
+ public static final int F_AR32BE = 0x8000;
+
+ public final MachineType machineType;
+ public final int sectionCount;
+ public final long timestamp;
+ public final long symbolFilePtr;
+ public final int symbolCount;
+ public final int optionalHeaderSize;
+ public final int flags;
+
+ public FileHeader( FileChannel channel ) throws IOException
+ {
+ final ByteBuffer buf = ByteBuffer.allocate( 20 );
+
+ buf.order( ByteOrder.LITTLE_ENDIAN );
+ readFully( channel, buf, "Unable to read file header!" );
+
+ machineType = MachineType.valueOf( buf.getShort() );
+ sectionCount = buf.getShort();
+ timestamp = buf.getInt();
+ symbolFilePtr = buf.getInt();
+ symbolCount = buf.getInt();
+ optionalHeaderSize = buf.getShort();
+ flags = buf.getShort();
+ }
+
+ /**
+ * @return true
if the COFF file is stripped and has no symbols,
+ * false
otherwise.
+ */
+ public boolean isStripped()
+ {
+ return symbolFilePtr == 0 && symbolCount == 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( machineType );
+ if ( timestamp != 0 )
+ {
+ sb.append( "created at " ).append( timestamp );
+ }
+ if ( isStripped() )
+ {
+ sb.append( " (stripped)" );
+ }
+ else
+ {
+ sb.append( " (" ).append( symbolCount ).append( " symbols)" );
+ }
+ if ( flags != 0 )
+ {
+ sb.append( " flags = 0x" ).append( Integer.toHexString( flags ) );
+ }
+ return sb.toString();
+ }
+
+ public ByteOrder getByteOrder()
+ {
+ ByteOrder result = ByteOrder.LITTLE_ENDIAN;
+ if ( ( flags & F_AR32BE ) != 0 || ( flags & F_AR32W ) != 0 )
+ {
+ result = ByteOrder.BIG_ENDIAN;
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/LineNumber.java b/app/src/main/java/nl/lxtreme/binutils/coff/LineNumber.java
new file mode 100644
index 00000000..1bc3cea6
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/LineNumber.java
@@ -0,0 +1,35 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import java.io.*;
+import java.nio.*;
+
+
+public class LineNumber
+{
+ public final int lineNumber;
+ public final int type;
+
+ public LineNumber( ByteBuffer buf ) throws IOException
+ {
+ type = buf.getInt();
+ lineNumber = buf.getShort();
+ }
+
+ public boolean isSymbolIndex()
+ {
+ return lineNumber == 0;
+ }
+
+ public boolean isVirtualAddress()
+ {
+ return lineNumber != 0;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/MachineType.java b/app/src/main/java/nl/lxtreme/binutils/coff/MachineType.java
new file mode 100644
index 00000000..df632562
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/MachineType.java
@@ -0,0 +1,74 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+public enum MachineType
+{
+ /** */
+ UNKNOWN( 0, "Unknown" ),
+ /** */
+ ALPHA( 0x184, "Alpha AXP" ),
+ /** */
+ ARM( 0x1c0, "ARM" ),
+ /** */
+ ALPHA64( 0x284, "Alpha AXP-64" ),
+ /** */
+ I386( 0x14c, "i386 compatible" ),
+ /** */
+ IA64( 0x200, "Intel IA64" ),
+ /** */
+ M68K( 0x268, "m68k" ),
+ /** */
+ MIPS16( 0x266, "MIPS-16" ),
+ /** */
+ MIPS_FPU( 0x366, "MIPS with FPU" ),
+ /** */
+ MIPS_FPU16( 0x466, "MIPS-16 with FPU" ),
+ /** */
+ POWERPC( 0x1f0, "PowerPC little-endian" ),
+ /** */
+ R3000( 0x162, "MIPS R3000 little-endian" ),
+ /** */
+ R4000( 0x166, "MIPS R4000 little-endian" ),
+ /** */
+ R10000( 0x168, "MIPS R10000 little-endian" ),
+ /** */
+ SH3( 0x1a2, "Hitachi SH3" ),
+ /** */
+ SH4( 0x1a6, "Hitachi SH4" ),
+ /** */
+ THUMB( 0x1c2, "ARM thumb" );
+
+ private final int no;
+ private final String desc;
+
+ private MachineType( int no, String desc )
+ {
+ this.no = no;
+ this.desc = desc;
+ }
+
+ static MachineType valueOf( int value )
+ {
+ for ( MachineType mt : values() )
+ {
+ if ( mt.no == value )
+ {
+ return mt;
+ }
+ }
+ throw new IllegalArgumentException( "Invalid machine type: " + value );
+ }
+
+ @Override
+ public String toString()
+ {
+ return desc;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/OptionalHeader.java b/app/src/main/java/nl/lxtreme/binutils/coff/OptionalHeader.java
new file mode 100644
index 00000000..d66d67ce
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/OptionalHeader.java
@@ -0,0 +1,60 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import java.io.*;
+import java.nio.*;
+
+
+/**
+ * Represents the a.out/optional header in a COFF file.
+ */
+public class OptionalHeader
+{
+ public final CoffMagic magic;
+ public final int versionStamp;
+ public final int textSize;
+ public final int initDataSize;
+ public final int uninitDataSize;
+ public final int entryPoint;
+ public final int textStart;
+ public final int dataStart;
+
+ public OptionalHeader( ByteBuffer buf ) throws IOException
+ {
+ magic = CoffMagic.valueOf( buf.getShort() );
+ versionStamp = buf.getShort();
+ textSize = buf.getInt();
+ initDataSize = buf.getInt();
+ uninitDataSize = buf.getInt();
+ entryPoint = buf.getInt();
+ textStart = buf.getInt();
+ if ( buf.hasRemaining() )
+ {
+ dataStart = buf.getInt();
+ }
+ else
+ {
+ dataStart = -1;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( magic );
+ sb.append( ", entry point: 0x" ).append( Integer.toHexString( entryPoint ) );
+ sb.append( ", code start: 0x" ).append( Integer.toHexString( textStart ) );
+ sb.append( ", code size: " ).append( textSize );
+ sb.append( ", data start: 0x" ).append( Integer.toHexString( dataStart ) );
+ sb.append( ", data size: " ).append( initDataSize ).append( "+" ).append( uninitDataSize );
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/RelocationInfo.java b/app/src/main/java/nl/lxtreme/binutils/coff/RelocationInfo.java
new file mode 100644
index 00000000..d614cf4d
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/RelocationInfo.java
@@ -0,0 +1,37 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import java.io.*;
+import java.nio.*;
+
+
+public class RelocationInfo
+{
+ public final int virtualAddress;
+ public final int symbolIndex;
+ public final int type;
+
+ public RelocationInfo( ByteBuffer buf ) throws IOException
+ {
+ virtualAddress = buf.getInt();
+ symbolIndex = buf.getInt();
+ type = buf.getShort();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( "virtualAddress = 0x" ).append( Integer.toHexString( virtualAddress ) );
+ sb.append( ", symbolIndex = " ).append( symbolIndex );
+ sb.append( ", type = " ).append( type );
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/SectionHeader.java b/app/src/main/java/nl/lxtreme/binutils/coff/SectionHeader.java
new file mode 100644
index 00000000..9f0803b6
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/SectionHeader.java
@@ -0,0 +1,77 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import java.io.*;
+import java.nio.*;
+
+
+public class SectionHeader
+{
+ public static final int FLAG_REG = 0x00; // regular segment
+ public static final int FLAG_DSECT = 0x01; // dummy segment
+ public static final int FLAG_NOLOAD = 0x02; // no-load segment
+ public static final int FLAG_GROUP = 0x04; // group segment
+ public static final int FLAG_PAD = 0x08; // .pad segment
+ public static final int FLAG_COPY = 0x10; // copy section
+ public static final int FLAG_TEXT = 0x20; // .text segment (= executable code)
+ public static final int FLAG_DATA = 0x40; // .data segment (= initialized
+ // data)
+ public static final int FLAG_BSS = 0x80; // .bss segment (= uninitialized
+ // data)
+ public static final int FLAG_INFO = 0x200; // .comment section
+ public static final int FLAG_OVER = 0x400; // overlay section
+ public static final int FLAG_LIB = 0x800; // library section
+
+ public final SectionType type;
+ public final int physicalAddress;
+ public final int virtualAddress;
+ public final int size;
+ public final int dataOffset;
+ public final int relocTableOffset;
+ public final int relocTableSize;
+ public final int lineNumberOffset;
+ public final int lineNumberSize;
+ public final int flags;
+
+ public SectionHeader( ByteBuffer buf ) throws IOException
+ {
+ byte[] nameBytes = new byte[8];
+ buf.get( nameBytes );
+
+ type = SectionType.valueOf( nameBytes );
+ physicalAddress = buf.getInt();
+ virtualAddress = buf.getInt();
+ size = buf.getInt();
+ dataOffset = buf.getInt();
+ relocTableOffset = buf.getInt();
+ lineNumberOffset = buf.getInt();
+ relocTableSize = buf.getShort();
+ lineNumberSize = buf.getShort();
+ flags = buf.getInt();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( type );
+ sb.append( ", size = " ).append( size );
+ if ( physicalAddress != virtualAddress )
+ {
+ sb.append( ", address (p/v) = 0x" ).append( Integer.toHexString( physicalAddress ) ).append( "/0x" ).append( Integer.toHexString( virtualAddress ) );
+ }
+ else
+ {
+ sb.append( ", address = 0x" ).append( Integer.toHexString( physicalAddress ) );
+ }
+ sb.append( ", flags = " ).append( flags );
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/SectionType.java b/app/src/main/java/nl/lxtreme/binutils/coff/SectionType.java
new file mode 100644
index 00000000..cecb6737
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/SectionType.java
@@ -0,0 +1,53 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+/**
+ * Represents the name/type of a section in a COFF file.
+ */
+public class SectionType
+{
+ public final static SectionType TEXT = new SectionType( ".text" );
+ public final static SectionType INIT = new SectionType( ".init" );
+ public final static SectionType FINI = new SectionType( ".fini" );
+ public final static SectionType RCONST = new SectionType( ".rconst" );
+ public final static SectionType RDATA = new SectionType( ".rdata" );
+ public final static SectionType DATA = new SectionType( ".data" );
+ public final static SectionType BSS = new SectionType( ".bss" );
+ public final static SectionType COMMENT = new SectionType( ".comment" );
+ public final static SectionType LIB = new SectionType( ".lib" );
+
+ private static final SectionType[] VALUES = { TEXT, INIT, FINI, RCONST, RDATA, DATA, BSS, COMMENT, LIB };
+
+ private final String name;
+
+ public SectionType( String name )
+ {
+ this.name = name;
+ }
+
+ public static SectionType valueOf( byte[] name )
+ {
+ String _name = new String( name );
+ for ( SectionType value : VALUES )
+ {
+ if ( _name.equals( value.name ) )
+ {
+ return value;
+ }
+ }
+ return new SectionType( _name );
+ }
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/coff/Symbol.java b/app/src/main/java/nl/lxtreme/binutils/coff/Symbol.java
new file mode 100644
index 00000000..55ecb77e
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/coff/Symbol.java
@@ -0,0 +1,83 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.coff;
+
+
+import static nl.lxtreme.binutils.coff.Coff.*;
+import java.io.*;
+import java.nio.*;
+
+
+public class Symbol
+{
+ public static final int DEBUG = 2;
+ public static final int ABSOLUTE = 1;
+ public static final int NONE = 0;
+
+ public final String name;
+ public final int value;
+ public final int sectionNumber;
+ public final int type;
+ public final int storageClass;
+ public final int auxCount;
+
+ public Symbol( ByteBuffer buf, byte[] stringTable ) throws IOException
+ {
+ byte[] nameBuf = new byte[8];
+ buf.get( nameBuf );
+
+ if ( nameBuf[0] == 0 && nameBuf[1] == 0 && nameBuf[2] == 0 && nameBuf[3] == 0 )
+ {
+ // Long name (> 8 characters)...
+ int offset = ( ( ( nameBuf[7] & 0xff ) << 24 ) | ( ( nameBuf[6] & 0xff ) << 16 ) | ( ( nameBuf[5] & 0xff ) << 8 )
+ | ( nameBuf[4] & 0xff ) ) - 4;
+ if ( offset > 0 && offset < stringTable.length )
+ {
+ name = getZString( stringTable, offset );
+ }
+ else
+ {
+ name = "";
+ }
+ }
+ else
+ {
+ name = new String( nameBuf );
+ }
+
+ value = buf.getInt();
+ sectionNumber = buf.getShort();
+ type = buf.getShort();
+ storageClass = buf.get();
+ auxCount = buf.getInt();
+ }
+
+ public boolean isAbsoluteSymbol()
+ {
+ return sectionNumber == ABSOLUTE;
+ }
+
+ public boolean isDebugSymbol()
+ {
+ return sectionNumber == DEBUG;
+ }
+
+ public boolean isExternal()
+ {
+ return sectionNumber == NONE;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( name );
+ sb.append( ", value = 0x" ).append( Integer.toHexString( value ) );
+ return sb.toString();
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/elf/AbiType.java b/app/src/main/java/nl/lxtreme/binutils/elf/AbiType.java
new file mode 100644
index 00000000..263019e4
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/elf/AbiType.java
@@ -0,0 +1,50 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.elf;
+
+/**
+ * Represent the various types of ABIs that exist (extracted from "elf.h" file from libc6-dev package).
+ */
+public enum AbiType {
+ SYSV(0, "UNIX System V ABI"),
+ HPUX(1, "HP-UX"),
+ NETBSD(2, "NetBSD."),
+ GNU(3, "Object uses GNU ELF extensions."),
+ SOLARIS(6, "Sun Solaris."),
+ AIX(7, "IBM AIX."),
+ IRIX(8, "SGI Irix."),
+ FREEBSD(9, "FreeBSD."),
+ TRU64(10, "Compaq TRU64 UNIX."),
+ MODESTO(11, "Novell Modesto."),
+ OPENBSD(12, "OpenBSD."),
+ ARM_AEABI(64, "ARM EABI"),
+ ARM(97, "ARM"),
+ STANDALONE(255, "Standalone (embedded) application");
+
+ private final int no;
+ private final String desc;
+
+ private AbiType(int no, String desc) {
+ this.no = no;
+ this.desc = desc;
+ }
+
+ static AbiType valueOf(int value) {
+ for (AbiType at : values()) {
+ if (at.no == value) {
+ return at;
+ }
+ }
+ throw new IllegalArgumentException("Invalid ABI type: " + value);
+ }
+
+ @Override
+ public String toString() {
+ return desc;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nl/lxtreme/binutils/elf/DynamicEntry.java b/app/src/main/java/nl/lxtreme/binutils/elf/DynamicEntry.java
new file mode 100644
index 00000000..c2d2f476
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/elf/DynamicEntry.java
@@ -0,0 +1,203 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.elf;
+
+/**
+ * Represents an entry in the dynamic table.
+ */
+public class DynamicEntry {
+
+ public static class Tag {
+ public static final Tag NULL = new Tag(0, "Marks end of dynamic section");
+ public static final Tag NEEDED = new Tag(1, "Name of needed library", true /* strTableOffset */);
+ public static final Tag PLTRELSZ = new Tag(2, "Size in bytes of PLT relocs");
+ public static final Tag PLTGOT = new Tag(3, "Processor defined value");
+ public static final Tag HASH = new Tag(4, "Address of symbol hash table");
+ public static final Tag STRTAB = new Tag(5, "Address of string table");
+ public static final Tag SYMTAB = new Tag(6, "Address of symbol table");
+ public static final Tag RELA = new Tag(7, "Address of Rela relocs");
+ public static final Tag RELASZ = new Tag(8, "Total size of Rela relocs");
+ public static final Tag RELAENT = new Tag(9, "Size of one Rela reloc");
+ public static final Tag STRSZ = new Tag(10, "Size of string table");
+ public static final Tag SYMENT = new Tag(11, "Size of one symbol table entry");
+ public static final Tag INIT = new Tag(12, "Address of init function");
+ public static final Tag FINI = new Tag(13, "Address of termination function");
+ public static final Tag SONAME = new Tag(14, "Name of shared object", true /* strTableOffset */);
+ public static final Tag RPATH = new Tag(15, "Library search path (deprecated)", true /* strTableOffset */);
+ public static final Tag SYMBOLIC = new Tag(16, "Start symbol search here");
+ public static final Tag REL = new Tag(17, "Address of Rel relocs");
+ public static final Tag RELSZ = new Tag(18, "Total size of Rel relocs");
+ public static final Tag RELENT = new Tag(19, "Size of one Rel reloc");
+ public static final Tag PLTREL = new Tag(20, "Type of reloc in PLT");
+ public static final Tag DEBUG = new Tag(21, "For debugging; unspecified");
+ public static final Tag TEXTREL = new Tag(22, "Reloc might modify .text");
+ public static final Tag JMPREL = new Tag(23, "Address of PLT relocs");
+ public static final Tag BIND_NOW = new Tag(24, "Process relocations of object");
+ public static final Tag INIT_ARRAY = new Tag(25, "Array with addresses of init fct");
+ public static final Tag FINI_ARRAY = new Tag(26, "Array with addresses of fini fct");
+ public static final Tag INIT_ARRAYSZ = new Tag(27, "Size in bytes of DT_INIT_ARRAY");
+ public static final Tag FINI_ARRAYSZ = new Tag(28, "Size in bytes of DT_FINI_ARRAY");
+ public static final Tag RUNPATH = new Tag(29, "Library search path");
+ public static final Tag FLAGS = new Tag(30, "Flags for the object being loaded");
+ public static final Tag ENCODING = new Tag(32, "Start of encoded range");
+ public static final Tag PREINIT_ARRAY = new Tag(32, "Array with addresses of preinit fct");
+ public static final Tag PREINIT_ARRAYSZ = new Tag(33, "size in bytes of DT_PREINIT_ARRAY");
+
+ public static final Tag GNU_PRELINKED = new Tag(0x6ffffdf5, "Prelinking timestamp");
+ public static final Tag GNU_CONFLICTSZ = new Tag(0x6ffffdf6, "Size of conflict section");
+ public static final Tag GNU_LIBLISTSZ = new Tag(0x6ffffdf7, "Size of library list");
+ public static final Tag CHECKSUM = new Tag(0x6ffffdf8, "CHECKSUM");
+ public static final Tag PLTPADSZ = new Tag(0x6ffffdf9, "DT_PLTPADSZ");
+ public static final Tag MOVEENT = new Tag(0x6ffffdfa, "DT_MOVEENT");
+ public static final Tag MOVESZ = new Tag(0x6ffffdfb, "DT_MOVESZ");
+ public static final Tag FEATURE_1 = new Tag(0x6ffffdfc, "DT_FEATURE_1");
+ public static final Tag POSFLAG_1 = new Tag(0x6ffffdfd, "DT_POSFLAG_1");
+ public static final Tag SYMINSZ = new Tag(0x6ffffdfe, "DT_SYMINSZ");
+
+ public static final Tag GNU_HASH = new Tag(0x6ffffef5, "GNU-style hash table");
+ public static final Tag TLSDESC_PLT = new Tag(0x6ffffef6, "DT_TLSDESC_PLT");
+ public static final Tag TLSDESC_GOT = new Tag(0x6ffffef7, "DT_TLSDESC_GOT");
+ public static final Tag GNU_CONFLICT = new Tag(0x6ffffef8, "Start of conflict section");
+ public static final Tag GNU_LIBLIST = new Tag(0x6ffffef9, "Library list");
+ public static final Tag CONFIG = new Tag(0x6ffffefa, "Configuration information");
+ public static final Tag DEPAUDIT = new Tag(0x6ffffefb, "Dependency auditing");
+ public static final Tag AUDIT = new Tag(0x6ffffefc, "Object auditing.");
+ public static final Tag PLTPAD = new Tag(0x6ffffefd, "PLT padding");
+ public static final Tag MOVETAB = new Tag(0x6ffffefe, "Move table");
+ public static final Tag SYMINFO = new Tag(0x6ffffeff, "Syminfo table");
+
+ public static final Tag VERSYM = new Tag(0x6ffffff0, "DT_VERSYM");
+ public static final Tag RELACOUNT = new Tag(0x6ffffff9, "DT_RELACOUNT");
+ public static final Tag RELCOUNT = new Tag(0x6ffffffa, "DT_RELCOUNT");
+ public static final Tag FLAGS_1 = new Tag(0x6ffffffb, "State flags");
+ public static final Tag VERDEF = new Tag(0x6ffffffc, "Address of version definition table");
+ public static final Tag VERDEFNUM = new Tag(0x6ffffffd, "Number of version definitions");
+ public static final Tag VERNEED = new Tag(0x6ffffffe, "Address of table with needed versions");
+ public static final Tag VERNEEDNUM = new Tag(0x6fffffff, "Number of needed versions");
+
+ private static final Tag[] VALUES =
+ { NULL, NEEDED, PLTRELSZ, PLTGOT, HASH, STRTAB, SYMTAB, RELA, RELASZ, RELAENT, STRSZ, SYMENT, INIT, FINI,
+ SONAME, RPATH, SYMBOLIC, REL, RELSZ, RELENT, PLTREL, DEBUG, TEXTREL, JMPREL, BIND_NOW, INIT_ARRAY,
+ FINI_ARRAY, INIT_ARRAYSZ, FINI_ARRAYSZ, RUNPATH, FLAGS, ENCODING, PREINIT_ARRAY, PREINIT_ARRAYSZ,
+ GNU_PRELINKED, GNU_CONFLICTSZ, GNU_LIBLISTSZ, CHECKSUM, PLTPADSZ, MOVEENT, MOVESZ, FEATURE_1, POSFLAG_1,
+ SYMINSZ, GNU_HASH, TLSDESC_PLT, TLSDESC_GOT, GNU_CONFLICT, GNU_LIBLIST, CONFIG, DEPAUDIT, AUDIT, PLTPAD,
+ MOVETAB, SYMINFO, VERSYM, RELACOUNT, RELCOUNT, FLAGS_1, VERDEF, VERDEFNUM, VERNEED, VERNEEDNUM };
+
+ private static final int DT_LOOS = 0x6000000d;
+ private static final int DT_HIOS = 0x6fffffff;
+ private static final int DT_LOPROC = 0x70000000;
+ private static final int DT_HIPROC = 0x7fffffff;
+
+ public static Tag valueOf(int value) {
+ for (Tag t : VALUES) {
+ if (t.no == value) {
+ return t;
+ }
+ }
+ if (value >= DT_LOOS && value <= DT_HIOS) {
+ return new Tag(value, "OS-specific tag");
+ } else if (value >= DT_LOPROC && value <= DT_HIPROC) {
+ return new Tag(value, "Processor-specific tag");
+ } else {
+ throw new IllegalArgumentException("Invalid/unknown tag: " + Integer.toHexString(value));
+ }
+ }
+
+ private final int no;
+ private final String desc;
+ private final boolean strTableOffset;
+
+ private Tag(int no, String desc) {
+ this(no, desc, false);
+ }
+
+ private Tag(int no, String desc, boolean strTableOffset) {
+ this.no = no;
+ this.desc = desc;
+ this.strTableOffset = strTableOffset;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ Tag other = (Tag) obj;
+ return (no == other.no);
+ }
+
+ @Override
+ public int hashCode() {
+ return 37 + no;
+ }
+
+ public String name() {
+ return desc;
+ }
+
+ public int ordinal() {
+ return no;
+ }
+
+ @Override
+ public String toString() {
+ return desc;
+ }
+ }
+
+ private final Tag tag;
+ private final long value;
+
+ public DynamicEntry(Tag tag, long value) {
+ this.tag = tag;
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ DynamicEntry other = (DynamicEntry) obj;
+ return (tag == other.tag) && (value == other.value);
+ }
+
+ public Tag getTag() {
+ return tag;
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public boolean isStringOffset() {
+ return tag.strTableOffset;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + tag.hashCode();
+ result = prime * result + (int) (value ^ (value >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%x]", tag, value);
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/elf/Elf.java b/app/src/main/java/nl/lxtreme/binutils/elf/Elf.java
new file mode 100644
index 00000000..b58ab4a3
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/elf/Elf.java
@@ -0,0 +1,498 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2016 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.elf;
+
+
+import android.util.*;
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+import nl.lxtreme.binutils.elf.DynamicEntry.*;
+
+
+/**
+ * Represents an ELF object file.
+ * + * This class is not thread-safe! + *
+ */ +public class Elf implements Closeable +{ + static int expectByteInRange( int in, int lowInclusive, int highInclusive, String errMsg ) throws IOException + { + if ( in < lowInclusive || in > highInclusive ) + { + throw new IOException( errMsg ); + } + return in; + } + + static String getZString( byte[] buf, long offset ) + { + return getZString( buf, ( int )( offset & 0xFFFFFFFF ) ); + } + + static String getZString( byte[] buf, int offset ) + { + int end = offset; + while ( end < buf.length && buf[end] != 0 ) + { + end++; + } + return new String( buf, offset, ( end - offset ) ); + } + + static boolean isBitSet( int flags, int mask ) + { + return ( flags & mask ) == mask; + } + + static boolean isBitSet( long flags, long mask ) + { + return ( flags & mask ) == mask; + } + + static void readFully( ReadableByteChannel ch, ByteBuffer buf, String errMsg ) throws IOException + { + buf.rewind(); + int read = ch.read( buf ); + if ( read != buf.limit() ) + { + throw new IOException( errMsg + " Read only " + read + " of " + buf.limit() + " bytes!" ); + } + buf.flip(); + } + + public final Header header; + public final ProgramHeader[] programHeaders; + public final SectionHeader[] sectionHeaders; + public final DynamicEntry[] dynamicTable; + + // locally managed. + private FileChannel channel; + + public Elf( File file ) throws IOException + { + this((new RandomAccessFile(file.getAbsolutePath(), "r").getChannel()));/* FileChannel.open( file.toPath(), StandardOpenOption.READ */ + /* FileChannel.open( file.toPath(), StandardOpenOption.READ*/ + } + + public Elf( FileChannel channel ) throws IOException + { + this.channel = channel; + this.header = new Header( channel ); + + // Read the last part of the ELF header and interpret the various headers... + ByteBuffer buf = ByteBuffer.allocate( 65536 ); + buf.order( header.elfByteOrder ); + buf.limit( 10 ); + + readFully( channel, buf, "Unable to read entry information!" ); + + int programHeaderEntrySize = buf.getShort(); + int programHeaderEntryCount = buf.getShort(); + int sectionHeaderEntrySize = buf.getShort(); + int sectionHeaderEntryCount = buf.getShort(); + int sectionNameTableIndex = buf.getShort(); + + // Should not be necessary unless we've not read the entire header... + channel.position( header.programHeaderOffset ); + + // Prepare for reading the program headers... + buf.limit( programHeaderEntrySize ); + + this.programHeaders = new ProgramHeader[programHeaderEntryCount]; + for ( int i = 0; i < programHeaderEntryCount; i++ ) + { + readFully( channel, buf, "Unable to read program header entry #" + i ); + + this.programHeaders[i] = new ProgramHeader( header.elfClass, buf ); + } + + // Should not be necessary unless we've not read the entire header... + channel.position( header.sectionHeaderOffset ); + + // Prepare for reading the section headers... + buf.limit( sectionHeaderEntrySize ); + + this.sectionHeaders = new SectionHeader[sectionHeaderEntryCount - 1]; + for ( int i = 0; i < sectionHeaderEntryCount; i++ ) + { + readFully( channel, buf, "Unable to read section header entry #" + i ); + + SectionHeader sHdr = new SectionHeader( header.elfClass, buf ); + if ( i == 0 ) + { + // Should always be a SHT_NONE entry... + if ( sHdr.type != SectionType.NULL ) + { + throw new IOException( "Invalid section found! First section should always be of type SHT_NULL!" ); + } + } + else + { + this.sectionHeaders[i - 1] = sHdr; + } + } + + if ( sectionNameTableIndex != 0 ) + { + // There's a section name string table present... + SectionHeader shdr = this.sectionHeaders[sectionNameTableIndex - 1]; + + buf = getSection( shdr ); + if ( buf == null ) + { + throw new IOException( "Unable to get section name table!" ); + } + + for ( SectionHeader hdr : sectionHeaders ) + { + hdr.setName( buf ); + } + } + + ProgramHeader phdr = getProgramHeaderByType( SegmentType.DYNAMIC ); + if ( phdr != null ) + { + Listnull
+ * if no such segment exists in this ELF object.
+ */
+ public ProgramHeader getProgramHeaderByType( SegmentType type )
+ {
+ if ( type == null )
+ {
+ throw new IllegalArgumentException( "Type cannot be null!" );
+ }
+ for ( ProgramHeader hdr : programHeaders )
+ {
+ if ( type.equals( hdr.type ) )
+ {
+ return hdr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for determining which interpreter should be used for
+ * this ELF object.
+ *
+ * @return the name of the interpreter, or null
if no interpreter
+ * could be determined.
+ */
+ public String getProgramInterpreter() throws IOException
+ {
+ ProgramHeader phdr = getProgramHeaderByType( SegmentType.INTERP );
+ if ( phdr == null )
+ {
+ return null;
+ }
+
+ ByteBuffer buf = getSegment( phdr );
+ if ( buf == null )
+ {
+ throw new IOException( "Unable to get program interpreter segment?!" );
+ }
+
+ return new String( buf.array(), 0, buf.remaining() );
+ }
+
+ /**
+ * Returns the actual section data based on the information from the given
+ * header.
+ *
+ * @return a byte buffer from which the section data can be read, never
+ * null
.
+ */
+ public ByteBuffer getSection( SectionHeader shdr ) throws IOException
+ {
+ if ( shdr == null )
+ {
+ throw new IllegalArgumentException( "Header cannot be null!" );
+ }
+ if ( channel == null )
+ {
+ throw new IOException( "ELF file is already closed!" );
+ }
+
+ ByteBuffer buf = ByteBuffer.allocate( ( int )shdr.size );
+ buf.order( header.elfByteOrder );
+
+ channel.position( shdr.fileOffset );
+ readFully( channel, buf, "Unable to read section completely!" );
+
+ return buf;
+ }
+
+ /**
+ * Returns the first section header with the given type.
+ *
+ * @return the first section header with the given type, or null
+ * if no such section exists in this ELF object.
+ */
+ public SectionHeader getSectionHeaderByType( SectionType type )
+ {
+ if ( type == null )
+ {
+ throw new IllegalArgumentException( "Type cannot be null!" );
+ }
+ for ( SectionHeader hdr : sectionHeaders )
+ {
+ if ( type.equals( hdr.type ) )
+ {
+ return hdr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the actual segment data based on the information from the given
+ * header.
+ *
+ * @return a {@link ByteBuffer} from which the segment data can be read, never
+ * null
.
+ */
+ public ByteBuffer getSegment( final ProgramHeader phdr ) throws IOException
+ {
+ if ( phdr == null )
+ {
+ throw new IllegalArgumentException( "Header cannot be null!" );
+ }
+ if ( channel == null )
+ {
+ throw new IOException( "ELF file is already closed!" );
+ }
+
+ ByteBuffer buf = ByteBuffer.allocate( ( int )phdr.segmentFileSize );
+ buf.order( header.elfByteOrder );
+
+ channel.position( phdr.offset );
+ readFully( channel, buf, "Unable to read segment completely!" );
+
+ return buf;
+ }
+
+ public List-1
in case an end-of-stream was
+ * encountered.
+ * @throws IOException
+ * in case of stream I/O problems;
+ */
+ public abstract int readByte() throws IOException;
+
+ /**
+ * Reads a long word (4 bytes) from the underlying stream.
+ *
+ * @return the next long word, can be -1
in case an end-of-stream
+ * was encountered.
+ * @throws IOException
+ * in case of stream I/O problems;
+ */
+ public int readLongWord() throws IOException
+ {
+ final byte[] data = readBytes( 4 );
+ if ( data == null )
+ {
+ return -1;
+ }
+
+ return ( int )ByteOrderUtils.decode( getByteOrder(), data );
+ }
+
+ /**
+ * Reads a word (2 bytes) from the underlying stream.
+ *
+ * @return the next word, can be -1
in case an end-of-stream was
+ * encountered.
+ * @throws IOException
+ * in case of stream I/O problems;
+ */
+ public int readWord() throws IOException
+ {
+ final byte[] data = readBytes( 2 );
+ if ( data == null )
+ {
+ return -1;
+ }
+
+ return ( int )ByteOrderUtils.decode( ByteOrder.LITTLE_ENDIAN, data );
+ }
+
+ /**
+ * Returns the byte order in which this data provider reads its data.
+ *
+ * @return a byte order, never null
.
+ */
+ protected abstract ByteOrder getByteOrder();
+
+ /**
+ * Convenience method to read a number of bytes.
+ *
+ * @param aCount
+ * the number of bytes to read, should be > 0.
+ * @return a byte array with the read bytes, can be null
in case
+ * an EOF was found.
+ * @throws IOException
+ * in case of I/O problems;
+ * @throws IllegalArgumentException
+ * in case the given count was <= 0.
+ */
+ protected final byte[] readBytes( final int aCount ) throws IOException, IllegalArgumentException
+ {
+ if ( aCount <= 0 )
+ {
+ throw new IllegalArgumentException( "Count cannot be less or equal to zero!" );
+ }
+
+ final byte[] result = new byte[aCount];
+ for ( int i = 0; i < aCount; i++ )
+ {
+ int readByte = readByte();
+ if ( readByte == -1 )
+ {
+ return null;
+ }
+ result[i] = ( byte )readByte;
+ }
+
+ return result;
+ }
+
+ protected final char[] readChars( final int aCount ) throws IOException, IllegalArgumentException
+ {
+ if ( aCount <= 0 )
+ {
+ throw new IllegalArgumentException( "Invalid count!" );
+ }
+ final char[] buf = new char[aCount];
+ if ( this.reader.read( buf ) != aCount )
+ {
+ throw new IOException( "Unexpected end of stream!" );
+ }
+ return buf;
+ }
+
+ /**
+ * Skips until the end-of-line is found.
+ */
+ protected final int readSingleByte() throws IOException
+ {
+ int ch;
+ do
+ {
+ ch = this.reader.read();
+ }
+ while ( ( ch != -1 ) && Character.isWhitespace( ch ) );
+ return ch;
+ }
+
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/IntelHexReader.java b/app/src/main/java/nl/lxtreme/binutils/hex/IntelHexReader.java
new file mode 100644
index 00000000..1e8b0ec7
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/hex/IntelHexReader.java
@@ -0,0 +1,240 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.hex;
+
+
+import java.io.*;
+import java.nio.*;
+
+import nl.lxtreme.binutils.hex.util.*;
+
+
+/**
+ * Provides a data provider based on a Intel-HEX file.
+ * + * A file in the Intel hex-format is a text file containing hexadecimal encoded + * data, organised in so-called records: a text line in the following format: + *
+ * + *+ * :<datacount><address><recordtype><data><checksum> + *+ *
+ * In which is: + *
+ *+ * Files with only 00, and 01 records are called to be in Intel "Intellec" 8/MDS + * format. The Intel MCS86 (Intellec 86) format adds the 02 type records to + * that. + *
+ */ +public class IntelHexReader extends AbstractReader +{ + // CONSTANTS + + private static final char PREAMBLE = ':'; + + private static final int DATA_TYPE = 0; + private static final int TERMINATION_TYPE = 1; + private static final int EXTENDED_SEGMENT_ADDRESS_TYPE = 2; + private static final int START_SEGMENT_ADDRESS_TYPE = 3; + private static final int EXTENDED_LINEAR_ADDRESS_TYPE = 4; + private static final int START_LINEAR_ADDRESS_TYPE = 5; + + // VARIABLES + + private final Checksummer checksum; + + private Integer segmentBaseAddress; + private Integer linearAddress; + private Integer address; + private Integer dataLength; + + // CONSTRUCTORS + + /** + * Creates a new IntelHexDataProvider instance. + * + * @param aReader + * the reader to use. + */ + public IntelHexReader( final Reader aReader ) + { + super( aReader ); + + this.checksum = Checksum.TWOS_COMPLEMENT.instance(); + } + + // METHODS + + @Override + public long getAddress() throws IOException + { + if ( this.address == null ) + { + throw new IOException( "Unexpected call to getAddress!" ); + } + return this.address.longValue(); + } + + @Override + public int readByte() throws IOException + { + int ch; + + do + { + ch = readSingleByte(); + if ( ch == -1 ) + { + // End-of-file reached; return immediately! + return -1; + } + + if ( PREAMBLE == ch ) + { + // New record started... + startNewDataRecord(); + } + else if ( this.dataLength != null ) + { + final int secondHexDigit = this.reader.read(); + if ( secondHexDigit == -1 ) + { + throw new IOException( "Unexpected end-of-stream!" ); + } + + final char[] buf = { ( char )ch, ( char )secondHexDigit }; + + final byte dataByte = HexUtils.parseHexByte( buf ); + if ( this.dataLength == 0 ) + { + // All data-bytes returned? If so, verify the CRC we've just read... + final byte calculatedCRC = this.checksum.getResult(); + if ( dataByte != calculatedCRC ) + { + throw new IOException( "CRC Error! Expected: 0x" + Integer.toHexString( dataByte ) + "; got: 0x" + + Integer.toHexString( calculatedCRC ) ); + } + } + else + { + // Decrease the number of hex-bytes we've got to read... + this.checksum.add( ( byte )dataByte ); + + this.dataLength--; + this.address++; + + return ( dataByte & 0xFF ); + } + } + } + while ( ch != -1 ); + + // We should never come here; it means that we've found a situation that + // isn't covered by our loop above... + throw new IOException( "Invalid Intel HEX-file!" ); + } + + @Override + protected ByteOrder getByteOrder() + { + return ByteOrder.LITTLE_ENDIAN; + } + + /** + * Starts a new data record, calculates the initial address, and checks what + * kind of data the record contains. + * + * @throws IOException + * in case of I/O problems. + */ + private void startNewDataRecord() throws IOException + { + // First byte is the length of the data record... + this.dataLength = ( int )HexUtils.readHexByte( this.reader ); + + // When a segment base address is previously set, calculate the actual + // address by OR-ing this base-address with the address of the record... + this.address = HexUtils.readHexWord( this.reader ); + if ( ( this.segmentBaseAddress != null ) && ( this.segmentBaseAddress > 0 ) ) + { + this.address = this.segmentBaseAddress | this.address; + } + else if ( ( this.linearAddress != null ) && ( this.linearAddress > 0 ) ) + { + this.address = ( this.linearAddress << 16 ) | this.address; + } + + final int recordType = HexUtils.readHexByte( this.reader ); + + // Calculate the first part of the record CRC; which is defined as the + // ones-complement of all (non-CRC) items in the record... + this.checksum.reset(); + this.checksum.add( this.dataLength.byteValue() ); + this.checksum.add( ( byte )recordType ); + this.checksum.addWord( this.address ); + + if ( DATA_TYPE == recordType ) + { + // Ok, found first data item... Adjust address with a single byte in + // order to obtain a valid first address... + this.address--; + } + else if ( EXTENDED_SEGMENT_ADDRESS_TYPE == recordType ) + { + this.segmentBaseAddress = HexUtils.readHexWord( this.reader ); + + this.checksum.addWord( this.segmentBaseAddress ); + + // Ignore the rest of the data; but calculate the CRC... + this.dataLength = 0; + } + else if ( EXTENDED_LINEAR_ADDRESS_TYPE == recordType ) + { + this.linearAddress = HexUtils.readHexWord( this.reader ); + + this.checksum.addWord( this.linearAddress ); + + // Ignore the rest of the data; but calculate the CRC... + this.dataLength = 0; + } + else if ( TERMINATION_TYPE == recordType ) + { + // Ignore the rest of the data; but calculate the CRC... + this.dataLength = 0; + } + else if ( ( START_LINEAR_ADDRESS_TYPE != recordType ) && ( START_SEGMENT_ADDRESS_TYPE != recordType ) ) + { + throw new IOException( "Unknown Intel record type: " + recordType ); + } + } +} diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/SRecordReader.java b/app/src/main/java/nl/lxtreme/binutils/hex/SRecordReader.java new file mode 100644 index 00000000..bca10483 --- /dev/null +++ b/app/src/main/java/nl/lxtreme/binutils/hex/SRecordReader.java @@ -0,0 +1,275 @@ +/* + * BinUtils - access various binary formats from Java + * + * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl + * + * Licensed under Apache License v2. + */ +package nl.lxtreme.binutils.hex; + + +import java.io.*; +import java.nio.*; + +import nl.lxtreme.binutils.hex.util.*; + + +/** + * Provides a data provider based on a Motorola SRecord file. + * + *+ * S<recordtype><recordlength><address><data><checksum> + *+ *
+ * In which is: + *
+ *+ * Files with only S0, S1 and S9 records are also called to be in Motorola + * Exorcisor format. If also S2 and S8 records appear, the format is also called + * Motorola Exormax. + *
+ */ +public class SRecordReader extends AbstractReader +{ + // CONSTANTS + + private static final char PREAMBLE = 'S'; + + // VARIABLES + + private final Checksummer checksum; + + private Integer address; + private Integer dataLength; + private boolean inDataRecord; + + // CONSTRUCTORS + + /** + * Creates a new SRecordDataProvider instance. + * + * @param aReader + */ + public SRecordReader( final Reader aReader ) + { + super( aReader ); + + this.checksum = Checksum.ONES_COMPLEMENT.instance(); + } + + // METHODS + + @Override + public long getAddress() throws IOException + { + if ( this.address == null ) + { + throw new IOException( "Unexpected call to getAddress!" ); + } + return this.address; + } + + @Override + public int readByte() throws IOException + { + int ch; + + do + { + ch = readSingleByte(); + if ( ch == -1 ) + { + // End-of-file reached; return immediately! + return -1; + } + + if ( PREAMBLE == ch ) + { + // New record started... + this.inDataRecord = isDataRecord( startNewRecord() ); + } + else if ( this.dataLength != null ) + { + final int secondHexDigit = this.reader.read(); + if ( secondHexDigit == -1 ) + { + throw new IOException( "Unexpected end-of-stream!" ); + } + final char[] buf = { ( char )ch, ( char )secondHexDigit }; + + final byte dataByte = HexUtils.parseHexByte( buf ); + if ( this.dataLength == 0 ) + { + // All data-bytes returned? If so, verify the CRC we've just read... + final byte calculatedCRC = this.checksum.getResult(); + if ( dataByte != calculatedCRC ) + { + throw new IOException( "CRC Error! Expected: " + dataByte + "; got: " + calculatedCRC ); + } + } + else + { + // Decrease the number of hex-bytes we've got to read... + this.checksum.add( ( byte )dataByte ); + + this.dataLength--; + this.address++; + + if ( this.inDataRecord ) + { + return ( dataByte & 0xFF ); + } + } + } + } + while ( ch != -1 ); + + // We should never come here; it means that we've found a situation that + // isn't covered by our loop above... + throw new IOException( "Invalid Intel HEX-file!" ); + } + + /** + * @see nl.lxtreme.cpemu.util.data.impl.AbstractDataProvider#getByteOrder() + */ + @Override + protected ByteOrder getByteOrder() + { + return ByteOrder.BIG_ENDIAN; + } + + /** + * Returns the address length in number of bytes of a given SRecord-type. + * + * @param aType + * the SRecord-type to return the address length for. + * @return the address length as number of bytes. + */ + private int getAddressLength( final int aType ) + { + int result = 2; + if ( ( aType == 2 ) || ( aType == 8 ) ) + { + result = 3; + } + else if ( ( aType == 3 ) || ( aType == 7 ) ) + { + result = 4; + } + + return result; + } + + /** + * @param aType + * the integer (srecord-)type; + * @returntrue
if the given (srecord-)type is a data record,
+ * otherwise false
.
+ */
+ private boolean isDataRecord( final int aType )
+ {
+ return ( aType == 1 ) || ( aType == 2 ) || ( aType == 3 );
+ }
+
+ /**
+ * @param aType
+ * the integer (srecord-)type;
+ * @return true
if the given (srecord-)type is a header record,
+ * otherwise false
.
+ */
+ private boolean isHeaderRecord( final int aType )
+ {
+ return ( aType == 0 );
+ }
+
+ /**
+ * Returns whether the given (srecord-)type is valid or not.
+ *
+ * @param aType
+ * the integer (srecord-)type;
+ * @return true
if the given (srecord-)type is valid, otherwise
+ * false
.
+ */
+ private boolean isValidType( final int aType )
+ {
+ // (S0, S1, S2, S3, S5, S7, S8, or S9)
+ return ( ( aType == 0 ) || ( aType == 1 ) || ( aType == 2 ) || ( aType == 3 ) || ( aType == 5 ) || ( aType == 7 )
+ || ( aType == 8 ) || ( aType == 9 ) );
+ }
+
+ private int startNewRecord() throws IOException
+ {
+ // First byte is the length of the data record...
+ final int type = this.reader.read() - '0';
+ if ( !isValidType( type ) )
+ {
+ throw new IOException( "Unknown type: " + type );
+ }
+
+ // recordLength is representing the number of address, data, and checksum
+ // bytes;
+ final int recordLength = HexUtils.readHexByte( this.reader );
+
+ final int addressLength = getAddressLength( type );
+ this.address = HexUtils.readHexNumber( this.reader, addressLength );
+
+ // Calculate the first part of the record CRC; which is defined as the
+ // ones-complement of all (non-CRC) items in the record...
+ // record length
+ this.checksum.reset();
+ this.checksum.add( ( byte )recordLength );
+ // address length
+ this.checksum.addWord( ( this.address >> 16 ) & 0xFFFF );
+ this.checksum.addWord( this.address & 0xFFFF );
+
+ // The real data length...
+ this.dataLength = recordLength - addressLength - 1;
+
+ if ( this.dataLength > 0 )
+ {
+ // Make sure NO data is only for records that should have NO data...
+ if ( !isDataRecord( type ) && !isHeaderRecord( type ) )
+ {
+ throw new IOException( "Data found while record-type should not have data!" );
+ }
+
+ this.address--;
+ }
+ else if ( this.dataLength == 0 )
+ {
+ // Make sure NO data is only for records that should have NO data...
+ if ( isDataRecord( type ) || isHeaderRecord( type ) )
+ {
+ throw new IOException( "No data found while record-type should have data!" );
+ }
+ }
+
+ return type;
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/util/ByteOrderUtils.java b/app/src/main/java/nl/lxtreme/binutils/hex/util/ByteOrderUtils.java
new file mode 100644
index 00000000..ab5d9e74
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/hex/util/ByteOrderUtils.java
@@ -0,0 +1,138 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.hex.util;
+
+
+import java.nio.*;
+
+
+/**
+ * In computing, endianness is the byte (and sometimes bit) ordering used to
+ * represent some kind of data. Typical cases are the order in which integer
+ * values are stored as bytes in computer memory (relative to a given memory
+ * addressing scheme) and the transmission order over a network or other medium.
+ * When specifically talking about bytes, endianness is also referred to simply
+ * as byte order.
+ */
+public class ByteOrderUtils
+{
+ // CONSTRUCTORS
+
+ private ByteOrderUtils()
+ {
+ // NO-op
+ }
+
+ // METHODS
+
+ /**
+ * Creates a (16-bit) word value with the correct byte order.
+ *
+ * @param aMSB
+ * the most significant byte;
+ * @param aLSB
+ * the least significant byte.
+ * @return the 16-bit combination of both given bytes in the order of
+ * endianness.
+ */
+ public static int createWord( final ByteOrder aByteOrder, final int aMSB, final int aLSB )
+ {
+ if ( aByteOrder == ByteOrder.BIG_ENDIAN )
+ {
+ return ( ( aMSB << 8 ) & 0xFF00 ) | ( aLSB & 0x00FF );
+ }
+
+ return ( ( aLSB << 8 ) & 0xFF00 ) | ( aMSB & 0x00FF );
+ }
+
+ /**
+ * Creates a (16-bit) word value with the correct byte order.
+ *
+ * @param aMSB
+ * the most significant byte;
+ * @param aLSB
+ * the least significant byte.
+ * @return the 16-bit combination of both given bytes in the order of
+ * endianness.
+ */
+ public static int createWord( final int aMSB, final int aLSB )
+ {
+ return createWord( ByteOrder.nativeOrder(), aMSB, aLSB );
+ }
+
+ /**
+ * Convenience method to create a single value using the given byte values in
+ * a given byte order.
+ *
+ * @param aExpectedByteOrder
+ * the expected byte order;
+ * @param aBytes
+ * the bytes to decode into a single value, their order depends!
+ * @return the word in the expected byte order.
+ */
+ public static long decode( final ByteOrder aExpectedByteOrder, final byte... aBytes )
+ {
+ final int byteCount = aBytes.length;
+ final int lastByteIdx = byteCount - 1;
+
+ long result = 0L;
+
+ if ( aExpectedByteOrder == ByteOrder.BIG_ENDIAN )
+ {
+ for ( int i = 0; i < byteCount; i++ )
+ {
+ result <<= 8;
+ result |= ( aBytes[i] & 0xFF );
+ }
+ }
+ else if ( aExpectedByteOrder == ByteOrder.LITTLE_ENDIAN )
+ {
+ for ( int i = lastByteIdx; i >= 0; i-- )
+ {
+ result <<= 8;
+ result |= ( aBytes[i] & 0xFF );
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Switches the order of bytes of the given (16-bit) word value.
+ * + * In effect, this method casts a little-endian value to a big-endian value + * and the other way around. + *
+ * + * @param aValue + * the (16-bit) word value to switch the byte order for. + * @return the given value with the MSB & LSB switched. + */ + public static int swap16( final int aValue ) + { + return ( ( ( aValue & 0x00ff ) << 8 ) | ( ( aValue & 0xff00 ) >> 8 ) ); + } + + /** + * Switches the order of bytes of the given (32-bit) long word value. + *+ * In effect, this method casts a little-endian value to a big-endian value + * and the other way around. + *
+ * + * @param aValue + * the (32-bit) long word value to switch the byte order for. + * @return the given value with the MSB & LSB switched. + */ + public static int swap32( final int aValue ) + { + return ( ( aValue & 0x000000FF ) << 24 ) | ( ( aValue & 0x0000FF00 ) << 8 ) // + | ( ( aValue & 0xFF000000 ) >>> 24 ) | ( ( aValue & 0x00FF0000 ) >>> 8 ); + } + +} diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksum.java b/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksum.java new file mode 100644 index 00000000..d979b59d --- /dev/null +++ b/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksum.java @@ -0,0 +1,104 @@ +/* + * BinUtils - access various binary formats from Java + * + * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl + * + * Licensed under Apache License v2. + */ +package nl.lxtreme.binutils.hex.util; + + +/** + * Provides two checksum algorithms commonly used in many HEX-files. + */ +public enum Checksum +{ + /** */ + ONES_COMPLEMENT + { + @Override + public Checksummer instance( byte seed ) + { + return new BaseChecksummer( seed ) + { + @Override + public byte getResult() + { + return ( byte )( ~this.sum ); + } + }; + } + }, + /** */ + TWOS_COMPLEMENT + { + @Override + public Checksummer instance( byte seed ) + { + return new BaseChecksummer( seed ) + { + @Override + public byte getResult() + { + return ( byte )( ~this.sum + 1 ); + } + }; + } + }; + + // METHODS + + /** + * @return a new instance, cannot benull
.
+ */
+ public final Checksummer instance()
+ {
+ return instance( ( byte )0 );
+ }
+
+ /**
+ * @param seed
+ * the initial value to use for the checksum calculation.
+ * @return a new instance, cannot be null
.
+ */
+ public abstract Checksummer instance( byte seed );
+
+ /**
+ * Base implementation of a {@link Checksummer} shared by the various specific
+ * implementations.
+ */
+ static abstract class BaseChecksummer implements Checksummer
+ {
+ protected byte sum;
+
+ public BaseChecksummer( byte seed )
+ {
+ this.sum = seed;
+ }
+
+ @Override
+ public final Checksummer add( byte... aValues )
+ {
+ for ( byte value : aValues )
+ {
+ this.sum += value;
+ }
+ return this;
+ }
+
+ @Override
+ public final Checksummer addWord( int value )
+ {
+ add( ( byte )( ( value >> 8 ) & 0xFF ) );
+ add( ( byte )( value & 0xFF ) );
+ return this;
+ }
+
+ @Override
+ public final Checksummer reset()
+ {
+ this.sum = 0;
+ return this;
+ }
+ }
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksummer.java b/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksummer.java
new file mode 100644
index 00000000..b96a8376
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/hex/util/Checksummer.java
@@ -0,0 +1,51 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.hex.util;
+
+
+public interface Checksummer
+{
+
+ /**
+ * Adds a given value to the checksum.
+ *
+ * @param values
+ * the byte values to add to this checksum.
+ * @return this checksummer instance, for chaining purposes.
+ */
+ Checksummer add( byte... values );
+
+ /**
+ * Adds a given word (16-bit) value to the checksum.
+ *
+ * This method is a convenience method for calling:
+ *
+ * @param value
+ * the word value to add to this checksum.
+ * @return this checksummer instance, for chaining purposes.
+ */
+ Checksummer addWord( int value );
+
+ /**
+ * Returns the resulting checksum of all previously added values.
+ *
+ * @return the resulting checksum value.
+ */
+ byte getResult();
+
+ /**
+ * Prepares this instance for a new checksum.
+ *
+ * @return this checksummer instance, for chaining purposes.
+ */
+ Checksummer reset();
+
+}
diff --git a/app/src/main/java/nl/lxtreme/binutils/hex/util/HexUtils.java b/app/src/main/java/nl/lxtreme/binutils/hex/util/HexUtils.java
new file mode 100644
index 00000000..d7f26f57
--- /dev/null
+++ b/app/src/main/java/nl/lxtreme/binutils/hex/util/HexUtils.java
@@ -0,0 +1,130 @@
+/*
+ * BinUtils - access various binary formats from Java
+ *
+ * (C) Copyright 2017 - JaWi - j.w.janssen@lxtreme.nl
+ *
+ * Licensed under Apache License v2.
+ */
+package nl.lxtreme.binutils.hex.util;
+
+
+import java.io.*;
+
+
+/**
+ * Provides some convenience utilities to work with strings of hex digits.
+ */
+public final class HexUtils
+{
+ // CONSTRUCTORS
+
+ /**
+ * Creates a new HexUtils instance.
+ */
+ private HexUtils()
+ {
+ // NO-op
+ }
+
+ // METHODS
+
+ /**
+ * Parses the hex-byte in the given character sequence at the given offset.
+ *
+ * @param aInput
+ * the characters to parse as hex-bytes.
+ * @return a byte value.
+ * @throws IllegalArgumentException
+ * in case the given char sequence was
+ * add((byte)(value >> 8));
+ * add((byte)(value & 0xFF));
+ *
null
, in case
+ * the given input did not yield a hex-byte, or the requested offset
+ * is outside the boundaries of the given char sequence.
+ */
+ public static byte parseHexByte( char[] aInput ) throws IllegalArgumentException
+ {
+ if ( aInput == null )
+ {
+ throw new IllegalArgumentException( "Input cannot be null!" );
+ }
+ if ( aInput.length < 2 )
+ {
+ throw new IllegalArgumentException( "Input should be at least two characters!" );
+ }
+ return ( byte )( ( parseHex( aInput[0] ) << 4 ) | ( parseHex( aInput[1] ) ) );
+ }
+
+ /**
+ * Reads two characters from the given reader and parses them as a single
+ * hex-value byte.
+ *
+ * @param aReader
+ * @return
+ * @throws IllegalArgumentException
+ * @throws IOException
+ */
+ public static byte readHexByte( Reader aReader ) throws IllegalArgumentException, IOException
+ {
+ return ( byte )readHexNumber( aReader, 1 );
+ }
+
+ /**
+ * Reads a number of characters from the given reader and parses them as a
+ * hex-value.
+ *
+ * @param aReader
+ * the reader to read the data from;
+ * @param aByteCount
+ * the number of bytes to read (= 2 * amount of actual characters
+ * read).
+ * @return the parsed number.
+ * @throws IllegalArgumentException
+ * in case the given reader was null
or the given byte
+ * count was <= 0.
+ * @throws IOException
+ * in case of I/O problems.
+ */
+ public static int readHexNumber( Reader aReader, int aByteCount ) throws IllegalArgumentException, IOException
+ {
+ if ( aReader == null )
+ {
+ throw new IllegalArgumentException( "Input cannot be null!" );
+ }
+ if ( aByteCount <= 0 )
+ {
+ throw new IllegalArgumentException( "Byte count cannot be less or equal to zero!" );
+ }
+
+ int result = 0;
+ int nibbleCount = 2 * aByteCount;
+ while ( nibbleCount-- > 0 )
+ {
+ int hexdigit = parseHex( aReader.read() );
+ result = ( result << 4 ) | hexdigit;
+ }
+
+ return result;
+ }
+
+ /**
+ * Reads four characters from the given reader and parses them as a single
+ * hex-value word.
+ *
+ * @param aReader
+ * @return
+ * @throws IllegalArgumentException
+ * @throws IOException
+ */
+ public static int readHexWord( Reader aReader ) throws IllegalArgumentException, IOException
+ {
+ return ( readHexNumber( aReader, 2 ) & 0xFFFF );
+ }
+
+ private static int parseHex( int c )
+ {
+ int v = Character.digit( c, 16 );
+ if ( v < 0 )
+ {
+ throw new IllegalArgumentException( "Unexpected character: " + c );
+ }
+ return v;
+ }
+}
diff --git a/app/src/main/jniLibs/armeabi-v7a/libcapstone.so b/app/src/main/jniLibs/armeabi-v7a/libcapstone.so
new file mode 100644
index 00000000..f590e370
Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libcapstone.so differ
diff --git a/app/src/main/jniLibs/armeabi-v7a/libjnidispatch.so b/app/src/main/jniLibs/armeabi-v7a/libjnidispatch.so
new file mode 100644
index 00000000..79e1fbae
Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libjnidispatch.so differ
diff --git a/app/src/main/jniLibs/x86/libcapstone.so b/app/src/main/jniLibs/x86/libcapstone.so
new file mode 100644
index 00000000..217768bd
Binary files /dev/null and b/app/src/main/jniLibs/x86/libcapstone.so differ
diff --git a/app/src/main/jniLibs/x86/libjnidispatch.so b/app/src/main/jniLibs/x86/libjnidispatch.so
new file mode 100644
index 00000000..4045c86b
Binary files /dev/null and b/app/src/main/jniLibs/x86/libjnidispatch.so differ
diff --git a/app/src/main/res/drawable-hdpi/cell_shape.xml b/app/src/main/res/drawable-hdpi/cell_shape.xml
new file mode 100644
index 00000000..e1cefa0e
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/cell_shape.xml
@@ -0,0 +1,14 @@
+