/*****************************************************************************/
/*                                                                           */
/*				    dump.c				     */
/*                                                                           */
/*	    Dump subroutines for the od65 object file dump utility	     */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 2002-2009, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



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

/* common */
#include "addrsize.h"
#include "cddefs.h"
#include "coll.h"
#include "exprdefs.h"
#include "filepos.h"
#include "objdefs.h"
#include "optdefs.h"
#include "segdefs.h"
#include "symdefs.h"
#include "xmalloc.h"

/* od65 */
#include "error.h"
#include "fileio.h"
#include "dump.h"



/*****************************************************************************/
/*    		    	    	     Code   				     */
/*****************************************************************************/



static void DestroyStrPool (Collection* C)
/* Free all strings in the given pool plus the item pointers. Note: The
 * collection may not be reused later.
 */
{
    unsigned I;
    for (I = 0; I < CollCount (C); ++I) {
        xfree (CollAtUnchecked (C, I));
    }
    DoneCollection (C);
}



static const char* GetString (const Collection* C, unsigned Index)
/* Get a string from a collection. In fact, this function calls CollConstAt,
 * but will print a somewhat more readable error message if the index is out
 * of bounds.
 */
{
    if (Index >= CollCount (C)) {
        Error ("Invalid string index (%u) - file corrupt!", Index);
    }
    return CollConstAt (C, Index);
}



static void DumpObjHeaderSection (const char* Name,
		   	          unsigned long Offset,
			          unsigned long Size)
/* Dump a header section */
{
    printf ("    %s:\n", Name);
    printf ("      Offset:%24lu\n", Offset);
    printf ("      Size:  %24lu\n", Size);
}



static char* TimeToStr (unsigned long Time)
/* Convert the time into a string and return it */
{
    /* Get the time and convert to string */
    time_t T = (time_t) Time;
    char*  S = asctime (localtime (&T));

    /* Remove the trailing newline */
    unsigned Len = strlen (S);
    if (Len > 0 && S[Len-1] == '\n') {
	S[Len-1 ] = '\0';
    }

    /* Return the time string */
    return S;
}



static void SkipExpr (FILE* F)
/* Skip an expression from the given file */
{
    /* Read the node tag and handle NULL nodes */
    unsigned char Op = Read8 (F);
    if (Op == EXPR_NULL) {
     	return;
    }

    /* Check the tag and handle the different expression types */
    if (EXPR_IS_LEAF (Op)) {
	switch (Op) {

	    case EXPR_LITERAL:
	   	(void) Read32Signed (F);
	   	break;

	    case EXPR_SYMBOL:
	   	/* Read the import number */
	   	(void) ReadVar (F);
	   	break;

	    case EXPR_SECTION:
	   	/* Read the segment number */
	   	(void) Read8 (F);
	   	break;

	    default:
	   	Error ("Invalid expression op: %02X", Op);

	}

    } else {

    	/* Not a leaf node */
       	SkipExpr (F);
	SkipExpr (F);
    }
}



static const char* GetExportFlags (unsigned Flags, const unsigned char* ConDes)
/* Get the export flags as a (static) string */
{
    /* Static buffer */
    static char TypeDesc[256];
    static char* T;

    unsigned Count;
    unsigned I;

    /* Type of expression */
    TypeDesc[0] = '\0';
    switch (Flags & EXP_MASK_VAL) {
       	case EXP_CONST:	strcat (TypeDesc, "EXP_CONST");	break;
       	case EXP_EXPR:	strcat (TypeDesc, "EXP_EXPR"); 	break;
    }

    /* Constructor/destructor declarations */
    T = TypeDesc + strlen (TypeDesc);
    Count = GET_EXP_CONDES_COUNT (Flags);
    if (Count > 0 && ConDes) {
	T += sprintf (T, ",EXP_CONDES=");
	for (I = 0; I < Count; ++I) {
	    unsigned Type = CD_GET_TYPE (ConDes[I]);
	    unsigned Prio = CD_GET_PRIO (ConDes[I]);
	    if (I > 0) {
		*T++ = ',';
	    }
       	    T += sprintf (T, "[%u,%u]", Type, Prio);
	}
    }

    /* Return the result */
    return TypeDesc;
}



