changeset 111:0f94d17899b3

tiobjd: disassembly integrated, no relocs or hints yet
author Michael Spacefalcon <msokolov@ivan.Harhan.ORG>
date Thu, 03 Apr 2014 05:14:15 +0000
parents e650fdc743fe
children 61a58677dc68
files ticoff/Makefile ticoff/armdis.c ticoff/atcommon.c ticoff/disasm.c ticoff/main.c ticoff/thumbdis.c
diffstat 6 files changed, 1052 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/ticoff/Makefile	Thu Apr 03 03:03:41 2014 +0000
+++ b/ticoff/Makefile	Thu Apr 03 05:14:15 2014 +0000
@@ -1,7 +1,8 @@
 CC=	gcc
 CFLAGS=	-O2
 PROG=	tiobjd
-OBJS=	basics.o globals.o lowlevel.o main.o reloc.o symtab.o tables.o
+OBJS=	armdis.o atcommon.o basics.o disasm.o globals.o lowlevel.o main.o \
+	reloc.o symtab.o tables.o thumbdis.o
 HDRS=	coffconst.h filestruct.h globals.h intstruct.h
 
 all:	${PROG}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ticoff/armdis.c	Thu Apr 03 05:14:15 2014 +0000
@@ -0,0 +1,491 @@
+/*
+ * ARM state disassembly
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "intstruct.h"
+#include "globals.h"
+
+extern unsigned get_u16(), get_u32();
+
+extern char *regnames[16], *condition_decode[16], *shift_types[4];
+
+static char *dataproc_ops[16] = {"and", "eor", "sub", "rsb",
+				 "add", "adc", "sbc", "rsc",
+				 "tst", "teq", "cmp", "cmn",
+				 "orr", "mov", "bic", "mvn"};
+
+static void
+arm_branch(off, word)
+	unsigned off, word;
+{
+	unsigned dest;
+
+	dest = (word & 0x00FFFFFF) << 2;
+	if (dest & 0x02000000)
+		dest |= 0xFC000000;
+	dest += off + 8;
+	printf("b%s%s\t0x%x\n", word&0x1000000 ? "l" : "",
+		condition_decode[word>>28], dest);
+}
+
+static void
+op2_immed(word)
+	unsigned word;
+{
+	unsigned low8, rot, val;
+
+	low8 = word & 0xFF;
+	rot = (word & 0xF00) >> 7;
+	val = (low8 << (32 - rot)) | (low8 >> rot);
+	if (val <= 9)
+		printf("#%u\n", val);
+	else
+		printf("#%u\t; 0x%x\n", val, val);
+}
+
+static void
+op2_regbyconst(word)
+	unsigned word;
+{
+	unsigned c, t;
+
+	c = (word >> 7) & 0x1F;
+	t = (word >> 5) & 3;
+	if (!c) {
+		switch (t) {
+		case 0:
+			printf("%s", regnames[word&0xF]);
+			return;
+		case 3:
+			printf("%s, rrx", regnames[word&0xF]);
+			return;
+		default:
+			c = 32;
+		}
+	}
+	printf("%s, %s #%u", regnames[word&0xF], shift_types[t], c);
+}
+
+static void
+op2_regbyreg(word)
+	unsigned word;
+{
+	printf("%s, %s %s", regnames[word&0xF], shift_types[(word>>5)&3],
+		regnames[(word>>8)&0xF]);
+}
+
+static void
+op2_regshift(word)
+	unsigned word;
+{
+	if (word & 0x10)
+		op2_regbyreg(word);
+	else
+		op2_regbyconst(word);
+	putchar('\n');
+}
+
+static void
+dataproc_op2(word)
+	unsigned word;
+{
+	if (word & 0x02000000)
+		op2_immed(word);
+	else
+		op2_regshift(word);
+}
+
+static void
+dataproc_tstcmp_overlay(word)
+	unsigned word;
+{
+	char msrmask[5], *cp;
+
+	if ((word & 0x0FFFFFF0) == 0x012FFF10) {
+		printf("bx%s\t%s\n", condition_decode[word>>28],
+			regnames[word&0xF]);
+		return;
+	} else if ((word & 0x0FBF0FFF) == 0x010F0000) {
+		printf("mrs%s\t%s, %cPSR\n", condition_decode[word>>28],
+			regnames[(word>>12)&0xF], word&0x400000 ? 'S' : 'C');
+		return;
+	} else if ((word & 0x0DB0F000) == 0x0120F000) {
+		if (!(word & 0x02000000) && (word & 0xFF0)) {
+			printf("<invalid MSR>\n");
+			return;
+		}
+		if (word & 0xF0000) {
+			cp = msrmask;
+			if (word & 0x80000)
+				*cp++ = 'f';
+			if (word & 0x40000)
+				*cp++ = 's';
+			if (word & 0x20000)
+				*cp++ = 'x';
+			if (word & 0x10000)
+				*cp++ = 'c';
+			*cp = '\0';
+		} else
+			strcpy(msrmask, "null");
+		printf("msr%s\t%cPSR_%s, ", condition_decode[word>>28],
+			word&0x400000 ? 'S' : 'C', msrmask);
+		dataproc_op2(word);
+		return;
+	}
+	printf("<invalid BX/MRS/MSR>\n");
+}
+
+static void
+dataproc(word)
+	unsigned word;
+{
+	unsigned opc;
+
+	opc = (word >> 21) & 0xF;
+	switch (opc) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+	case 0xC:
+	case 0xE:
+		printf("%s%s%s\t%s, %s, ", dataproc_ops[opc],
+			condition_decode[word>>28], word&0x100000 ? "s" : "",
+			regnames[(word>>12)&0xF], regnames[(word>>16)&0xF]);
+		dataproc_op2(word);
+		return;
+	case 0xD:
+	case 0xF:
+		printf("%s%s%s\t%s, ", dataproc_ops[opc],
+			condition_decode[word>>28], word&0x100000 ? "s" : "",
+			regnames[(word>>12)&0xF]);
+		dataproc_op2(word);
+		return;
+	case 8:
+	case 9:
+	case 0xA:
+	case 0xB:
+		if (word & 0x100000) {
+			printf("%s%s\t%s, ", dataproc_ops[opc],
+				condition_decode[word>>28],
+				regnames[(word>>16)&0xF]);
+			dataproc_op2(word);
+		} else
+			dataproc_tstcmp_overlay(word);
+		return;
+	}
+}
+
+static void
+multiply(word)
+	unsigned word;
+{
+	if ((word & 0x0FE000F0) == 0x90)
+		printf("mul%s%s\t%s, %s, %s\n", condition_decode[word>>28],
+			word&0x100000 ? "s" : "", regnames[(word>>16)&0xF],
+			regnames[word&0xF], regnames[(word>>8)&0xF]);
+	else if ((word & 0x0FE000F0) == 0x00200090)
+		printf("mla%s%s\t%s, %s, %s, %s\n", condition_decode[word>>28],
+			word&0x100000 ? "s" : "", regnames[(word>>16)&0xF],
+			regnames[word&0xF], regnames[(word>>8)&0xF],
+			regnames[(word>>12)&0xF]);
+	else if ((word & 0x0F8000F0) == 0x00800090)
+		printf("%c%sl%s%s\t%s, %s, %s, %s\n",
+			word&0x400000 ? 's' : 'u',
+			word&0x200000 ? "mla" : "mul",
+			condition_decode[word>>28],
+			word&0x100000 ? "s" : "",
+			regnames[(word>>12)&0xF], regnames[(word>>16)&0xF],
+			regnames[word&0xF], regnames[(word>>8)&0xF]);
+	else if ((word & 0x0FB00FF0) == 0x01000090)
+		printf("swp%s%s\t%s, %s, [%s]\n", condition_decode[word>>28],
+			word&0x400000, "b", "", regnames[(word>>12)&0xF],
+			regnames[word&0xF], regnames[(word>>16)&0xF]);
+	else
+		printf("<invalid multiply>\n");
+}
+
+static int
+check_ldr_litpool(sec, off, word, loff, size)
+	struct internal_scnhdr *sec;
+	unsigned off, word, loff;
+{
+	unsigned litoff, datum;
+
+	/* base reg must be 15 */
+	if (((word >> 16) & 0xF) != 15)
+		return(0);
+	/* must be a load */
+	if (!(word & 0x100000))
+		return(0);
+	/* no writeback allowed */
+	if (word & 0x200000)
+		return(0);
+	/* alignment */
+	if (loff & (size - 1))
+		return(0);
+	/* range */
+	off += 8;
+	if (word & 0x800000)
+		litoff = off + loff;
+	else {
+		if (loff > off)
+			return(0);
+		litoff = off - loff;
+	}
+	if (litoff >= sec->size)
+		return(0);
+	/* all checks passed, proceed */
+	switch (size) {
+	case 1:
+		datum = filemap[sec->data_offset + litoff];
+		break;
+	case 2:
+		datum = get_u16(filemap + sec->data_offset + litoff);
+		break;
+	case 4:
+		datum = get_u32(filemap + sec->data_offset + litoff);
+		break;
+	}
+	printf("=0x%x\t; via 0x%x\n", datum, litoff);
+	return(1);
+}
+
+static void
+ldr_str_imm_pre(sec, off, word)
+	struct internal_scnhdr *sec;
+	unsigned off, word;
+{
+	unsigned loff = word & 0xFFF;
+
+	printf("%s%s%s\t%s, ", word&0x100000 ? "ldr" : "str",
+		condition_decode[word>>28], word&0x400000 ? "b" : "",
+		regnames[(word>>12)&0xF]);
+	if (check_ldr_litpool(sec, off, word, loff, word&0x400000 ? 1 : 4))
+		return;
+	printf("[%s", regnames[(word>>16)&0xF]);
+	if (loff || word&0x200000)
+		printf(", #%s%u", word&0x800000 ? "" : "-", loff);
+	putchar(']');
+	if (word & 0x200000)
+		putchar('!');
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+ldr_str_imm_post(word)
+	unsigned word;
+{
+	unsigned loff = word & 0xFFF;
+
+	printf("%s%s%s%s\t%s, [%s], #%s%u", word&0x100000 ? "ldr" : "str",
+		condition_decode[word>>28], word&0x400000 ? "b" : "",
+		word&0x200000 ? "t" : "",
+		regnames[(word>>12)&0xF], regnames[(word>>16)&0xF],
+		word&0x800000 ? "" : "-", loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+ldr_str_reg_pre(word)
+	unsigned word;
+{
+	if (word & 0x10) {
+		printf("<invalid ldr/str: offset reg shift by reg>\n");
+		return;
+	}
+	printf("%s%s%s\t%s, [%s, ", word&0x100000 ? "ldr" : "str",
+		condition_decode[word>>28], word&0x400000 ? "b" : "",
+		regnames[(word>>12)&0xF], regnames[(word>>16)&0xF]);
+	if (!(word & 0x800000))
+		putchar('-');
+	op2_regbyconst(word);
+	putchar(']');
+	if (word & 0x200000)
+		putchar('!');
+	putchar('\n');
+}
+
+static void
+ldr_str_reg_post(word)
+	unsigned word;
+{
+	if (word & 0x10) {
+		printf("<invalid ldr/str: offset reg shift by reg>\n");
+		return;
+	}
+	printf("%s%s%s%s\t%s, [%s], ", word&0x100000 ? "ldr" : "str",
+		condition_decode[word>>28], word&0x400000 ? "b" : "",
+		word&0x200000 ? "t" : "",
+		regnames[(word>>12)&0xF], regnames[(word>>16)&0xF]);
+	if (!(word & 0x800000))
+		putchar('-');
+	op2_regbyconst(word);
+	putchar('\n');
+}
+
+static void
+ldr_str_ext(sec, off, word)
+	struct internal_scnhdr *sec;
+	unsigned off, word;
+{
+	unsigned loff;
+
+	if (!(word&0x01000000) && word&0x200000) {
+		printf("<invalid ldrh/strh: P=0, W=1>\n");
+		return;
+	}
+	if (!(word&0x400000) && word&0xF00) {
+		printf("<invalid ldrh/strh: SBZ!=0>\n");
+		return;
+	}
+	printf("%s%s%s%c\t%s, ", word&0x100000 ? "ldr" : "str",
+		condition_decode[word>>28],
+		word&0x40 ? "s" : "",
+		word&0x20 ? 'h' : 'b',
+		regnames[(word>>12)&0xF]);
+	if (word & 0x400000)
+		loff = ((word & 0xF00) >> 4) | (word & 0xF);
+	switch (word & 0x01400000) {
+	case 0:
+		/* reg post */
+		printf("[%s], %s%s", regnames[(word>>16)&0xF],
+			word&0x800000 ? "" : "-", regnames[word&0xF]);
+		break;
+	case 0x400000:
+		/* imm post */
+		printf("[%s], #%s%u", regnames[(word>>16)&0xF],
+			word&0x800000 ? "" : "-", loff);
+		if (loff >= 10)
+			printf("\t; 0x%x", loff);
+		break;
+	case 0x01000000:
+		/* reg pre */
+		printf("[%s, %s%s]%s", regnames[(word>>16)&0xF],
+			word&0x800000 ? "" : "-", regnames[word&0xF],
+			word&0x200000 ? "!" : "");
+		break;
+	case 0x01400000:
+		/* imm pre */
+		if (check_ldr_litpool(sec, off, word, loff, word&0x20 ? 2 : 1))
+			return;
+		printf("[%s", regnames[(word>>16)&0xF]);
+		if (loff || word&0x200000)
+			printf(", #%s%u", word&0x800000 ? "" : "-", loff);
+		putchar(']');
+		if (word & 0x200000)
+			putchar('!');
+		if (loff >= 10)
+			printf("\t; 0x%x", loff);
+		break;
+	}
+	putchar('\n');
+}
+
+static void
+dataproc_74_overlay(sec, off, word)
+	struct internal_scnhdr *sec;
+	unsigned off, word;
+{
+	if (word & 0x60)
+		ldr_str_ext(sec, off, word);
+	else
+		multiply(word);
+}
+
+static void
+ldm_stm(word)
+	unsigned word;
+{
+	int r, flag;
+
+	printf("%s%s%c%c\t%s", word&0x100000 ? "ldm" : "stm",
+		condition_decode[word>>28],
+		word&0x800000 ? 'i' : 'd', word&0x01000000 ? 'b' : 'a',
+		regnames[(word>>16)&0xF]);
+	if (word & 0x200000)
+		putchar('!');
+	fputs(", {", stdout);
+	flag = 0;
+	for (r = 0; r < 16; r++)
+		if (word & (1 << r)) {
+			if (flag)
+				fputs(", ", stdout);
+			fputs(regnames[r], stdout);
+			flag = 1;
+		}
+	putchar('}');
+	if (word & 0x400000)
+		putchar('^');
+	putchar('\n');
+}
+
+void
+arm_disasm_line(sec, off)
+	struct internal_scnhdr *sec;
+	unsigned off;
+{
+	unsigned word;
+
+	word = get_u32(filemap + sec->data_offset + off);
+	printf("%08x\t", word);
+	if ((word >> 28) == 0xF) {
+		printf("<invalid-F>\n");
+		return;
+	}
+	switch ((word >> 24) & 0xF) {
+	case 0:
+	case 1:
+		if ((word & 0x90) == 0x90)
+			dataproc_74_overlay(sec, off, word);
+		else
+			dataproc(word);
+		return;
+	case 2:
+	case 3:
+		dataproc(word);
+		return;
+	case 4:
+		ldr_str_imm_post(word);
+		return;
+	case 5:
+		ldr_str_imm_pre(sec, off, word);
+		return;
+	case 6:
+		ldr_str_reg_post(word);
+		return;
+	case 7:
+		ldr_str_reg_pre(word);
+		return;
+	case 8:
+	case 9:
+		ldm_stm(word);
+		return;
+	case 0xA:
+	case 0xB:
+		arm_branch(off, word);
+		return;
+	case 0xC:
+	case 0xD:
+	case 0xE:
+		printf("<COPROCESSOR>\n");
+		return;
+	case 0xF:
+		printf("swi%s\t0x%x\n", condition_decode[word>>28],
+			word & 0xFFFFFF);
+		return;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ticoff/atcommon.c	Thu Apr 03 05:14:15 2014 +0000
@@ -0,0 +1,14 @@
+/*
+ * Lean and mean ARM7TDMI disassembler
+ * Written by Spacefalcon the Outlaw
+ */
+
+/* a few disassembly bits common between ARM and Thumb */
+
+char *regnames[16] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
+			"r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc"};
+
+char *condition_decode[16] = {"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
+				"hi", "ls", "ge", "lt", "gt", "le", "", "INV"};
+
+char *shift_types[4] = {"lsl", "lsr", "asr", "ror"};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ticoff/disasm.c	Thu Apr 03 05:14:15 2014 +0000
@@ -0,0 +1,144 @@
+/*
+ * Putting it all together: section-, symbol- and reloc-aware disassembly
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "intstruct.h"
+#include "coffconst.h"
+#include "globals.h"
+
+extern unsigned get_u16(), get_u32();
+
+void
+disasm_text_section(sec)
+	struct internal_scnhdr *sec;
+{
+	unsigned symnum, relnum;
+	unsigned pos, incr, headroom;
+	int state = -1, linebrk = 0;
+	struct internal_syment *sym;
+	struct internal_reloc *rel;
+	char *sym_comment;
+
+	printf("Disassembling code section:\n");
+	if (sec->nsymbols)
+		sort_symbols_of_sec(sec);
+	if (sec->nreloc)
+		get_relocs_of_sec(sec);
+	symnum = relnum = 0;
+	for (pos = 0; pos < sec->size; pos += incr) {
+		headroom = sec->size - pos;
+		while (symnum < sec->nsymbols) {
+			sym = sec->sorted_symbols[symnum];
+			if (sym->value > pos) {
+				if (sym->value - pos < headroom)
+					headroom = sym->value - pos;
+				break;
+			}
+			/* hit symbol */
+			if (!linebrk) {
+				putchar('\n');
+				linebrk = 1;
+			}
+			switch (sym->class) {
+			case C_EXT:
+				sym_comment = "Global";
+				break;
+			case C_STAT:
+				sym_comment = "static";
+				break;
+			case C_LABEL:
+				sym_comment = "label";
+				if (!strcmp(sym->name, "$CODE16"))
+					state = 1;
+				else if (!strcmp(sym->name, "$CODE32"))
+					state = 0;
+				break;
+			default:
+				sym_comment = "unexpected class!";
+			}
+			printf("%s:\t; %s\n", sym->name, sym_comment);
+			symnum++;
+		}
+		if (relnum < sec->nreloc) {
+			rel = sec->int_relocs + relnum;
+			if (rel->location == pos)
+				relnum++;	/* it's ours */
+			else {
+				if (rel->location - pos < headroom)
+					headroom = rel->location - pos;
+				rel = 0;	/* no reloc for current pos */
+			}
+		} else
+			rel = 0;
+		printf("%8x:\t", pos);
+		switch (state) {
+		case 0:		/* ARM */
+			if (pos & 3) {
+			   printf("MISALIGNED pos in CODE32 state, aborting\n");
+				return;
+			}
+			/* reloc handling to be added */
+			arm_disasm_line(sec, pos);
+			incr = 4;
+			break;
+		case 1:		/* Thumb */
+			if (pos & 1) {
+			   printf("MISALIGNED pos in CODE16 state, aborting\n");
+				return;
+			}
+			/* reloc handling to be added */
+			if (headroom >= 4 && thumb_check_bl(sec, pos))
+				incr = 4;
+			else {
+				thumb_disasm_line(sec, pos);
+				incr = 2;
+			}
+			break;
+		default:
+			printf("UNKNOWN T state, aborting\n");
+			return;
+		}
+		linebrk = 0;
+		if (incr > headroom) {
+			printf("error: increment %u > headroom %u, aborting\n",
+				incr, headroom);
+			return;
+		}
+	}
+}
+
+void
+disasm_sectype_by_name(sec)
+	struct internal_scnhdr *sec;
+{
+	if (!strncmp(sec->name, ".text", 5))
+		disasm_text_section(sec);
+	/* other section types to be added */
+	else
+		printf("Unrecognized section type, skipped\n");
+}
+
+cmd_disasm()
+{
+	struct internal_scnhdr *sec;
+	unsigned secnum;
+
+	printf("%s:\n", objfilename);
+	dump_filehdr_info();
+	putchar('\n');
+	get_int_section_table();
+	get_int_symbol_table();
+	extern_profile_report("Module");
+	for (secnum = 0; secnum < nsections; secnum++) {
+		sec = sections + secnum;
+		printf("=== %s ===\n", sec->name);
+		disasm_sectype_by_name(sec);
+		putchar('\n');
+	}
+	exit(0);
+}
--- a/ticoff/main.c	Thu Apr 03 03:03:41 2014 +0000
+++ b/ticoff/main.c	Thu Apr 03 05:14:15 2014 +0000
@@ -10,6 +10,7 @@
 #include "globals.h"
 
 extern int cmd_basics();
+extern int cmd_disasm();
 extern int cmd_nm();
 extern int cmd_profile();
 extern int cmd_rawrel();
@@ -23,6 +24,7 @@
 	int	(*func)();
 } cmdtab[] = {
 	{"basics", cmd_basics},
+	{"disasm", cmd_disasm},
 	{"dumpsym", cmd_symtab},	/* backward compat */
 	{"hdr", dump_filehdr_info},
 	{"nm", cmd_nm},
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ticoff/thumbdis.c	Thu Apr 03 05:14:15 2014 +0000
@@ -0,0 +1,399 @@
+/*
+ * Thumb state disassembly
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "intstruct.h"
+#include "globals.h"
+
+extern unsigned get_u16(), get_u32();
+
+extern char *regnames[16], *condition_decode[16], *shift_types[4];
+
+static void
+format_1_2(word)
+	unsigned word;
+{
+	unsigned op, imm;
+
+	op = (word >> 11) & 3;
+	if (op != 3) {
+		/* format 1 */
+		imm = (word >> 6) & 0x1F;
+		if (op != 0 && imm == 0)
+			imm = 32;
+		printf("%s\t%s, %s, #%u\n", shift_types[op], regnames[word&7],
+			regnames[(word>>3)&7], imm);
+		return;
+	}
+	/* format 2 */
+	printf("%s\t%s, %s, ", word&0x200 ? "sub" : "add", regnames[word&7],
+		regnames[(word>>3)&7]);
+	if (word & 0x400)
+		printf("#%u\n", (word >> 6) & 7);
+	else
+		printf("%s\n", regnames[(word >> 6) & 7]);
+}
+
+static void
+format_3(word)
+	unsigned word;
+{
+	static char *opctab[4] = {"mov", "cmp", "add", "sub"};
+	unsigned imm;
+
+	imm = word & 0xFF;
+	printf("%s\t%s, #%u", opctab[(word>>11)&3], regnames[(word>>8)&7], imm);
+	if (imm > 9)
+		printf("\t; 0x%x", imm);
+	putchar('\n');
+}
+
+static void
+format_4(word)
+	unsigned word;
+{
+	static char *opc[16] = {"and", "eor", "lsl", "lsr",
+				"asr", "adc", "sbc", "ror",
+				"tst", "neg", "cmp", "cmn",
+				"orr", "mul", "bic", "mvn"};
+
+	printf("%s\t%s, %s\n", opc[(word>>6)&0xF], regnames[word&7],
+		regnames[(word>>3)&7]);
+}
+
+static void
+format_5_bx(word)
+	unsigned word;
+{
+	if (word & 0x80)
+		printf("<invalid: blx instead of bx>\n");
+	else
+		printf("bx\t%s\n", regnames[(word>>3)&0xF]);
+}
+
+static void
+format_5_hiops(word)
+	unsigned word;
+{
+	static char *opctab[3] = {"add", "cmp", "mov"};
+	int reg1, reg2, op;
+
+	if (word & 0xC0) {
+		reg1 = word & 7;
+		if (word & 0x80)
+			reg1 += 8;
+		reg2 = (word >> 3) & 0xF;
+		op = (word >> 8) & 3;
+		if (op == 2 && reg1 == reg2 && reg1 != 15)
+			printf("nop\t\t\t(mov %s, %s)\n",
+				regnames[reg1], regnames[reg2]);
+		else
+			printf("%s\t%s, %s\n", opctab[op],
+				regnames[reg1], regnames[reg2]);
+	} else
+		printf("<invalid: hi-reg format with both low regs>\n");
+}
+
+static void
+format_5(word)
+	unsigned word;
+{
+	if ((word & 0x300) == 0x300)
+		format_5_bx(word);
+	else
+		format_5_hiops(word);
+}
+
+static void
+format_6(sec, off, word)
+	struct internal_scnhdr *sec;
+	unsigned off, word;
+{
+	unsigned loff, litoff;
+
+	loff = (word & 0xFF) << 2;
+	off &= ~3;
+	off += 4;
+	litoff = off + loff;
+	if (litoff+4 <= sec->size)
+		printf("ldr\t%s, =0x%x\t; via 0x%x\n", regnames[(word>>8)&7],
+			get_u32(filemap + sec->data_offset + litoff), litoff);
+	else
+		printf("ldr\t%s, [pc, #%u]\t(0x%x)\n", regnames[(word>>8)&7],
+			loff, litoff);
+}
+
+static void
+format_7(word)
+	unsigned word;
+{
+	printf("%s%s\t%s, [%s, %s]\n", word&0x800 ? "ldr" : "str",
+		word&0x400 ? "b" : "", regnames[word&7],
+		regnames[(word>>3)&7], regnames[(word>>6)&7]);
+}
+
+static void
+format_8(word)
+	unsigned word;
+{
+	static char *opc[4] = {"strh", "ldrsb", "ldrh", "ldrsh"};
+
+	printf("%s\t%s, [%s, %s]\n", opc[(word>>10)&3], regnames[word&7],
+		regnames[(word>>3)&7], regnames[(word>>6)&7]);
+}
+
+static void
+format_9(word)
+	unsigned word;
+{
+	unsigned loff;
+
+	loff = (word >> 6) & 0x1F;
+	if (!(word & 0x1000))
+		loff <<= 2;
+	printf("%s%s\t%s, [%s, #%u]", word&0x800 ? "ldr" : "str",
+		word&0x1000 ? "b" : "", regnames[word&7],
+		regnames[(word>>3)&7], loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+format_10(word)
+	unsigned word;
+{
+	unsigned loff;
+
+	loff = (word >> 6) & 0x1F;
+		loff <<= 1;
+	printf("%sh\t%s, [%s, #%u]", word&0x800 ? "ldr" : "str",
+		regnames[word&7], regnames[(word>>3)&7], loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+format_11(word)
+	unsigned word;
+{
+	unsigned loff;
+
+	loff = (word & 0xFF) << 2;
+	printf("%s\t%s, [sp, #%u]", word&0x800 ? "ldr" : "str",
+		regnames[(word>>8)&7], loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+format_12(off, word)
+	unsigned off, word;
+{
+	unsigned loff;
+
+	loff = (word & 0xFF) << 2;
+	printf("add\t%s, %s, #%u", regnames[(word>>8)&7],
+		word&0x800 ? "sp" : "pc", loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+format_13(word)
+	unsigned word;
+{
+	unsigned loff;
+
+	if ((word & 0xFF00) != 0xB000) {
+		printf("<invalid format 13>\n");
+		return;
+	}
+	loff = (word & 0x7F) << 2;
+	printf("%s\tsp, #%u", word&0x80 ? "sub" : "add", loff);
+	if (loff >= 10)
+		printf("\t; 0x%x", loff);
+	putchar('\n');
+}
+
+static void
+format_14(word)
+	unsigned word;
+{
+	int r, flag;
+
+	if ((word & 0xF600) != 0xB400) {
+		printf("<invalid format 14>\n");
+		return;
+	}
+	printf("%s\t{", word&0x800 ? "pop" : "push");
+	flag = 0;
+	for (r = 0; r < 9; r++)
+		if (word & (1 << r)) {
+			if (flag)
+				fputs(", ", stdout);
+			if (r == 8)
+				fputs(word&0x800 ? "pc" : "lr", stdout);
+			else
+				fputs(regnames[r], stdout);
+			flag = 1;
+		}
+	putchar('}');
+	putchar('\n');
+}
+
+static void
+format_15(word)
+	unsigned word;
+{
+	int r, flag;
+
+	printf("%sia\t%s!, {", word&0x800 ? "ldm" : "stm",
+		regnames[(word>>8)&7]);
+	flag = 0;
+	for (r = 0; r < 8; r++)
+		if (word & (1 << r)) {
+			if (flag)
+				fputs(", ", stdout);
+			fputs(regnames[r], stdout);
+			flag = 1;
+		}
+	putchar('}');
+	putchar('\n');
+}
+
+static void
+format_16_17(off, word)
+	unsigned off, word;
+{
+	unsigned cond;
+	unsigned dest;
+
+	cond = (word >> 8) & 0xF;
+	switch (cond) {
+	case 0xE:
+		printf("<invalid: bal>\n");
+		return;
+	case 0xF:
+		printf("swi\t0x%x\n", word & 0xFF);
+		return;
+	}
+	dest = (word & 0xFF) << 1;
+	if (dest & 0x00000100)
+		dest |= 0xFFFFFE00;
+	dest += off + 4;
+	printf("b%s\t0x%x\n", condition_decode[cond], dest);
+}
+
+static void
+format_18(off, word)
+	unsigned off, word;
+{
+	unsigned dest;
+
+	if (word & 0x800) {
+		printf("<invalid format 18>\n");
+		return;
+	}
+	dest = (word & 0x7FF) << 1;
+	if (dest & 0x00000800)
+		dest |= 0xFFFFF000;
+	dest += off + 4;
+	printf("b\t0x%x\n", dest);
+}
+
+void
+thumb_disasm_line(sec, off)
+	struct internal_scnhdr *sec;
+	unsigned off;
+{
+	unsigned word;
+
+	word = get_u16(filemap + sec->data_offset + off);
+	printf("%04x\t\t", word);
+	switch (word >> 12) {
+	case 0:
+	case 1:
+		format_1_2(word);
+		return;
+	case 2:
+	case 3:
+		format_3(word);
+		return;
+	case 4:
+		if (word & 0x800)
+			format_6(sec, off, word);
+		else if (word & 0x400)
+			format_5(word);
+		else
+			format_4(word);
+		return;
+	case 5:
+		if (word & 0x200)
+			format_8(word);
+		else
+			format_7(word);
+		return;
+	case 6:
+	case 7:
+		format_9(word);
+		return;
+	case 8:
+		format_10(word);
+		return;
+	case 9:
+		format_11(word);
+		return;
+	case 0xA:
+		format_12(off, word);
+		return;
+	case 0xB:
+		if (word & 0x400)
+			format_14(word);
+		else
+			format_13(word);
+		return;
+	case 0xC:
+		format_15(word);
+		return;
+	case 0xD:
+		format_16_17(off, word);
+		return;
+	case 0xE:
+		format_18(off, word);
+		return;
+	case 0xF:
+		printf("<half-bl>\n");
+		return;
+	}
+}
+
+thumb_check_bl(sec, off)
+	struct internal_scnhdr *sec;
+	unsigned off;
+{
+	unsigned ins1, ins2;
+	unsigned dest;
+
+	ins1 = get_u16(filemap + sec->data_offset + off);
+	if ((ins1 & 0xF800) != 0xF000)
+		return(0);
+	ins2 = get_u16(filemap + sec->data_offset + off + 2);
+	if ((ins2 & 0xF800) != 0xF800)
+		return(0);
+	/* match */
+	dest = ((ins1 & 0x7FF) << 12) | ((ins2 & 0x7FF) << 1);
+	if (dest & 0x00400000)
+		dest |= 0xFF800000;
+	dest += off + 4;
+	printf("%04x %04x\tbl\t0x%x\n", ins1, ins2, dest);
+	return(1);
+}