1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 #define TITLE "hax65816 - Simple 65816 disassembler"
4 #define COPYR "Copyright (C) 1998,2010 Neill Corlett"
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 ////////////////////////////////////////////////////////////////////////////////
20
21 #include "common.h"
22 #include "banner.h"
23
24 ////////////////////////////////////////////////////////////////////////////////
25
26 static char aluop[8][4] =
27 {"ora", "and", "eor", "adc", "sta", "lda", "cmp", "sbc" };
28 static char rmwop[8][4] =
29 {"asl", "rol", "lsr", "ror", "???", "???", "dec", "inc" };
30
31 static int8_t mflag_default = 1;
32 static int8_t xflag_default = 0;
33 static int8_t flag_return = 1;
34 static int8_t flag_guess = 1;
35 static int8_t flag_follow = 1;
36
37 static uint32_t dasm_address, ins_address;
38 static int8_t mflag, xflag;
39
40 static int getbyte(void);
41
42 ////////////////////////////////////////////////////////////////////////////////
43
44 static int fetchlist[10];
45 static int unfetchstack[10];
46 static int fetchn, unfetchn;
47
fetchbyte(void)48 static int fetchbyte(void) {
49 int b;
50 dasm_address++;
51 if(unfetchn) b = unfetchstack[--unfetchn]; else b = getbyte();
52 fetchlist[fetchn++] = b;
53 return b;
54 }
55
unfetchbyte(int b)56 static void unfetchbyte(int b) {
57 dasm_address--;
58 fetchn--;
59 if(fetchn < 0) fetchn = 0;
60 unfetchstack[unfetchn++] = b;
61 }
62
63 enum { INS_COLUMN = 23 };
64
ins(const char * fmt,...)65 static void ins(const char* fmt, ...) {
66 static char outputstr[100];
67 int outputn = INS_COLUMN;
68 va_list ap;
69 va_start(ap, fmt);
70 for(;;) {
71 char c = *fmt++;
72 if(!c) break;
73 if(c == '%') {
74 c = *fmt++;
75 switch(c) {
76 case '%': {
77 outputstr[outputn++] = '%';
78 break; }
79 case 'A': {
80 int n = (va_arg(ap, int) >> 5) & 7;
81 outputstr[outputn++] = aluop[n][0];
82 outputstr[outputn++] = aluop[n][1];
83 outputstr[outputn++] = aluop[n][2];
84 break; }
85 case 'M': {
86 int n = (va_arg(ap, int) >> 5) & 7;
87 outputstr[outputn++] = rmwop[n][0];
88 outputstr[outputn++] = rmwop[n][1];
89 outputstr[outputn++] = rmwop[n][2];
90 break; }
91 case 'B': {
92 int n = fetchbyte();
93 outputstr[outputn++] = '$';
94 if(n < 0) {
95 outputstr[outputn++] = '-';
96 outputstr[outputn++] = '-';
97 } else {
98 sprintf(
99 outputstr + outputn,
100 "%02lX", (unsigned long)n
101 );
102 outputn += 2;
103 }
104 break; }
105 case 'R': {
106 int n = fetchbyte();
107 outputstr[outputn++] = '$';
108 if(n < 0) {
109 outputstr[outputn++] = '-';
110 outputstr[outputn++] = '-';
111 outputstr[outputn++] = '-';
112 outputstr[outputn++] = '-';
113 } else {
114 n = ((int8_t)(n)) +
115 dasm_address;
116 sprintf(
117 outputstr + outputn,
118 "%04lX", (unsigned long)(n & 0xFFFF)
119 );
120 outputn += 4;
121 }
122 break; }
123 case 'Z': {
124 int n1 = fetchbyte();
125 int n2 = fetchbyte();
126 outputstr[outputn++] = '$';
127 if((n1 < 0) || (n2 < 0)) {
128 for(n1 = 0; n1 < 6; n1++) {
129 outputstr[outputn++] = '-';
130 }
131 } else {
132 int n = (n2 << 8) | (n1 & 0xFF);
133 n = ((int16_t)(n)) +
134 dasm_address;
135 sprintf(
136 outputstr + outputn,
137 "%04lX", (unsigned long)(n & 0xFFFF)
138 );
139 outputn += 6;
140 }
141 break; }
142 case 'G': {
143 int n = va_arg(ap, int);
144 outputstr[outputn++] = '$';
145 if(n < 0) {
146 outputstr[outputn++] = '-';
147 outputstr[outputn++] = '-';
148 } else {
149 sprintf(
150 outputstr + outputn,
151 "%02lX", (unsigned long)(n)
152 );
153 outputn += 2;
154 }
155 break; }
156 case 'W': {
157 int q, n[2];
158 n[1] = fetchbyte();
159 n[0] = fetchbyte();
160 outputstr[outputn++] = '$';
161 for(q = 0; q < 2; q++) {
162 if(n[q] < 0) {
163 outputstr[outputn++] = '-';
164 outputstr[outputn++] = '-';
165 } else {
166 sprintf(
167 outputstr + outputn,
168 "%02lX", (unsigned long)(n[q])
169 );
170 outputn += 2;
171 }
172 }
173 break; }
174 case 'L': {
175 int q, n[3];
176 n[2] = fetchbyte();
177 n[1] = fetchbyte();
178 n[0] = fetchbyte();
179 outputstr[outputn++] = '$';
180 for(q = 0; q < 3; q++) {
181 if(n[q] < 0) {
182 outputstr[outputn++] = '-';
183 outputstr[outputn++] = '-';
184 } else {
185 sprintf(
186 outputstr + outputn,
187 "%02lX", (unsigned long)(n[q])
188 );
189 outputn += 2;
190 }
191 }
192 break; }
193 case 'I': case 'X': {
194 int q, n[3];
195 int8_t* sflag = (c == 'I') ? (&mflag) : (&xflag);
196 outputstr[outputn++] = '$';
197 if(*sflag) {
198 n[0] = fetchbyte();
199 n[1] = fetchbyte();
200 //
201 // BRK/COP/WDM/STP after this?
202 //
203 switch(n[1]) {
204 case 0x00: case 0x02:
205 case 0x42: case 0xDB:
206 if(flag_follow && flag_guess
207 ) {
208 (*sflag) = 0;
209 q = 2;
210 break;
211 }
212 default:
213 unfetchbyte(n[1]);
214 q = 1;
215 break;
216 }
217 } else {
218 n[0] = fetchbyte();
219 n[1] = fetchbyte();
220 n[2] = fetchbyte();
221 //
222 // BRK/COP/WDM/STP after this?
223 //
224 switch(n[2]) {
225 case 0x00: case 0x02:
226 case 0x42: case 0xDB:
227 if(flag_follow && flag_guess
228 ) {
229 (*sflag) = 1;
230 unfetchbyte(n[2]);
231 unfetchbyte(n[1]);
232 q = 1;
233 break;
234 }
235 default:
236 unfetchbyte(n[2]);
237 q = 2;
238 break;
239 }
240 }
241 while(q--) {
242 if(n[q] < 0) {
243 outputstr[outputn++] = '-';
244 outputstr[outputn++] = '-';
245 } else {
246 sprintf(
247 outputstr + outputn,
248 "%02lX", (unsigned long)(n[q])
249 );
250 outputn += 2;
251 }
252 }
253 break; }
254 }
255 } else {
256 outputstr[outputn++] = c;
257 }
258 }
259 outputstr[outputn] = 0;
260 sprintf(outputstr, "%02lX/%04lX:",
261 (unsigned long)(ins_address >> 16),
262 (unsigned long)(ins_address & 0xFFFF)
263 );
264 memset(outputstr + 8, ' ', INS_COLUMN - 8);
265
266 outputn = 9;
267 { int i;
268 for(i = 0; i < fetchn; i++) {
269 int n = fetchlist[i];
270 if(n < 0) {
271 outputstr[outputn ] = '-';
272 outputstr[outputn+1] = '-';
273 } else {
274 sprintf(outputstr + outputn, "%02X", n);
275 outputstr[outputn+2] = ' ';
276 }
277 outputn += 3;
278 }
279 }
280 fetchn = 0;
281 printf("%s\n", outputstr);
282
283 va_end(ap);
284 }
285
disassemble_one(void)286 static void disassemble_one(void) {
287 int opcode;
288 ins_address = dasm_address;
289 opcode = fetchbyte();
290 if(opcode < 0) return;
291
292 if (opcode == 0x00) { ins("brk %B");
293 } else if(opcode == 0x20) { ins("jsr %W");
294 } else if(opcode == 0x40) { ins("rti"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
295 } else if(opcode == 0x60) { ins("rts"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
296 } else if(opcode == 0x80) { ins("bra %R");
297 } else if(opcode == 0xA0) { ins("ldy #%X");
298 } else if(opcode == 0xC0) { ins("cpy #%X");
299 } else if(opcode == 0xE0) { ins("cpx #%X");
300 } else if(opcode == 0x10) { ins("bpl %R");
301 } else if(opcode == 0x30) { ins("bmi %R");
302 } else if(opcode == 0x50) { ins("bvc %R");
303 } else if(opcode == 0x70) { ins("bvs %R");
304 } else if(opcode == 0x90) { ins("bcc %R");
305 } else if(opcode == 0xB0) { ins("bcs %R");
306 } else if(opcode == 0xD0) { ins("bne %R");
307 } else if(opcode == 0xF0) { ins("beq %R");
308
309 } else if((opcode & 0x1F) == 0x01) { ins("%A (%B,x)" , opcode);
310 } else if((opcode & 0x1F) == 0x11) { ins("%A (%B),y" , opcode);
311
312 } else if(opcode == 0x02) { ins("cop %B");
313 } else if(opcode == 0x22) { ins("jsr %L");
314 } else if(opcode == 0x42) { ins("wdm %B");
315 } else if(opcode == 0x62) { ins("per %Z");
316 } else if(opcode == 0x82) { ins("brl %Z");
317 } else if(opcode == 0xA2) { ins("ldx #%X");
318 } else if(opcode == 0xC2) {
319 int n = fetchbyte();
320 if(flag_follow) if(n >= 0) {
321 if(n & 0x10) xflag = 0;
322 if(n & 0x20) mflag = 0;
323 }
324 ins("rep #%G", n);
325 } else if(opcode == 0xE2) {
326 int n = fetchbyte();
327 if(flag_follow) if(n >= 0) {
328 if(n & 0x10) xflag = 1;
329 if(n & 0x20) mflag = 1;
330 }
331 ins("sep #%G", n);
332 } else if((opcode & 0x1F) == 0x12) { ins("%A (%B)" , opcode);
333
334 } else if((opcode & 0x1F) == 0x03) { ins("%A %B,s" , opcode);
335 } else if((opcode & 0x1F) == 0x13) { ins("%A (%B,s),y", opcode);
336
337 } else if(opcode == 0x04) { ins("tsb %B");
338 } else if(opcode == 0x24) { ins("bit %B");
339 } else if(opcode == 0x44) { ins("mvp %B,%B");
340 } else if(opcode == 0x64) { ins("stz %B");
341 } else if(opcode == 0x84) { ins("sty %B");
342 } else if(opcode == 0xA4) { ins("ldy %B");
343 } else if(opcode == 0xC4) { ins("cpy %B");
344 } else if(opcode == 0xE4) { ins("cpx %B");
345 } else if(opcode == 0x14) { ins("trb %B");
346 } else if(opcode == 0x34) { ins("bit %B,x");
347 } else if(opcode == 0x54) { ins("mvn %B,%B");
348 } else if(opcode == 0x74) { ins("stz %B,x");
349 } else if(opcode == 0x94) { ins("sty %B,x");
350 } else if(opcode == 0xB4) { ins("ldy %B,x");
351 } else if(opcode == 0xD4) { ins("pei (%B)");
352 } else if(opcode == 0xF4) { ins("pea %W");
353
354 } else if((opcode & 0x1F) == 0x05) { ins("%A %B" , opcode);
355 } else if((opcode & 0x1F) == 0x15) { ins("%A %B,x" , opcode);
356
357 } else if(((opcode & 0x1F) == 0x06) && ((opcode & 0xC0) != 0x80)) {
358 ins("%M %B", opcode);
359 } else if(opcode == 0x86) { ins("stx %B");
360 } else if(opcode == 0xA6) { ins("ldx %B");
361 } else if(((opcode & 0x1F) == 0x16) && ((opcode & 0xC0) != 0x80)) {
362 ins("%M %B,x", opcode);
363 } else if(opcode == 0x96) { ins("stx %B,y");
364 } else if(opcode == 0xB6) { ins("ldx %B,y");
365
366 } else if((opcode & 0x1F) == 0x07) { ins("%A [%B]" , opcode);
367 } else if((opcode & 0x1F) == 0x17) { ins("%A [%B],y" , opcode);
368
369 } else if(opcode == 0x08) { ins("php");
370 } else if(opcode == 0x28) { ins("plp");
371 } else if(opcode == 0x48) { ins("pha");
372 } else if(opcode == 0x68) { ins("pla");
373 } else if(opcode == 0x88) { ins("dey");
374 } else if(opcode == 0xA8) { ins("tay");
375 } else if(opcode == 0xC8) { ins("iny");
376 } else if(opcode == 0xE8) { ins("inx");
377 } else if(opcode == 0x18) { ins("clc");
378 } else if(opcode == 0x38) { ins("sec");
379 } else if(opcode == 0x58) { ins("cli");
380 } else if(opcode == 0x78) { ins("sei");
381 } else if(opcode == 0x98) { ins("tya");
382 } else if(opcode == 0xB8) { ins("clv");
383 } else if(opcode == 0xD8) { ins("cld");
384 } else if(opcode == 0xF8) { ins("sed");
385
386 } else if((opcode & 0x1F) == 0x09) {
387 if(opcode == 0x89) ins("bit #%I");
388 else ins("%A #%I", opcode);
389 } else if((opcode & 0x1F) == 0x19) { ins("%A %W,y" , opcode);
390
391 } else if(opcode == 0x0A) { ins("asl");
392 } else if(opcode == 0x2A) { ins("rol");
393 } else if(opcode == 0x4A) { ins("lsr");
394 } else if(opcode == 0x6A) { ins("ror");
395 } else if(opcode == 0x8A) { ins("txa");
396 } else if(opcode == 0xAA) { ins("tax");
397 } else if(opcode == 0xCA) { ins("dex");
398 } else if(opcode == 0xEA) { ins("nop");
399 } else if(opcode == 0x1A) { ins("inc");
400 } else if(opcode == 0x3A) { ins("dec");
401 } else if(opcode == 0x5A) { ins("phy");
402 } else if(opcode == 0x7A) { ins("ply");
403 } else if(opcode == 0x9A) { ins("txs");
404 } else if(opcode == 0xBA) { ins("tsx");
405 } else if(opcode == 0xDA) { ins("phx");
406 } else if(opcode == 0xFA) { ins("plx");
407
408 } else if(opcode == 0x0B) { ins("phd");
409 } else if(opcode == 0x2B) { ins("pld");
410 } else if(opcode == 0x4B) { ins("phk");
411 } else if(opcode == 0x6B) { ins("rtl"); if(flag_return) { mflag = mflag_default; xflag = xflag_default; }
412 } else if(opcode == 0x8B) { ins("phb");
413 } else if(opcode == 0xAB) { ins("plb");
414 } else if(opcode == 0xCB) { ins("wai");
415 } else if(opcode == 0xEB) { ins("xba");
416 } else if(opcode == 0x1B) { ins("tcs");
417 } else if(opcode == 0x3B) { ins("tsc");
418 } else if(opcode == 0x5B) { ins("tcd");
419 } else if(opcode == 0x7B) { ins("tdc");
420 } else if(opcode == 0x9B) { ins("txy");
421 } else if(opcode == 0xBB) { ins("tyx");
422 } else if(opcode == 0xDB) { ins("stp");
423 } else if(opcode == 0xFB) { ins("xce");
424
425 } else if(opcode == 0x0C) { ins("tsb %W");
426 } else if(opcode == 0x2C) { ins("bit %W");
427 } else if(opcode == 0x4C) { ins("jmp %W");
428 } else if(opcode == 0x6C) { ins("jmp (%W)");
429 } else if(opcode == 0x8C) { ins("sty %W");
430 } else if(opcode == 0xAC) { ins("ldy %W");
431 } else if(opcode == 0xCC) { ins("cpy %W");
432 } else if(opcode == 0xEC) { ins("cpx %W");
433 } else if(opcode == 0x1C) { ins("trb %W");
434 } else if(opcode == 0x3C) { ins("bit %W,x");
435 } else if(opcode == 0x5C) { ins("jmp %L");
436 } else if(opcode == 0x7C) { ins("jmp (%W,x)");
437 } else if(opcode == 0x9C) { ins("stz %W");
438 } else if(opcode == 0xBC) { ins("ldy %W,x");
439 } else if(opcode == 0xDC) { ins("jmp [%W]");
440 } else if(opcode == 0xFC) { ins("jsr (%W,x)");
441
442 } else if((opcode & 0x1F) == 0x0D) { ins("%A %W" , opcode);
443 } else if((opcode & 0x1F) == 0x1D) { ins("%A %W,x" , opcode);
444
445 } else if(((opcode & 0x1F) == 0x0E) && ((opcode & 0xC0) != 0x80)) {
446 ins("%M %W", opcode);
447 } else if(opcode == 0x8E) { ins("stx %W");
448 } else if(opcode == 0xAE) { ins("ldx %W");
449 } else if(((opcode & 0x1F) == 0x1E) && ((opcode & 0xC0) != 0x80)) {
450 ins("%M %W,x", opcode);
451 } else if(opcode == 0x9E) { ins("stz %W,x");
452 } else if(opcode == 0xBE) { ins("ldx %W,y");
453
454 } else if((opcode & 0x1F) == 0x0F) { ins("%A %L" , opcode);
455 } else if((opcode & 0x1F) == 0x1F) { ins("%A %L,x" , opcode);
456 } else { ins("???"); }
457 }
458
459 ////////////////////////////////////////////////////////////////////////////////
460
461 static FILE* infile = NULL;
462 static uint32_t infile_bytes_left;
463
getbyte(void)464 static int getbyte(void) {
465 if(infile_bytes_left) {
466 int n = fgetc(infile);
467 if(n == EOF) {
468 infile_bytes_left = 0;
469 return -1;
470 } else {
471 infile_bytes_left--;
472 return (n & 0xFF);
473 }
474 }
475 return -1;
476 }
477
disasm_range(uint32_t fileoffset,uint32_t len,uint32_t addr)478 static void disasm_range(uint32_t fileoffset, uint32_t len, uint32_t addr) {
479 fseeko(infile, fileoffset, SEEK_SET);
480 dasm_address = addr;
481 infile_bytes_left = len;
482 mflag = mflag_default;
483 xflag = xflag_default;
484 fetchn = unfetchn = 0;
485 while(infile_bytes_left > 0) {
486 disassemble_one();
487 }
488 }
489
gethex(const char * s)490 static uint32_t gethex(const char* s) {
491 if(!s[0]) return 0;
492 if(s[0] == '$') s++;
493 if(s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
494 return (uint32_t)strtoul(s, NULL, 16);
495 }
496
497 ////////////////////////////////////////////////////////////////////////////////
498
main(int argc,char ** argv)499 int main(int argc, char** argv) {
500 const char* infilename;
501 uint32_t arg_start;
502 uint32_t arg_len;
503 uint32_t arg_addr;
504 int option_argn = 4;
505
506 normalize_argv0(argv[0]);
507
508 if(argc < 4) {
509 banner();
510 printf(
511 "Usage: %s imagefile start address [length] [options]\n"
512 "Output is written to stdout. All values must be given in hex.\n"
513 "If no length is given, disassembly will stop at the end of the bank.\n"
514 "Options:\n"
515 " -m0 Assume M flag = 0\n"
516 " -m1 Assume M flag = 1 (default)\n"
517 " -x0 Assume X flag = 0 (default)\n"
518 " -x1 Assume X flag = 1\n"
519 " -noreturn Disable flag reset after RTS/RTL/RTI\n"
520 " -noguess Disable flag guess on BRK/COP/WDM/STP\n"
521 " -nofollow Disable REP/SEP following (not recommended)\n",
522 argv[0]
523 );
524 return 1;
525 }
526
527 infilename = argv[1];
528 arg_start = gethex(argv[2]);
529 arg_addr = gethex(argv[3]);
530
531 arg_len = ((arg_addr & 0xFFFF) ^ 0xFFFF) + 1;
532
533 if((argc >= 5) && (argv[4][0] != '-')) {
534 arg_len = gethex(argv[4]);
535 option_argn = 5;
536 }
537
538 while(option_argn < argc) {
539 const char* s = argv[option_argn];
540 if (!strcmp(s, "-m0")) { mflag_default = 0;
541 } else if(!strcmp(s, "-m1")) { mflag_default = 1;
542 } else if(!strcmp(s, "-x0")) { xflag_default = 0;
543 } else if(!strcmp(s, "-x1")) { xflag_default = 1;
544 } else if(!strcmp(s, "-noreturn")) { flag_return = 0;
545 } else if(!strcmp(s, "-noguess" )) { flag_guess = 0;
546 } else if(!strcmp(s, "-nofollow")) { flag_follow = 0;
547 } else {
548 printf("unknown option: %s\n", s);
549 return 1;
550 }
551 option_argn++;
552 }
553
554 printf(
555 "Disassembly of %s\n"
556 "Starting at offset $%lX for $%lX bytes\n"
557 "65816 address starts at $%lX\n"
558 "return=%s guess=%s follow=%s\n"
559 "\n",
560 infilename,
561 (unsigned long)arg_start,
562 (unsigned long)arg_len,
563 (unsigned long)arg_addr,
564 flag_return ? "on" : "off",
565 flag_guess ? "on" : "off",
566 flag_follow ? "on" : "off"
567 );
568
569 infile = fopen(infilename, "rb");
570 if(!infile) {
571 fprintf(stderr, "Error: %s: %s\n", infilename, strerror(errno));
572 return 1;
573 }
574
575 disasm_range(arg_start, arg_len, arg_addr);
576
577 fclose(infile);
578
579 return 0;
580 }
581
582 ////////////////////////////////////////////////////////////////////////////////
583