void DumpObjHeader (FILE* F, unsigned long Offset)
/* Dump the header of the given object file */
{
    ObjHeader H;

    /* Seek to the header position */
    FileSetPos (F, Offset);

    /* Read the header */
    ReadObjHeader (F, &H);

    /* Now dump the information */

    /* Output a header */
    printf ("  Header:\n");

    /* Magic */
    printf ("    Magic:%17s0x%08lX\n", "", H.Magic);

    /* Version */
    printf ("    Version:%25u\n", H.Version);

    /* Flags */
    printf ("    Flags:%21s0x%04X  (", "", H.Flags);
    if (H.Flags & OBJ_FLAGS_DBGINFO) {
    	printf ("OBJ_FLAGS_DBGINFO");
    }
    printf (")\n");

    /* Options */
    DumpObjHeaderSection ("Options", H.OptionOffs, H.OptionSize);

    /* Files */
    DumpObjHeaderSection ("Files", H.FileOffs, H.FileSize);

    /* Segments */
    DumpObjHeaderSection ("Segments", H.SegOffs, H.SegSize);

    /* Imports */
    DumpObjHeaderSection ("Imports", H.ImportOffs, H.ImportSize);

    /* Exports */
    DumpObjHeaderSection ("Exports", H.ExportOffs, H.ExportSize);

    /* Debug symbols */
    DumpObjHeaderSection ("Debug symbols", H.DbgSymOffs, H.DbgSymSize);

    /* Line infos */
    DumpObjHeaderSection ("Line infos", H.LineInfoOffs, H.LineInfoSize);

    /* String pool */
    DumpObjHeaderSection ("String pool", H.StrPoolOffs, H.StrPoolSize);
}



