/* objtype.c                        Copyright (C) 2005-2007 Codemist Ltd */


/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */

/* Signature: 73591169 07-Apr-2007 */


/*
 * Decodes the type of an object file and generates a small source
 * file that gives information about it and that can also be used to
 * give information about a compiler command. Typical usage:
 *   objtype foo.o imports.def - $CC $CFLAGS
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <time.h>

/*
 * In some cases I may end up passing a file-name of the form
 *     /cygdrive/d/path.../leaf
 * here. For things to behave I need to map that onto
 *     d:/...path/leaf
 */

static FILE *myfopen(char *name, char *mode)
{
    char newname[256];
    if (strncmp(name, "/cygdrive/", 10) != 0)
        return fopen(name, mode);
    newname[0] = name[10];
    newname[1] = ':';
    strcpy(&newname[2], &name[11]);
    return fopen(newname, mode);
}

int main(int argc, char *argv[])
{
    FILE *f1, *f2, *f3;
    time_t t = time(NULL);
    unsigned char hdr[20];
    int sixtyFourBit = 0, byteOrder = 0, machine = 0;
    char *s = NULL;
    int i;
    if (argc < 4)
    {   fprintf(stderr, "Usage: objtype xx.obj imports.def dest.c $CC $CFLAGS\n");
        return 1;
    }
    if (strcmp(argv[1], "-") == 0) f1 = stdin;
    else f1 = myfopen(argv[1], "rb");
    if (f1 == NULL)
    {   fprintf(stderr, "File \"%s\" not found\n", argv[1]);
        return 1;
    }
    if (strcmp(argv[2], "-") == 0) f2 = stdin;
    else f2 = myfopen(argv[2], "r");
    if (f2 == NULL)
    {   fprintf(stderr, "File \"%s\" not found\n", argv[2]);
        fclose(f1);
        return 1;
    }
    if (strcmp(argv[3], "-") == 0) f3 = stdout;
    else f3 = myfopen(argv[3], "w");
    if (f3 == NULL)
    {   fprintf(stderr, "File \"%s\" can not be written to\n", argv[3]);
        fclose(f1);
        fclose(f2);
        return 1;
    }
    if (fread(hdr, 20, 1, f1) != 1)
    {   fprintf(stderr, "Unable to read header from input file\n");
        fclose(f1);
        fclose(f2);
        fclose(f3);
        return 1;
    }
    fprintf(f3, "/*\n * Created %s */\n\n", asctime(localtime(&t)));
    fprintf(f3, "\nchar *linker_type = \"");
    if (hdr[0] == 0xfe &&
        hdr[1] == 0xed &&
        hdr[2] == 0xfa &&
        hdr[3] == 0xce) fprintf(f3, "mac-darwin");
    else if (hdr[0] == 0x7f &&
        hdr[1] == 'E' &&
        hdr[2] == 'L' &&
        hdr[3] == 'F')      /* ELF-format file */
    {   switch (hdr[4])
        {
    case 1:                 /* 32-bit ELF */
            sixtyFourBit = 0;
            break;
    case 2:                 /* 64-bit ELF */
            sixtyFourBit = 1;
            break;
    default:
            goto invalidObjectFormat;
        }
        /* hdr[5] is 1 for LSB, 2 for MSB data format */
        if (hdr[5] == 0 || hdr[5] > 2) goto invalidObjectFormat;
        byteOrder = hdr[5] - 1;
        /* hdr[6] had BETTER be 1, for "current ELT version" */
        if (hdr[6] != 1) goto invalidObjectFormat;
        /* hdr[7] & hdr[8] ought to be 0 to indicate "ordinary" ELF. */
        /* (hdr[7] can be 1 for HP Unix?) */
        if (byteOrder == 0)
            machine = hdr[18] | (hdr[19]<<8);
        else machine = (hdr[18]<<8) | hdr[19];
/*
 * A few sample machine types...
 *   386    3
 *   ppc    20
 *   ppc64  21
 *   arm    40
 *   x86_64 62
 * I will detect and name those ones explicitly
 */
        switch (machine)
        {
    case 3: if (!sixtyFourBit && byteOrder == 0) s = "i386";
            break;
    case 20:if (!sixtyFourBit) s = "ppc";
            break;
    case 21:if (sixtyFourBit) s = "ppc64";
            break;
    case 62:if (sixtyFourBit) s = "x86_64";
            break;
        }
        if (s != NULL) fprintf(f3, "%s", s);
        else fprintf(f3, "ELF-%d-%d", 2*sixtyFourBit+byteOrder, machine);
    }
    else
    {
/*
 * If the file is not ELF I will suppose that it is COFF, and
 * in that case the first two bytes identify a machine-type. Notable values
 * are
 *   386     0x14c
 *   arm     0x1c0
 *   x86_64  0x8664     note that these are given LSB
 * This format is what is used by Windows.
 */
        machine = hdr[0] + (hdr[1]<<8);
        switch (machine)
        {
    case 0x14c: fprintf(f3, "win32");
                break;
    case 0x1c0: fprintf(f3, "arm-coff");
                break;
    case 0x8664:fprintf(f3, "win64");
                break;
    default:    /* eg Itanium Windows could hit this case, I guess */
                fprintf(f3, "COFF-%x", hdr[0] | (hdr[1]<<8));
                break;
        }
    }
    fprintf(f3, "\";\n\nchar *import_data[] = {\n");
    for (;;)
    {   char line[100];
        int ch = getc(f2);
        while (ch == '\r') ch = getc(f2);
        i = 0;
        while (ch != '\n' && ch != EOF)
        {   if (i < 90) line[i++] = ch;
            ch = getc(f2);
            while (ch == '\r') ch = getc(f2);
        }
        line[i] = 0;
        if (i == 0) break;
        fprintf(f3, "    \"%s\",\n", line);
    }
    fprintf(f3, "    NULL\n};\n\nchar *compiler_command[] = {\n    \"%s\"",
                argv[4]);
    for (i=5; i<argc; i++)
    {   char *a = argv[i];
/*
 * I filter out some options that I do not think I will need in the
 * particular context of compiling the machine-genarated code that I
 * will be using here...
 */
        if (a[0]=='-' && a[1]=='I' && a[2]=='.') continue;
        if (strcmp(a, "-Wall") == 0) continue;
        if (strcmp(a, "-DHAVE_CONFIG_H") == 0) continue;
        fprintf(f3, ",\n    \"%s\"", a);
    }
    fprintf(f3, "\n};\n\n");
    fclose(f1);
    fclose(f2);
    fclose(f3);
    return 0;
invalidObjectFormat:
    fprintf(stderr, "Object file appears to be corrupt\n");
    fclose(f1);
    fclose(f2);
    fclose(f3);
    return 1;
}

/* end of objtype.c */

