/*	NeXT disassembly tool
	Copyright (C) 1989 by Bill Spitzak.
	See Copyright notice in makefile
*/

/* $Log:	dis.m,v $
Revision 1.3  94/08/24  22:42:48  ediger
changed a comment

Revision 1.2  94/08/22  22:32:31  ediger
added some comments

Revision 1.1  94/05/31  22:06:37  ediger
Initial revision

 *  */

static char rcsident[] = "$Id: dis.m,v 1.3 94/08/24 22:42:48 ediger Exp Locker: ediger $";

#include <dis.h>
#include <sys/stat.h>

#import <MappedFile.h>

/*	Disassembly switches: */
int  decimalrange = 127;  /* -d */
flag globalscode = FALSE; /* -g */
int  printpass = 100;     /* -p n */
flag xlabels = FALSE;     /* -x */
flag onlysymbols = FALSE; /* -t */
flag noextern = FALSE;    /* -e */

flag printit;

/* fwd declarations of functions in this file */
long filelength(int);
void endline(void);
int  main(int, char **);

long 
filelength(int handle)
{
	struct stat sStat;

	assert(handle >= 0);

	if (fstat(handle, &sStat) < 0)
		perror("finding file size");

	return sStat.st_size;
}

extern int   optind;
extern char *optarg;

void usage(void);

int 
main(int argc, char **argv)
{
	int         iOption;
	MappedFile *oMappedFile;

	while ((iOption = getopt(argc, argv, "d:egGtxp:")) != EOF)
	{
		switch (iOption)
		{
		case 'd':
			decimalrange = atoi(optarg);
			break;
		case 'e':
			noextern = TRUE;
			break;
		case 'g':
			globalscode = TRUE;
			break;
		case 'G':
			globalscode = 2;
			break;
		case 'p':
			printpass = atoi(optarg);
			break;
		case 't':
			onlysymbols = TRUE;
			break;
		case 'x':
			xlabels = TRUE;
			break;
		default:
			usage();
			break;
		}
	}

	if (argv[optind] == NULL)
		usage();

	oMappedFile = [[[MappedFile alloc] init] filename:argv[optind]];
	if (oMappedFile == NULL)
	{
		fprintf(stderr, "Failed to memory-map \"%s\"\n", argv[optind]);
		exit(33);
	} else if (![oMappedFile map]) {
		fprintf(stderr, "Failed to memory-map \"%s\": %s\n", argv[optind],
			[oMappedFile errorString]);
		[oMappedFile free];
		exit(33);
	}

	disasmmappedfile([oMappedFile address]);

	/* we could go on to do multiple files here... */

	if (![oMappedFile unmap])
		fprintf(stderr, "Failed to UNmemory-map \"%s\": %s\n", argv[optind],
			[oMappedFile errorString]);
	[oMappedFile free];

	return (0);
}


/* do exactly one, already mapped, object file */
void
disasmmappedfile(char *map)
{
	int     i, j, sectn = 0;
	unsigned startaddress = 1;
	struct segment_command *p;
	struct section *q;
	struct nlist *n;
	int     pass;
	extern int newlabels;
	extern flag incode;
	struct mach_header *header = (struct mach_header *)map;

	check_header(header);

	/*
	 * This next for/switch combo appears to be order of section dependent.
	 * That is, loading the symbol table correctly means having the section
	 * total count correct.
	 */
	p = (struct segment_command *)(header + 1);
	for (i = 0; i < header->ncmds; i++)
	{
		switch (p->cmd)
		{
		case LC_SEGMENT:  /* a segment load command, get all the sections: */
			sectn = set_sections(p);
			break;

		case LC_SYMTAB:	/* load the symbol table */
			load_symbol_table(map, (struct symtab_command *)p, sectn);
			break;

		case LC_THREAD:     /* a thread thing */
		case LC_UNIXTHREAD: /* a unix-thread thing */
			startaddress = thread_prog_counter(p);
			break;

		case LC_LOADFVMLIB:
			/* should pick up the symbols out of this thing */
			break;
		}
		p = (struct segment_command *) ((void *)p + p->cmdsize);
	}

	if (startaddress != 1)
	{
		n = findlabel(startaddress);
		if (n)
			n->n_desc = CODE;
		else
			createlabel(startaddress, 1, CODE, "start");
	}

	/* have to sort the relocation info because getlabel() looks
	 * in the current relocation info table (rtab global) to find
	 * a label for the address passed in */
	sortreltables(map);

	/* do non-printing passes until no more symbols: */
	printit = FALSE;
	for (pass = 0; pass < printpass; pass++)
	{
		newlabels = 0;
		sectn = 0;

		p = (struct segment_command *) (header + 1);

		for (i = 0; i < header->ncmds; i++)
		{
			if (p->cmd == LC_SEGMENT)
			{	/* for each section */

				q = (struct section *)(p + 1);
				for (j = 0; j < p->nsects; q++, j++)
				{
					sectn++;	/* for each segment */

					incode = (sectn == 1);

					disasmblock(p, q, sectn, map);
				}
			}

		    p = (struct segment_command *)((void *)p + p->cmdsize);
		}

		if (!newlabels)
			break;

		printf("| Pass %d, %d new labels\n", pass, newlabels);
	}

	startaddress = 0;
	printit = TRUE;
	sectn = 0;

	if (!onlysymbols)
	{
		p = (struct segment_command *)(header + 1);
		for (i = 0; i < header->ncmds; i++)
		{
			if (p->cmd == LC_SEGMENT)
			{	/* for each section */
				q = (struct section *)(p + 1);
				for (j = 0; j < p->nsects; q++, j++)
				{
					if (j == 0)
						disasmsymbols(startaddress, q->addr, FALSE);

					startaddress = q->addr + q->size;

					sectn++;	/* for each segment */

					incode = sectn == 1;

					if (!strcmp(q->sectname, "__text") ||
					    !strcmp(q->sectname, "__data") ||
					    !strcmp(q->sectname, "__bss"))
					{
						if (!q->size)
							continue;
						startline(q->addr);
						cprint('.');
						sprint(q->sectname + 2);
					} else {
						startline(q->addr);
						fprint(".segment %s; .section %s:",
						   q->segname, q->sectname);
					}

					disasmblock(p, q, sectn, map);
				}
			}

			p = (struct segment_command *)((void *)p + p->cmdsize);
		}
	}

	disasmsymbols(startaddress, 0, TRUE);

	endline();
}