void DumpObjOptions (FILE* F, unsigned long Offset)
/* Dump the file options */
{
    ObjHeader  H;
    Collection StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned   Count;
    unsigned   I;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the options */
    FileSetPos (F, Offset + H.OptionOffs);

    /* Output a header */
    printf ("  Options:\n");

    /* Read the number of options and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all options */
    for (I = 0; I < Count; ++I) {

    	const char*   ArgStr;
    	unsigned      ArgLen;

    	/* Read the type of the option and the value */
    	unsigned char Type = Read8 (F);
        unsigned long Val  = ReadVar (F);

       	/* Get the type of the argument */
    	unsigned char ArgType = Type & OPT_ARGMASK;

    	/* Determine which option follows */
    	const char* TypeDesc;
    	switch (Type) {
       	    case OPT_COMMENT:  	TypeDesc = "OPT_COMMENT";	break;
    	    case OPT_AUTHOR:  	TypeDesc = "OPT_AUTHOR";	break;
    	    case OPT_TRANSLATOR:TypeDesc = "OPT_TRANSLATOR";	break;
    	    case OPT_COMPILER:	TypeDesc = "OPT_COMPILER";	break;
    	    case OPT_OS:      	TypeDesc = "OPT_OS";		break;
    	    case OPT_DATETIME:	TypeDesc = "OPT_DATETIME";	break;
    	    default:	      	TypeDesc = "OPT_UNKNOWN";	break;
    	}

    	/* Print the header */
    	printf ("    Index:%27u\n", I);

    	/* Print the data */
    	printf ("      Type:%22s0x%02X  (%s)\n", "", Type, TypeDesc);
    	switch (ArgType) {

    	    case OPT_ARGSTR:
    	     	ArgStr = GetString (&StrPool, Val);
    	    	ArgLen = strlen (ArgStr);
    	     	printf ("      Data:%*s\"%s\"\n", (int)(24-ArgLen), "", ArgStr);
    	     	break;

    	    case OPT_ARGNUM:
    	     	printf ("      Data:%26lu", Val);
    		if (Type == OPT_DATETIME) {
    		    /* Print the time as a string */
    		    printf ("  (%s)", TimeToStr (Val));
    		}
		printf ("\n");
	     	break;

	    default:
	     	/* Unknown argument type. This means that we cannot determine
	     	 * the option length, so we cannot proceed.
	     	 */
	     	Error ("Unknown option type: 0x%02X", Type);
	     	break;
	}
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjFiles (FILE* F, unsigned long Offset)
/* Dump the source files */
{
    ObjHeader  H;
    Collection StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned   Count;
    unsigned   I;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the source files */
    FileSetPos (F, Offset + H.FileOffs);

    /* Output a header */
    printf ("  Files:\n");

    /* Read the number of files and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all files */
    for (I = 0; I < Count; ++I) {

	/* Read the data for one file */
       	const char*   Name  = GetString (&StrPool, ReadVar (F));
	unsigned long MTime = Read32 (F);
	unsigned long Size  = Read32 (F);
	unsigned      Len   = strlen (Name);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
	printf ("      Name:%*s\"%s\"\n", (int)(24-Len), "", Name);
       	printf ("      Size:%26lu\n", Size);
	printf ("      Modification time:%13lu  (%s)\n", MTime, TimeToStr (MTime));
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjSegments (FILE* F, unsigned long Offset)
/* Dump the segments in the object file */
{
    ObjHeader  H;
    Collection StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned   Count;
    unsigned   I;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the segments */
    FileSetPos (F, Offset + H.SegOffs);

    /* Output a header */
    printf ("  Segments:\n");

    /* Read the number of segments and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all segments */
    for (I = 0; I < Count; ++I) {

	/* Read the data for one segments */
        unsigned long DataSize  = Read32 (F);
        unsigned long NextSeg   = ftell (F) + DataSize;
       	const char*   Name      = GetString (&StrPool, ReadVar (F));
	unsigned      Len       = strlen (Name);
	unsigned long Size      = Read32 (F);
	unsigned      Align     = (1U << Read8 (F));
       	unsigned char AddrSize  = Read8 (F);
        unsigned long FragCount = ReadVar (F);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
	printf ("      Name:%*s\"%s\"\n", (int)(24-Len), "", Name);
       	printf ("      Size:%26lu\n", Size);
	printf ("      Alignment:%21u\n", Align);
	printf ("      Address size:%14s0x%02X  (%s)\n", "", AddrSize,
                AddrSizeToStr (AddrSize));
       	printf ("      Fragment count:%16lu\n", FragCount);

        /* Seek to the end of the segment data (start of next) */
        FileSetPos (F, NextSeg);
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjImports (FILE* F, unsigned long Offset)
/* Dump the imports in the object file */
{
    ObjHeader  H;
    Collection StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned   Count;
    unsigned   I;
    FilePos    Pos;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the imports */
    FileSetPos (F, Offset + H.ImportOffs);

    /* Output a header */
    printf ("  Imports:\n");

    /* Read the number of imports and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all imports */
    for (I = 0; I < Count; ++I) {

       	/* Read the data for one import */
       	unsigned char AddrSize = Read8 (F);
       	const char*   Name     = GetString (&StrPool, ReadVar (F));
	unsigned      Len      = strlen (Name);
	ReadFilePos (F, &Pos);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
	printf ("      Address size:%14s0x%02X  (%s)\n", "", AddrSize,
                AddrSizeToStr (AddrSize));
	printf ("      Name:%*s\"%s\"\n", (int)(24-Len), "", Name);
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjExports (FILE* F, unsigned long Offset)
/* Dump the exports in the object file */
{
    ObjHeader 	H;
    Collection  StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned   	Count;
    unsigned   	I;
    FilePos    	Pos;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the exports */
    FileSetPos (F, Offset + H.ExportOffs);

    /* Output a header */
    printf ("  Exports:\n");

    /* Read the number of exports and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all exports */
    for (I = 0; I < Count; ++I) {

	unsigned long 	Value = 0;
	int    		HaveValue;
	unsigned char	ConDes [CD_TYPE_COUNT];
       	const char*    	Name;
	unsigned	Len;


       	/* Read the data for one export */
       	unsigned char Type = Read8 (F);
        unsigned char AddrSize = Read8 (F);
	ReadData (F, ConDes, GET_EXP_CONDES_COUNT (Type));
       	Name  = GetString (&StrPool, ReadVar (F));
	Len   = strlen (Name);
       	if (IS_EXP_EXPR (Type)) {
	    SkipExpr (F);
	    HaveValue = 0;
	} else {
	    Value = Read32 (F);
	    HaveValue = 1;
	}
	ReadFilePos (F, &Pos);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
       	printf ("      Type:%22s0x%02X  (%s)\n", "", Type, GetExportFlags (Type, ConDes));
	printf ("      Address size:%14s0x%02X  (%s)\n", "", AddrSize,
                AddrSizeToStr (AddrSize));
	printf ("      Name:%*s\"%s\"\n", (int)(24-Len), "", Name);
	if (HaveValue) {
	    printf ("      Value:%15s0x%08lX  (%lu)\n", "", Value, Value);
	}
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjDbgSyms (FILE* F, unsigned long Offset)
/* Dump the debug symbols from an object file */
{
    ObjHeader   H;
    Collection  StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned    Count;
    unsigned    I;
    FilePos     Pos;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the debug syms */
    FileSetPos (F, Offset + H.DbgSymOffs);

    /* Output a header */
    printf ("  Debug symbols:\n");

    /* Check if the object file was compiled with debug info */
    if ((H.Flags & OBJ_FLAGS_DBGINFO) == 0) {
	/* Print that there no debug symbols and bail out */
	printf ("    Count:%27u\n", 0);
	return;
    }

    /* Read the number of exports and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all debug symbols */
    for (I = 0; I < Count; ++I) {

	unsigned long 	Value = 0;
	int 	      	HaveValue;

       	/* Read the data for one symbol */
       	unsigned char Type     = Read8 (F);
        unsigned char AddrSize = Read8 (F);
       	const char*   Name     = GetString (&StrPool, ReadVar (F));
	unsigned      Len      = strlen (Name);
	if (IS_EXP_EXPR (Type)) {
	    SkipExpr (F);
	    HaveValue = 0;
	} else {
	    Value = Read32 (F);
	    HaveValue = 1;
	}
	ReadFilePos (F, &Pos);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
       	printf ("      Type:%22s0x%02X  (%s)\n", "", Type, GetExportFlags (Type, 0));
	printf ("      Address size:%14s0x%02X  (%s)\n", "", AddrSize,
                AddrSizeToStr (AddrSize));
	printf ("      Name:%*s\"%s\"\n", (int)(24-Len), "", Name);
	if (HaveValue) {
	    printf ("      Value:%15s0x%08lX  (%lu)\n", "", Value, Value);
	}
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjLineInfo (FILE* F, unsigned long Offset)
/* Dump the line info from an object file */
{
    ObjHeader   H;
    Collection  StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned    Count;
    unsigned    I;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of line infos */
    FileSetPos (F, Offset + H.LineInfoOffs);

    /* Output a header */
    printf ("  Line info:\n");

    /* Check if the object file was compiled with debug info */
    if ((H.Flags & OBJ_FLAGS_DBGINFO) == 0) {
	/* Print that there no line infos and bail out */
	printf ("    Count:%27u\n", 0);
	return;
    }

    /* Read the number of line infos and print it */
    Count = ReadVar (F);
    printf ("    Count:%27u\n", Count);

    /* Read and print all line infos */
    for (I = 0; I < Count; ++I) {

	FilePos   Pos;

       	/* Read one line info */
	ReadFilePos (F, &Pos);

	/* Print the header */
	printf ("    Index:%27u\n", I);

	/* Print the data */
       	printf ("      Line:%26lu\n", Pos.Line);
       	printf ("      Col:%27u\n", Pos.Col);
       	printf ("      Name:%26u\n", Pos.Name);
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



void DumpObjSegSize (FILE* F, unsigned long Offset)
/* Dump the sizes of the segment in the object file */
{
    ObjHeader   H;
    Collection  StrPool = AUTO_COLLECTION_INITIALIZER;
    unsigned    Count;

    /* Seek to the header position and read the header */
    FileSetPos (F, Offset);
    ReadObjHeader (F, &H);

    /* Seek to the start of the string pool and read it */
    FileSetPos (F, Offset + H.StrPoolOffs);
    ReadStrPool (F, &StrPool);

    /* Seek to the start of the segments */
    FileSetPos (F, Offset + H.SegOffs);

    /* Output a header */
    printf ("  Segment sizes:\n");

    /* Read the number of segments */
    Count = ReadVar (F);

    /* Read and print the sizes of all segments */
    while (Count--) {

       	/* Read the data for one segments */
        unsigned long DataSize = Read32 (F);
        unsigned long NextSeg  = ftell (F) + DataSize;
	const char*   Name     = GetString (&StrPool, ReadVar (F));
	unsigned      Len      = strlen (Name);
	unsigned long Size     = Read32 (F);

        /* Skip alignment, type and fragment count */
        (void) Read8 (F);
        (void) Read8 (F);
        (void) ReadVar (F);

	/* Print the size for this segment */
	printf ("    %s:%*s%6lu\n", Name, (int)(24-Len), "", Size);

        /* Seek to the end of the segment data (start of next) */
        FileSetPos (F, NextSeg);
    }

    /* Destroy the string pool */
    DestroyStrPool (&StrPool);
}



