/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * 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 APPLE HEREBY DISCLAIMS 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.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
#include <stdio.h>
#include <string.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/reloc.h>
#include <mach-o/i860/reloc.h>
#include "stuff/symbol.h"
#include "stuff/bytesex.h"
#include "otool.h"
#include "../as/i860-opcode.h"

static void i860_dump_operands(
    unsigned long opcode,
    char *format,
    unsigned long addr,
    unsigned long sect_addr,
    struct relocation_info *relocs,
    unsigned long nrelocs,
    nlist_t *symbols,
    unsigned long nsymbols,
    struct symbol *sorted_symbols,
    unsigned long nsorted_symbols,
    char *strings,
    unsigned long strings_size,
    enum bool verbose);

static void i860_dump_addr(
    unsigned long addr_field,
    int format,
    long addr,
    unsigned long sect_addr,
    struct relocation_info *relocs,
    unsigned long nrelocs,
    nlist_t *symbols,
    unsigned long nsymbols,
    struct symbol *sorted_symbols,
    unsigned long nsorted_symbols,
    char *strings,
    unsigned long strings_size,
    enum bool verbose);

static enum bool i860_print_symbol(
    unsigned long value,
    struct relocation_info *rp,
    nlist_t *symbols,
    unsigned long nsymbols,
    struct symbol *sorted_symbols,
    unsigned long nsorted_symbols,
    char *strings,
    unsigned long strings_size,
    enum bool verbose);

/*
 * Disassemble 1 instruction and return the length of the disassembled 
 * piece in bytes.
 */
unsigned long
i860_disassemble(
char *sect,
unsigned long left,
unsigned long addr,
unsigned long sect_addr,
enum byte_sex object_byte_sex,
struct relocation_info *relocs,
unsigned long nrelocs,
nlist_t *symbols,
unsigned long nsymbols,
struct symbol *sorted_symbols,
unsigned long nsorted_symbols,
char *strings,
unsigned long strings_size,
enum bool verbose)
{
    enum byte_sex host_byte_sex;
    enum bool swapped;
    unsigned long opcode;
    int isdual;
    unsigned long i;
    struct i860_opcode *op;

	host_byte_sex = get_host_byte_sex();
	swapped = host_byte_sex != object_byte_sex;

	if(left < sizeof(unsigned long)){
	   if(left != 0){
		memcpy(&opcode, sect, left);
		if(swapped)
		    opcode = SWAP_LONG(opcode);
		printf(".long\t0x%08x\n", (unsigned int)opcode);
	   }
	   printf("(end of section)\n");
	   return(left);
	}

	memcpy(&opcode, sect, sizeof(unsigned long));
	if(swapped)
	    opcode = SWAP_LONG(opcode);

	/*
	 * The pad opcode, 0, is chosen as an illegal insn to fault if
	 * executed
	 */
	if(opcode == 0){
	    printf("| Padded to i860 section boundary\n");
	    return(4);
	}

