/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  Mapsym DIP loading/unloading of symbolic information.
*
****************************************************************************/


#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include "msym.h"
#include "exedos.h"
#include "exeos2.h"
#include "exeflat.h"


/* Implementation notes:
 *
 * - Symbol files may contain multiple maps. This is not supported because
 *   I've never seen such a .sym file and I don't even know how to produce one.
 */

#if defined( __WATCOMC__ ) && defined( __386__ )

/* WD looks for this symbol to determine module bitness */
int __nullarea;
#pragma aux __nullarea "*";

#endif

static struct {
    unsigned long       fpos;
    unsigned            len;
    unsigned            off;
    unsigned_8          data[4096];
} Buff;

unsigned long BSeek( dig_fhandle h, unsigned long p, dig_seek w )
{
    unsigned long       bpos;
    unsigned long       npos;

    bpos = Buff.fpos - Buff.len;
    switch( w ) {
    case DIG_END:
        return( -1UL ); /* unsupported */
    case DIG_CUR:
        npos = bpos + p + Buff.off;
        break;
    case DIG_ORG:
        npos = p;
        break;
    }
    if( npos >= bpos && npos < (bpos+Buff.len) ) {
        Buff.off = npos - bpos;
        return( npos );
    }
    Buff.fpos = DCSeek( h, npos, DIG_ORG );
    Buff.off = 0;
    Buff.len = 0;
    return( Buff.fpos );
}

unsigned BRead( dig_fhandle h, void *b, unsigned s )
{
    unsigned    got;
    unsigned    want;

    if( s > sizeof( Buff.data ) ) {
        Buff.fpos = DCSeek( h, (int)Buff.fpos + (int)Buff.off - (int)Buff.len, DIG_ORG );
        Buff.len = 0;
        Buff.off = 0;
        if( Buff.fpos == -1UL )
            return( 0 );
        got = DCRead( h, b, s );
        Buff.fpos += got;
        return( got );
    }
    want = s;
    got = Buff.len - Buff.off;
    if( got > want ) got = want;
    memcpy( b, &Buff.data[Buff.off], got );
    Buff.off += got;
    want -= got;
    if( want > 0 ) {
        Buff.len = DCRead( h, &Buff.data[0], sizeof( Buff.data ) );
        if( Buff.len == (unsigned)-1 ) {
            Buff.fpos = -1UL;
            Buff.off = 0;
            Buff.len = 0;
            return( (unsigned)-1 );
        }
        Buff.fpos += Buff.len;
        b = (unsigned_8 *)b + got;
        memcpy( b, &Buff.data[0], want );
        Buff.off = want;
    }
    return( s );
}

#define ROUND_UP( d, r ) (((d)+(r)-1) & ~((r)-1))

static void *HunkAlloc( imp_image_handle *ii, unsigned size )
{
    msym_hunk   *hunk;
    unsigned    alloc;

    size = ROUND_UP( size, sizeof( void * ) );
    hunk = ii->hunks;
    if( hunk == NULL || size > hunk->left ) {
        alloc = max( HUNK_SIZE, size );
        hunk = DCAlloc( (sizeof( *hunk ) - HUNK_SIZE) + alloc );
        if( hunk == NULL )
            return( NULL );
        hunk->next = ii->hunks;
        ii->hunks = hunk;
        hunk->left = alloc;
    }
    hunk->left -= size;
    return( &hunk->data[ hunk->left ] );
}

static void ImpUnloadInfo( imp_image_handle *ii )
{
    msym_hunk   *curr;
    msym_hunk   *next;

    for( curr = ii->hunks; curr != NULL; curr = next ) {
        next = curr->next;
        DCFree( curr );
    }
    ii->hunks = NULL;
}

msym_block *FindAddrBlock( imp_image_handle *ii, addr_ptr addr )
{
    msym_block  *b;

    for( b = ii->addr; b != NULL; b = b->next ) {
        if( SameAddrSpace( b->start, addr )
          && b->start.offset <= addr.offset
          && (b->start.offset+b->len) > addr.offset ) {
            return( b );
        }
    }
    return( NULL );
}

static dip_status AddName( imp_image_handle *ii, unsigned len, char *name )
{
    char        *start;
    char        *end;

    end = NULL;
    start = name;
    for( ;; ) {
        if( len == 0 )
            break;
        switch( *name ) {
        case ':':
        case '\\':
        case '/':
            start = name + 1;
            end = NULL;
            break;
        case '.':
            end = name;
            break;
        }
        ++name;
        --len;
    }
    if( end == NULL )
        end = name;
    ii->len = end - start;
    ii->name = HunkAlloc( ii, ii->len );
    if( ii->name == NULL )
        return( DS_ERR | DS_NO_MEM );
    memcpy( ii->name, start, ii->len );
    return( DS_OK );
}

