-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathudis.py
More file actions
executable file
·254 lines (211 loc) · 8.48 KB
/
Copy pathudis.py
File metadata and controls
executable file
·254 lines (211 loc) · 8.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#! /usr/bin/env python3
#
# Universal Disassembler
# Copyright (c) 2013-2020 by Jeff Tranter <tranter@pobox.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import argparse
import signal
# Flags
pcr = 1
und = 2
z80bit = 4
# Functions
def isprint(char):
"Return if character is printable ASCII"
return '@' <= char <= '~'
def auto_int(x):
return int(x, 0)
# Avoids an error when output piped, e.g. to "less"
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# Parse command line options
parser = argparse.ArgumentParser()
parser.add_argument("filename", help="Binary file to disassemble")
parser.add_argument("-c", "--cpu", help="Specify CPU type (defaults to 6502)", default="6502")
parser.add_argument("-n", "--nolist", help="Don't list instruction bytes (make output suitable for assembler)", action="store_true")
parser.add_argument("-a", "--address", help="Specify starting address (defaults to 0)", default=0, type=auto_int)
parser.add_argument("-u", "--undocumented", help="Allow undocumented opcodes", action="store_true")
parser.add_argument("-i", "--invalid", help="Show invalid opcodes as ??? rather than constants", action="store_true")
args = parser.parse_args()
# Load CPU plugin based on command line option.
# Looks for plugin in same directory as this program.
dir = os.path.dirname(os.path.realpath(__file__))
plugin = dir + os.sep + args.cpu + ".py"
try:
exec(open(plugin).read())
except FileNotFoundError:
print(("error: CPU plugin file '{}' not found.".format(plugin)), file=sys.stderr)
print("The following CPUs are supported: 1802 6502 65816 65c02 6800 6801/6803 6809 6811 8051 8080 8085 z80")
sys.exit(1)
# Get filename from command line arguments.
filename = args.filename
# Current instruction address. Silently force it to be in valid range.
address = args.address & 0xffff
# Any flags for current instruction.
flags = 0
# Contains a line of output.
line = ""
# Open input file.
# Display error and exit if filename does not exist.
try:
f = open(filename, "rb")
except FileNotFoundError:
print(("error: input file '{}' not found.".format(filename)), file=sys.stderr)
sys.exit(1)
# Variables:
# address - current instruction address
# opcode - binary instruction opcode (may be multiple bytes)
# length - length of current instruction
# mnemonic - assembler mnemonic for current instruction
# format - operand format string
# line - line to output
# leadin - extended opcode (true/false)
s = " "
# Print initial origin address
if args.nolist is False:
print("{0:04X}{1:s}.org ${0:04X}".format(address, s[0:maxLength*3+3]))
else:
print(" .org ${0:04X}".format(address))
while True:
try:
b = f.read(1) # Get binary byte from file
if not b: # handle EOF
if args.nolist is False:
print("{0:04X}{1:s}end".format(address, s[0:maxLength*3+3]))
break
# Get op code
opcode = ord(b)
# Handle if opcode is a leadin byte
if opcode in leadInBytes:
b = f.read(1) # Get next byte of extended opcode
if not b: # Unexpected EOF
break
opcode = (opcode << 8) + ord(b)
leadin = True
else:
leadin = False
# Given opcode, get data from opcode table and address mode table for CPU.
if opcode in opcodeTable:
length = opcodeTable[opcode][0]
mnemonic = opcodeTable[opcode][1]
mode = opcodeTable[opcode][2]
if len(opcodeTable[opcode]) > 3:
flags = opcodeTable[opcode][3] # Get optional flags
else:
flags = 0
if mode in addressModeTable:
format = addressModeTable[mode]
else:
print(("error: mode '{}' not found in addressModeTable.".format(mode)), file=sys.stderr)
sys.exit(1)
else:
length = 1 # Invalid opcode
format = ""
mnemonic = "???"
if flags & 2 == und and not args.undocumented:
# currently only handles one-byte undocumented opcodes
length = 1
format = ""
mnemonic = "???"
# Disassembly format:
# XXXX XX XX XX XX XX nop ($1234,X)
# With --nolist option:
# nop ($1234,X)
# Add current address to output line
if args.nolist is False:
if leadin is True:
line += "{0:04X} {1:02X} {2:02X}".format(address, opcode // 256, opcode % 256)
length -= 1
else:
line += "{0:04X} {1:02X}".format(address, opcode)
op = {} # Array to hold operands
# Get any operands and store in an array
for i in range(1, maxLength):
if i < length:
b = f.read(1)
if not b: # Unexpected EOF
break
op[i] = ord(b) # Get operand bytes
if args.nolist is False:
line += " {0:02X}".format(op[i])
else:
if args.nolist is False and leadin is False and i != length-1:
line += " "
if not b: # Unexpected EOF
break
# Handle relative addresses. Indicated by the flag pcr being set.
# Assumes the operand that needs to be PC relative is the last one.
# Note: Code will need changes if more flags are added.
if flags & pcr:
if op[length-1] < 128:
op[length-1] = address + op[length-1] + length
else:
op[length-1] = address - (256 - op[length-1]) + length
if op[length-1] < 0:
op[length-1] = 65536 + op[length-1]
# Format the operand using format string and any operands.
if length == 1:
operand = format
elif length == 2:
operand = format.format(op[1])
elif length == 3:
if flags & z80bit:
opcode = (opcode << 16) + op[2]
# reread opcode table for real format string
length, mnemonic, mode, flags = opcodeTable[opcode]
format = addressModeTable[mode]
operand = format.format(op[1])
else:
operand = format.format(op[1], op[2])
elif length == 4:
operand = format.format(op[1], op[2], op[3])
elif length == 5:
operand = format.format(op[1], op[2], op[3], op[4])
elif length == 6:
operand = format.format(op[1], op[2], op[3], op[4], op[5])
elif length == 7:
operand = format.format(op[1], op[2], op[3], op[4], op[5], op[6])
# Special check for invalid op code. Display as ??? or .byte depending on command line option.
if mnemonic == "???" and not args.invalid:
# Handle case where invalid opcode has a leadin byte.
if leadin is True:
if args.nolist is False:
mnemonic = "{0:s}.byte ${1:02X},${2:02X}".format(s[0:(maxLength-length-2)*3], opcode // 256, opcode % 256)
else:
mnemonic = ".byte ${0:02X},${1:02X}".format(opcode // 256, opcode % 256)
else:
if isprint(chr(opcode)):
mnemonic = ".byte '{0:c}'".format(opcode)
else:
mnemonic = ".byte ${0:02X}".format(opcode)
# Need one more space if not in no list mode.
if args.nolist is False:
line += " "
# Add mnemonic and any operands to the output line.
if operand == "":
line += " {0:s}".format(mnemonic)
else:
line += " {0:5s} {1:s}".format(mnemonic, operand)
# Print line of output
print(line)
# Update address, handlng wraparound at 64K.
address = (address + length) & 0xffff
# Reset variables for next line of output.
line = ""
operand = ""
flags = 0
except KeyboardInterrupt:
print("Interrupted by Control-C", file=sys.stderr)
break