	isdual = 0;
	/*
	 * See if this is a dual insn mode opcode.
	 * Turn off the dual mode bit and print a d. if appropriate.
	 */
	if((opcode & OP_PREFIX_MASK) == PREFIX_FPU || 
	   opcode == (OP_FNOP|DUAL_INSN_MODE_BIT)){
	    if(opcode & DUAL_INSN_MODE_BIT){
		opcode &= ~DUAL_INSN_MODE_BIT;
		isdual = 1;
	    }
	}
	/*
	 * Search the instruction table for a match for this opcode.
	 * We use a linear search because it's easy, uses the 
	 * assembler insn tables, and I'm so lazy....
	 * Feel free to recode with whizzy hashes and such.
	 */
	op = (struct i860_opcode *)i860_opcodes;
	for(i = 0; i < NUMOPCODES; i++, op++){
	    if((opcode & op->mask) == op->match){
		if(isdual)
		    printf("d.%-12s\t", op->name);
		else
		    printf("%-12s\t", op->name);
		i860_dump_operands(opcode, (char *)op->args, addr, sect_addr,
			relocs, nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		return(sizeof(unsigned long));
	    }
	}

	/* Didn't find the opcode.  Dump it as a .long directive. */
	/* Build it as a little-endian insn, in a format to match asm */
	printf(".long\t0x%08x\n", (unsigned int)opcode);
	return(sizeof(unsigned long));
}

/* 32 possible valuse, of which 6 are actually used. */
static char *i860_controlregs[] = {"fir", "psr", "dirbase", "db", "fsr", "epsr",
				   "?","?","?","?","?","?",
				   "?","?","?","?","?","?",
				   "?","?","?","?","?","?",
				   "?","?","?","?","?","?"};

static
void
i860_dump_operands(
unsigned long opcode,
char *format,
unsigned long addr,
unsigned long sect_addr,
struct relocation_info *relocs,
unsigned long nrelocs,
nlist_t *symbols,
unsigned long nsymbols,
struct symbol *sorted_symbols,
unsigned long nsorted_symbols,
char *strings,
unsigned long strings_size,
enum bool verbose)
{
    unsigned long addr_field;
	
	while(*format != '\0'){
	    switch(*format){
	    case '1': /* rs1 register, bits 11-15 of insn */
		printf("r%lu", GET_RS1(opcode));
		break;
		    
	    case '2': /* rs2 register, bits 21-25 of insn */
		printf("r%lu", GET_RS2(opcode));
		break;
		    
	    case 'd': /* rd register, bits 16-20 of insn */
		printf("r%lu", GET_RD(opcode));
		break;
	    
	    case 'E':	
	    case 'e': /* frs1 floating point register, bits 11-15 of insn */
		printf("f%lu", GET_RS1(opcode));
		break;
	    
	    case 'F':	
	    case 'f': /* frs2 floating point register, bits 21-25 of insn */
		printf("f%lu", GET_RS2(opcode));
		break;
	    
	    case 'H':
	    case 'G':	
	    case 'g': /* frsd floating point register, bits 16-20 of insn */ 
		printf("f%lu", GET_RD(opcode));
		break;
		    
	    case 'I': /* 16 bit High portion of address, I860_RELOC_HIGH */
	    case 'J': /* 16 bit High portion of addr requiring adjustment */
		addr_field = opcode & 0xFFFF;
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'i': /* 16 bit byte address low half */
		addr_field = (opcode & 0xFFFF);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'j': /* 16 bit short address, I860_RELOC_LOW1 */
		addr_field = (opcode & 0xFFFE);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'k': /* 16 bit word/int address low half, I860_RELOC_LOW2 */
		addr_field = (opcode & 0xFFFC);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'l': /* 16 bit 8-byte address (double) low half */
		addr_field = (opcode & 0xFFF8);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'm': /* 16 bit 16-byte address (quad) low half */
		addr_field = (opcode & 0xFFF0);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'n': /* 16 bit byte aligned low half, split fields */
		addr_field = ((opcode >> 5) & 0xF800) | (opcode & 0x7FF);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'o': /* 16 bit short aligned low half, split fields */
		addr_field = ((opcode >> 5) & 0xF800) | (opcode & 0x7FE);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'p': /* 16 bit int/word aligned low half, split fields */
		addr_field = ((opcode >> 5) & 0xF800) | (opcode & 0x7FC);
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'K': /* 26 bit branch displacement */
		addr_field = opcode & 0x3FFFFFF;
		if(addr_field & 0x02000000)	/* MSB set? */
		    addr_field |= 0xFC000000; 	/* Sign extend */
		addr_field <<= 2;	/* Convert to byte addr */
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;
		    
	    case 'L': /* 16 bit split branch displacement */
		addr_field = ((opcode >> 5) & 0xF800) | (opcode & 0x7FF);
		if(addr_field & 0x8000)		/* MSB set? */
		    addr_field |= 0xFFFF0000; 	/* Sign extend */
		addr_field <<= 2;	/* Convert to byte addr */
		i860_dump_addr(addr_field, *format, addr, sect_addr, relocs,
			nrelocs, symbols, nsymbols, sorted_symbols,
			nsorted_symbols, strings, strings_size, verbose);
		break;

	    case 'D': /* constant for shift opcode */	
		printf("%lu", opcode & 0xFFFF);
		break;
		    
	    case 'B': /* 5 bit immediate, for bte and btne insn */
		printf("%lu", GET_RS1(opcode));
		break;
		    
	    case 'C': /* Control Register */
		printf(i860_controlregs[GET_RS2(opcode)]);
		break;
		    
	    default:
		printf("%c", *format);
		break;
	    }
	    ++format;
	}
	printf("\n");
}

static
void
i860_dump_addr(
unsigned long addr_field,
int format,
long addr,
unsigned long sect_addr,
struct relocation_info *relocs,
unsigned long nrelocs,
nlist_t *symbols,
unsigned long nsymbols,
struct symbol *sorted_symbols,
unsigned long nsorted_symbols,
char *strings,
unsigned long strings_size,
enum bool verbose)
{
    unsigned long i;
    struct relocation_info *rp, *pairp;
    struct scattered_relocation_info *sreloc;
    char *prefix;
	