static dip_status AddBlock( imp_image_handle *ii, addr_seg seg, addr_off off,
                        unsigned_32 len, unsigned_8 code )
{
    msym_block  *new;

    new = HunkAlloc( ii, sizeof( *new ) );
    if( new == NULL )
        return( DS_ERR | DS_NO_MEM );
    new->start.segment = seg;
    new->start.offset = off;
    new->len = len;
    new->code = code;
    new->next = ii->addr;
    ii->addr = new;
    return( DS_OK );
}

static dip_status AddSymbol( imp_image_handle *ii, addr_seg seg, addr_off off,
                        unsigned len, char *name )
{
    msym_sym    *new;

    new = HunkAlloc( ii, (sizeof( *new ) - 1) + len );
    if( new == NULL )
        return( DS_ERR | DS_NO_MEM );
    new->addr.segment = seg;
    new->addr.offset = off;
    new->len = len;
    memcpy( new->name, name, len );
    new->next = ii->gbl;
    ii->gbl = new;
    return( DS_OK );
}

/* Heuristics to determine whether given file is a MAPSYM .sym file */
static dip_status CheckSymFile( dig_fhandle h )
{
    sym_endmap          end_map;
    unsigned long       pos;

    /* seek to the end, read and check end map record */
    pos = DCSeek( h, -(long)sizeof( end_map ), DIG_END );
    if( pos == -1UL ) {
        return( DS_ERR | DS_FSEEK_FAILED );
    }
    /* the endmap record must be 16-byte aligned */
    if( pos % 16 ) {
        return( DS_FAIL );
    }
    if( DCRead( h, &end_map, sizeof( end_map ) ) != sizeof( end_map ) ) {
        return( DS_ERR | DS_FREAD_FAILED );
    }
    if( end_map.zero != 0 ) {
        return( DS_FAIL );
    }

    /* Check .sym file version. The version number seems to correspond to
     * the linker version. Versions 3.10, 4.0, 5.10, 5.11 have been seen.
     * Version 5.1 seems to be identical to 4.0 with added support for
     * 32-bit symbols.
     */
    if( (end_map.major_ver < 3) || (end_map.major_ver > 5)
        || (end_map.minor_ver > 11) ) {
        return( DS_FAIL );
    }

    /* looks like the right sort of .sym file */
    return( DS_OK );
}

/* Read a Pascal style string - limited to 255 chars max length */
static dip_status ReadString( dig_fhandle h, char *buf, unsigned *len_ptr )
{
    unsigned_8  str_len;

    if( BRead( h, &str_len, sizeof( str_len ) ) != sizeof( str_len ) ) {
        return( DS_ERR | DS_FREAD_FAILED );
    }
    if( BRead( h, buf, str_len ) != str_len ) {
        return( DS_ERR | DS_FREAD_FAILED );
    }
    buf[str_len] = '\0';            // NUL terminate string
    if( len_ptr != NULL )
        *len_ptr = str_len;
    return( DS_OK );
}

/* Load symbols for a segment */
static dip_status LoadSymTable( dig_fhandle h, imp_image_handle *ii, int count,
                        unsigned long base_ofs, unsigned_32 table_ofs,
                        addr_seg seg, int big_syms )
{
    dip_status      ds;
    unsigned_16     *sym_tbl;
    size_t          tbl_size;
    sym_symdef      sym;
    sym_symdef_32   sym_32;
    char            name[256];
    unsigned        name_len;
    int             i;

    tbl_size = count * sizeof( unsigned_16 );
    sym_tbl = DCAlloc( tbl_size );
    if( sym_tbl == NULL ) {
        return( DS_ERR | DS_NO_MEM );
    }
    if( BSeek( h, base_ofs + table_ofs, DIG_ORG ) == -1UL ) {
        ds = DS_ERR | DS_FSEEK_FAILED;
        goto done;
    }
    if( BRead( h, sym_tbl, tbl_size ) != tbl_size ) {
        ds = DS_ERR | DS_FREAD_FAILED;
        goto done;
    }

    for( i = 0; i < count; ++i ) {
        if( BSeek( h, base_ofs + sym_tbl[i], DIG_ORG ) == -1UL ) {
            ds = DS_ERR | DS_FSEEK_FAILED;
            goto done;
        }
        if( big_syms ) {
            if( BRead( h, &sym_32, SYM_SYMDEF_32_FIXSIZE ) != SYM_SYMDEF_32_FIXSIZE ) {
                ds = DS_ERR | DS_FREAD_FAILED;
                goto done;
            }
            ds = ReadString( h, name, &name_len );
            if( ds != DS_OK )
                goto done;
            ds = AddSymbol( ii, seg, sym_32.offset, name_len, name );
            if( ds != DS_OK )
                goto done;
        } else {
            if( BRead( h, &sym, SYM_SYMDEF_FIXSIZE ) != SYM_SYMDEF_FIXSIZE ) {
                ds = DS_ERR | DS_FREAD_FAILED;
                goto done;
            }
            ds = ReadString( h, name, &name_len );
            if( ds != DS_OK )
                goto done;
            ds = AddSymbol( ii, seg, sym.offset, name_len, name );
            if( ds != DS_OK )
                goto done;
        }
    }

    // The .sym file doesn't say how big a segment is. Since the symbols are
    // sorted by offset, just use the last offset as segment size. Should be
    // close enough.
    if( big_syms ) {
        ii->addr->len = sym_32.offset;
    } else {
        ii->addr->len = sym.offset;
    }

    ds = DS_OK;

done:
    DCFree( sym_tbl );
    return( ds );
}