void
disasmsymbols(
	unsigned start,	/* address to start at */
	unsigned end,	/* address to end at */
	int rest		/* ignore end, print all of them */
	)
{
	struct nlist *n;
	extern unsigned symaddress;

	n = firstsymbol(start);

	while (n && (rest || symaddress < end))
	{
		printsymbol(n);
		n = nextsymbol();
	}
}

unsigned int
thread_prog_counter(struct segment_command *spSegmentCmd)
{
	struct thread_command      *spThreadCmd;
	struct thread_state_flavor *spThreadStateFlavor;
	unsigned long               lNextAddress;
	unsigned int irPC = -1;

	assert(NULL != spSegmentCmd);

	spThreadCmd = (struct thread_command *)spSegmentCmd;

	spThreadStateFlavor =
	 (struct thread_state_flavor *)(spThreadCmd + 1);

	lNextAddress = (unsigned long)(spThreadStateFlavor + 1);

	switch(spThreadStateFlavor->flavor)
	{
	case M68K_THREAD_STATE_REGS:
		irPC = ((struct m68k_thread_state_regs *)lNextAddress)->pc;
		break;
	case M68K_THREAD_STATE_68882:
		irPC = ((struct m68k_thread_state_68882 *)lNextAddress)->iar;
		break;
	case M68K_THREAD_STATE_USER_REG:
		irPC = ((struct m68k_thread_state_user_reg *)lNextAddress)->user_reg;
		break;
	case i386_THREAD_STATE:
		irPC = ((i386_thread_state_t *)lNextAddress)->eip;
		break;
	}
	 
	return irPC;
}

void
check_header(struct mach_header *header)
{
	if (header->magic != MH_MAGIC)
	{
		printf("Not a Mach-O file (bad magic number 0x%x)!\n", header->magic);
		exit(1);
	}

	if (header->cputype != CPU_TYPE_MC680x0)
		printf("Cputype is %d (Not MC68030 so disassembly may be wrong)\n",
			header->cputype);

	if (header->cpusubtype != CPU_SUBTYPE_MC680x0_ALL)
		printf("Wrong machine (cpusubtype is %d, MC680x0 is 1)?\n",
			header->cpusubtype);
}

void
usage()
{
	puts("              dis: the Mach-O/NeXT disassembler.\n"
		"      Written by Bill Spitzak (SPITZAK@MCIMAIL.COM) Sep 1989\n"
		"      This software is distributed FREE and may not be sold.\n"
		"\n"
		"dis {-xxx} name\t\t: disassemble executable\n"
		"dis {-xxx} name.o\t: disassemble object\n"
		"dis {-xxx} lib.a name.o\t: disassemble object in library\n"
		"\n"
		"switches:\n"
		"-d #\t: print constants larger than # in hex (default=127)\n"
		"-e\t: don't print .globl declarations for externals\n"
		"-g\t: assume all globals in .text segment are code\n"
		"-G\t: assume everything in .text is code\n"
		"-p #\t: print during pass # (0 is first pass)\n"
		"-t\t: print only the symbols, no code\n"
		"-x\t: print address in hex before each line\n"
		"\n");

	exit (1);
}