	rp = NULL;
	pairp = NULL;
	if(nrelocs){
	    for(i = 0; i < nrelocs; i++){
		if(((relocs[i].r_address) & R_SCATTERED) != 0){
		    sreloc = (struct scattered_relocation_info *)(relocs + i);
		    if(sreloc->r_type == I860_RELOC_PAIR){
			fprintf(stderr, "Stray I860_RELOC_PAIR relocation "
				"entry %lu\n", i);
			continue;
		    }
		    if(sreloc->r_type == I860_RELOC_HIGH ||
		       sreloc->r_type == I860_RELOC_HIGHADJ ||
		       sreloc->r_type == I860_RELOC_SECTDIFF){
			if(i+1 >= nrelocs ||
			   relocs[i+1].r_type != I860_RELOC_PAIR){
				fprintf(stderr, "No I860_RELOC_PAIR relocation "
					"entry after entry %lu\n", i);
			}
			else{
			    if(((relocs[i+1].r_address) & R_SCATTERED) != 0){
				sreloc = (struct scattered_relocation_info *)
					 (relocs + i + 1);
				if(sreloc->r_type != I860_RELOC_PAIR)
				    fprintf(stderr, "No I860_RELOC_PAIR "
					    "relocation entry after entry "
					    "%lu\n", i);
			    }
			    else if(relocs[i+1].r_type != I860_RELOC_PAIR){
				fprintf(stderr, "No I860_RELOC_PAIR relocation "
					"entry after entry %lu\n", i);
			    }
			    i++;
			    continue;
			}
		    }
		}
		if(relocs[i].r_type == I860_RELOC_PAIR){
		    fprintf(stderr, "Stray I860_RELOC_PAIR relocation entry "
			    "%lu\n", i);
		    continue;
		}
		if((unsigned long)relocs[i].r_address == addr - sect_addr){
		    rp = &relocs[i];
		    if(rp->r_type == I860_RELOC_HIGH ||
		       rp->r_type == I860_RELOC_HIGHADJ ||
		       rp->r_type == I860_RELOC_SECTDIFF){
			if(i+1 < nrelocs){
			    pairp = &rp[1];
			    if(pairp->r_type != I860_RELOC_PAIR){
				fprintf(stderr, "No I860_RELOC_PAIR relocation "
					"entry after entry %lu\n", i);
				rp = NULL;
				pairp = NULL;
				continue;
			    }
			}
		    }
		    break;
		}
		if(relocs[i].r_type == I860_RELOC_HIGH ||
		   relocs[i].r_type == I860_RELOC_HIGHADJ ||
		   relocs[i].r_type == I860_RELOC_SECTDIFF){
		    if(i+1 >= nrelocs ||
		       relocs[i+1].r_type != I860_RELOC_PAIR){
			    fprintf(stderr, "No I860_RELOC_PAIR relocation "
				    "entry after entry %lu\n", i);
		    }
		    else
			i++;
		}
	    }
	}