/* Load all segments for a map */
static dip_status LoadSegments( dig_fhandle h, imp_image_handle *ii, int count )
{
    dip_status      ds;
    sym_segdef      seg;
    char            name[256];
    unsigned        name_len;
    unsigned_32     seg_start;
    int             i;
    int             is_code;

    for( i = 0; i < count; ++i ) {
        seg_start = BSeek( h, 0, DIG_CUR );
        if( seg_start == -1UL ) {
            return( DS_ERR | DS_FSEEK_FAILED );
        }

        if( BRead( h, &seg, SYM_SEGDEF_FIXSIZE ) != SYM_SEGDEF_FIXSIZE ) {
            return( DS_ERR | DS_FREAD_FAILED );
        }
        ds = ReadString( h, name, &name_len );
        if( ds != DS_OK )
            return( ds );

        /* There's no good way to tell whether segment is code or data. Try
         * to guess what a segment is based on its name.
         */
        if( !strcmp( name, "DGROUP" ) )
            is_code = 0;
        else
            is_code = 1;
        ds = AddBlock( ii, seg.load_addr, 0, 0, is_code );
        if( ds != DS_OK )
            return( ds );

        LoadSymTable( h, ii, seg.num_syms, seg_start, seg.sym_tab_ofs,
            seg.load_addr, (seg.sym_type & SYM_FLAG_32BIT) != 0 );

        if( BSeek( h, SYM_PTR_TO_OFS( seg.next_ptr ), DIG_ORG ) == -1UL ) {
            return( DS_ERR | DS_FSEEK_FAILED );
        }
    }
    return( DS_OK );
}

/* Load all symbols in a .sym file */
static dip_status LoadSymFile( dig_fhandle h, imp_image_handle *ii )
{
    dip_status      ds;
    sym_mapdef      map;
    unsigned long   map_start;
    char            name[256];
    unsigned        name_len;

    map_start = BSeek( h, 0, DIG_ORG );
    if( map_start == -1UL ) {
        return( DS_ERR | DS_FSEEK_FAILED );
    }

    /* Read the first map and use its name as the module name */
    if( BRead( h, &map, SYM_MAPDEF_FIXSIZE ) != SYM_MAPDEF_FIXSIZE ) {
        return( DS_ERR | DS_FREAD_FAILED );
    }

    ds = ReadString( h, name, &name_len );
    if( ds != DS_OK )
        return( ds );

    ds = AddName( ii, name_len, name );
    if( ds != DS_OK )
        return( ds );

    if( BSeek( h, SYM_PTR_TO_OFS( map.seg_ptr ), DIG_ORG ) == -1UL ) {
        return( DS_ERR | DS_FSEEK_FAILED );
    }
    ds = LoadSegments( h, ii, map.num_segs );
    if( ds != DS_OK )
        return( ds );

    return( DS_OK );
}

dip_status DIPENTRY DIPImpLoadInfo( dig_fhandle h, imp_image_handle *ii )
{
    dip_status  ds;

    if( h == DIG_NIL_HANDLE )
        return( DS_ERR | DS_FOPEN_FAILED );

    ii->gbl = NULL;
    ii->addr = NULL;
    ii->name = NULL;
    ii->hunks = NULL;
    Buff.len = 0;
    Buff.off = 0;

    ds = CheckSymFile( h );
    if( ds == DS_OK )
        ds = LoadSymFile( h, ii );

    if( ds != DS_OK ) {
        DCStatus( ds );
        /* clean up any allocations */
        ImpUnloadInfo( ii );
        return( ds );
    }
    DCClose( h );
    return( DS_OK );
}

void DIPENTRY DIPImpMapInfo( imp_image_handle *ii, void *d )
{
    msym_block  *b;
    msym_sym    *s;

    for( s = ii->gbl; s != NULL; s = s->next ) {
        DCMapAddr( &s->addr, d );
    }
    for( b = ii->addr; b != NULL; b = b->next ) {
        DCMapAddr( &b->start, d );
    }
}

void DIPENTRY DIPImpUnloadInfo( imp_image_handle *ii )
{
    ImpUnloadInfo( ii );
}