	/* Guess a prefix code for the immediate value */
	prefix = NULL;
	if((rp != NULL && rp->r_type == I860_RELOC_HIGH) || format == 'I')
	    prefix = "h%";
	else if((rp != NULL && rp->r_type == I860_RELOC_HIGHADJ) ||
		format == 'J' )
	    prefix = "ha%";
	else if(rp != NULL && rp->r_type >= I860_RELOC_LOW0 &&
			      rp->r_type <= I860_RELOC_SPLIT2){
	    if(rp->r_pcrel == 0)	/* Don't use for bte insns */
		prefix = "l%";
	}
	if(rp != NULL && (rp->r_type == I860_RELOC_HIGH ||
			  rp->r_type == I860_RELOC_HIGHADJ)){
	    if(pairp->r_type == I860_RELOC_PAIR){
		if(rp->r_type == I860_RELOC_HIGHADJ)

		    if(pairp->r_address & 0x8000)
			addr_field = (addr_field << 16) +
				     (0xffff0000 | (pairp->r_address & 0xffff));
		    else
			addr_field = (addr_field << 16) +
				     (pairp->r_address & 0xffff);
		else
		    addr_field = (addr_field << 16) |
				 (pairp->r_address & 0xffff);
	    }
	}	
	if(prefix != NULL)
	    printf("%s", prefix);

	if(format == 'K' || format == 'L'){ /* branch displacement */
	    if(i860_print_symbol(addr + 4 + ((long)addr_field), rp,
		     symbols, nsymbols, sorted_symbols, nsorted_symbols,
		     strings, strings_size, verbose) == TRUE)
		return;
	    printf(".%+ld", (long)(addr_field + 4));
	    return;
	}
	if(i860_print_symbol(addr_field, rp, symbols, nsymbols,
			     sorted_symbols, nsorted_symbols, strings,
			     strings_size, verbose) == TRUE)	
	    return;

	/* we can't find anything else to do with it. */
	printf("0x%x", (unsigned int)addr_field);
}

/*
 * i860_print_symbol prints a symbol name for the addr and relocation entry
 * if a symbol exist with the same address.  Nothing else is printed, no
 * whitespace, no newline.  If it prints something then it returns TRUE, else
 * it returns FALSE.
 */
static
enum bool
i860_print_symbol(
unsigned long value,
struct relocation_info *rp,
nlist_t *symbols,
unsigned long nsymbols,
struct symbol *sorted_symbols,
unsigned long nsorted_symbols,
char *strings,
unsigned long strings_size,
enum bool verbose)
{
    long high, low, mid;

	if(verbose == FALSE)
	    return(FALSE);

	if(rp != NULL){
	    if(rp->r_extern &&
	       rp->r_symbolnum < nsymbols){
		if(value != 0)
		    printf("%s+0x%x", strings +
			   symbols[rp->r_symbolnum].n_un.n_strx,
			   (unsigned int)value);
		else
		    printf("%s",strings + symbols[rp->r_symbolnum].n_un.n_strx);
		return(TRUE);
	    }
	}

	low = 0;
	high = nsorted_symbols - 1;
	mid = (high - low) / 2;
	while(high >= low){
	    if(sorted_symbols[mid].nl.n_value == value){
		printf("%s", sorted_symbols[mid].name);
		return(TRUE);
	    }
	    if(sorted_symbols[mid].nl.n_value > value){
		high = mid - 1;
		mid = (high + low) / 2;
	    }
	    else{
		low = mid + 1;
		mid = (high + low) / 2;
	    }
	}
	return(FALSE);
}
