1006 lines
36 KiB
Python
1006 lines
36 KiB
Python
|
# @file ConvertMasmToNasm.py
|
||
|
# This script assists with conversion of MASM assembly syntax to NASM
|
||
|
#
|
||
|
# Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.<BR>
|
||
|
#
|
||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||
|
#
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
#
|
||
|
# Import Modules
|
||
|
#
|
||
|
import argparse
|
||
|
import io
|
||
|
import os.path
|
||
|
import re
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
|
||
|
class UnsupportedConversion(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class NoSourceFile(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class UnsupportedArch(Exception):
|
||
|
unsupported = ('aarch64', 'arm', 'ebc', 'ipf')
|
||
|
|
||
|
|
||
|
class CommonUtils:
|
||
|
|
||
|
# Version and Copyright
|
||
|
VersionNumber = "0.01"
|
||
|
__version__ = "%prog Version " + VersionNumber
|
||
|
__copyright__ = "Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved."
|
||
|
__usage__ = "%prog [options] source.asm [destination.nasm]"
|
||
|
|
||
|
def __init__(self, clone=None):
|
||
|
if clone is None:
|
||
|
self.args = self.ProcessCommandLine()
|
||
|
else:
|
||
|
self.args = clone.args
|
||
|
|
||
|
self.unsupportedSyntaxSeen = False
|
||
|
self.src = self.args.source
|
||
|
self.keep = self.args.keep
|
||
|
assert(os.path.exists(self.src))
|
||
|
self.dirmode = os.path.isdir(self.src)
|
||
|
srcExt = os.path.splitext(self.src)[1]
|
||
|
assert (self.dirmode or srcExt != '.nasm')
|
||
|
self.infmode = not self.dirmode and srcExt == '.inf'
|
||
|
self.diff = self.args.diff
|
||
|
self.git = self.args.git
|
||
|
self.force = self.args.force
|
||
|
|
||
|
if clone is None:
|
||
|
self.rootdir = os.getcwd()
|
||
|
self.DetectGit()
|
||
|
else:
|
||
|
self.rootdir = clone.rootdir
|
||
|
self.gitdir = clone.gitdir
|
||
|
self.gitemail = clone.gitemail
|
||
|
|
||
|
def ProcessCommandLine(self):
|
||
|
parser = argparse.ArgumentParser(description=self.__copyright__)
|
||
|
parser.add_argument('--version', action='version',
|
||
|
version='%(prog)s ' + self.VersionNumber)
|
||
|
parser.add_argument("-q", "--quiet", action="store_true",
|
||
|
help="Disable all messages except FATAL ERRORS.")
|
||
|
parser.add_argument("--git", action="store_true",
|
||
|
help="Use git to create commits for each file converted")
|
||
|
parser.add_argument("--keep", action="append", choices=('asm', 's'),
|
||
|
default=[],
|
||
|
help="Don't remove files with this extension")
|
||
|
parser.add_argument("--diff", action="store_true",
|
||
|
help="Show diff of conversion")
|
||
|
parser.add_argument("-f", "--force", action="store_true",
|
||
|
help="Force conversion even if unsupported")
|
||
|
parser.add_argument('source', help='MASM input file')
|
||
|
parser.add_argument('dest', nargs='?',
|
||
|
help='NASM output file (default=input.nasm; - for stdout)')
|
||
|
|
||
|
return parser.parse_args()
|
||
|
|
||
|
def RootRelative(self, path):
|
||
|
result = path
|
||
|
if result.startswith(self.rootdir):
|
||
|
result = result[len(self.rootdir):]
|
||
|
while len(result) > 0 and result[0] in '/\\':
|
||
|
result = result[1:]
|
||
|
return result
|
||
|
|
||
|
def MatchAndSetMo(self, regexp, string):
|
||
|
self.mo = regexp.match(string)
|
||
|
return self.mo is not None
|
||
|
|
||
|
def SearchAndSetMo(self, regexp, string):
|
||
|
self.mo = regexp.search(string)
|
||
|
return self.mo is not None
|
||
|
|
||
|
def ReplacePreserveSpacing(self, string, find, replace):
|
||
|
if len(find) >= len(replace):
|
||
|
padded = replace + (' ' * (len(find) - len(replace)))
|
||
|
return string.replace(find, padded)
|
||
|
elif find.find(replace) >= 0:
|
||
|
return string.replace(find, replace)
|
||
|
else:
|
||
|
lenDiff = len(replace) - len(find)
|
||
|
result = string
|
||
|
for i in range(lenDiff, -1, -1):
|
||
|
padded = find + (' ' * i)
|
||
|
result = result.replace(padded, replace)
|
||
|
return result
|
||
|
|
||
|
def DetectGit(self):
|
||
|
lastpath = os.path.realpath(self.src)
|
||
|
self.gitdir = None
|
||
|
while True:
|
||
|
path = os.path.split(lastpath)[0]
|
||
|
if path == lastpath:
|
||
|
self.gitemail = None
|
||
|
return
|
||
|
candidate = os.path.join(path, '.git')
|
||
|
if os.path.isdir(candidate):
|
||
|
self.gitdir = candidate
|
||
|
self.gitemail = self.FormatGitEmailAddress()
|
||
|
return
|
||
|
lastpath = path
|
||
|
|
||
|
def FormatGitEmailAddress(self):
|
||
|
if not self.git or not self.gitdir:
|
||
|
return ''
|
||
|
|
||
|
cmd = ('git', 'config', 'user.name')
|
||
|
name = self.RunAndCaptureOutput(cmd).strip()
|
||
|
cmd = ('git', 'config', 'user.email')
|
||
|
email = self.RunAndCaptureOutput(cmd).strip()
|
||
|
if name.find(',') >= 0:
|
||
|
name = '"' + name + '"'
|
||
|
return name + ' <' + email + '>'
|
||
|
|
||
|
def RunAndCaptureOutput(self, cmd, checkExitCode=True, pipeIn=None):
|
||
|
if pipeIn:
|
||
|
subpStdin = subprocess.PIPE
|
||
|
else:
|
||
|
subpStdin = None
|
||
|
p = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stdin=subpStdin)
|
||
|
(stdout, stderr) = p.communicate(pipeIn)
|
||
|
if checkExitCode:
|
||
|
if p.returncode != 0:
|
||
|
print('command:', ' '.join(cmd))
|
||
|
print('stdout:', stdout)
|
||
|
print('stderr:', stderr)
|
||
|
print('return:', p.returncode)
|
||
|
assert p.returncode == 0
|
||
|
return stdout.decode('utf-8', 'ignore')
|
||
|
|
||
|
def FileUpdated(self, path):
|
||
|
if not self.git or not self.gitdir:
|
||
|
return
|
||
|
|
||
|
cmd = ('git', 'add', path)
|
||
|
self.RunAndCaptureOutput(cmd)
|
||
|
|
||
|
def FileAdded(self, path):
|
||
|
self.FileUpdated(path)
|
||
|
|
||
|
def RemoveFile(self, path):
|
||
|
if not self.git or not self.gitdir:
|
||
|
return
|
||
|
|
||
|
if self.ShouldKeepFile(path):
|
||
|
return
|
||
|
|
||
|
cmd = ('git', 'rm', path)
|
||
|
self.RunAndCaptureOutput(cmd)
|
||
|
|
||
|
def ShouldKeepFile(self, path):
|
||
|
ext = os.path.splitext(path)[1].lower()
|
||
|
if ext.startswith('.'):
|
||
|
ext = ext[1:]
|
||
|
return ext in self.keep
|
||
|
|
||
|
def FileConversionFinished(self, pkg, module, src, dst):
|
||
|
if not self.git or not self.gitdir:
|
||
|
return
|
||
|
|
||
|
if not self.args.quiet:
|
||
|
print('Committing: Conversion of', dst)
|
||
|
|
||
|
prefix = ' '.join(filter(lambda a: a, [pkg, module]))
|
||
|
message = ''
|
||
|
if self.unsupportedSyntaxSeen:
|
||
|
message += 'ERROR! '
|
||
|
message += '%s: Convert %s to NASM\n' % (prefix, src)
|
||
|
message += '\n'
|
||
|
message += 'The %s script was used to convert\n' % sys.argv[0]
|
||
|
message += '%s to %s\n' % (src, dst)
|
||
|
message += '\n'
|
||
|
message += 'Contributed-under: TianoCore Contribution Agreement 1.0\n'
|
||
|
assert(self.gitemail is not None)
|
||
|
message += 'Signed-off-by: %s\n' % self.gitemail
|
||
|
message = message.encode('utf-8', 'ignore')
|
||
|
|
||
|
cmd = ('git', 'commit', '-F', '-')
|
||
|
self.RunAndCaptureOutput(cmd, pipeIn=message)
|
||
|
|
||
|
|
||
|
class ConvertAsmFile(CommonUtils):
|
||
|
|
||
|
def __init__(self, src, dst, clone):
|
||
|
CommonUtils.__init__(self, clone)
|
||
|
self.ConvertAsmFile(src, dst)
|
||
|
self.FileAdded(dst)
|
||
|
self.RemoveFile(src)
|
||
|
|
||
|
def ConvertAsmFile(self, inputFile, outputFile=None):
|
||
|
self.globals = set()
|
||
|
self.unsupportedSyntaxSeen = False
|
||
|
self.inputFilename = inputFile
|
||
|
if not outputFile:
|
||
|
outputFile = os.path.splitext(inputFile)[0] + '.nasm'
|
||
|
self.outputFilename = outputFile
|
||
|
|
||
|
fullSrc = os.path.realpath(inputFile)
|
||
|
srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
|
||
|
maybeArch = srcParentDir.lower()
|
||
|
if maybeArch in UnsupportedArch.unsupported:
|
||
|
raise UnsupportedArch
|
||
|
self.ia32 = maybeArch == 'ia32'
|
||
|
self.x64 = maybeArch == 'x64'
|
||
|
|
||
|
self.inputFileBase = os.path.basename(self.inputFilename)
|
||
|
self.outputFileBase = os.path.basename(self.outputFilename)
|
||
|
self.output = io.BytesIO()
|
||
|
if not self.args.quiet:
|
||
|
dirpath, src = os.path.split(self.inputFilename)
|
||
|
dirpath = self.RootRelative(dirpath)
|
||
|
dst = os.path.basename(self.outputFilename)
|
||
|
print('Converting:', dirpath, src, '->', dst)
|
||
|
lines = io.open(self.inputFilename).readlines()
|
||
|
self.Convert(lines)
|
||
|
if self.outputFilename == '-' and not self.diff:
|
||
|
output_data = self.output.getvalue()
|
||
|
if sys.version_info >= (3, 0):
|
||
|
output_data = output_data.decode('utf-8', 'ignore')
|
||
|
sys.stdout.write(output_data)
|
||
|
self.output.close()
|
||
|
else:
|
||
|
f = io.open(self.outputFilename, 'wb')
|
||
|
f.write(self.output.getvalue())
|
||
|
f.close()
|
||
|
self.output.close()
|
||
|
|
||
|
endOfLineRe = re.compile(r'''
|
||
|
\s* ( ; .* )? \n $
|
||
|
''',
|
||
|
re.VERBOSE | re.MULTILINE
|
||
|
)
|
||
|
begOfLineRe = re.compile(r'''
|
||
|
\s*
|
||
|
''',
|
||
|
re.VERBOSE
|
||
|
)
|
||
|
|
||
|
def Convert(self, lines):
|
||
|
self.proc = None
|
||
|
self.anonLabelCount = -1
|
||
|
output = self.output
|
||
|
self.oldAsmEmptyLineCount = 0
|
||
|
self.newAsmEmptyLineCount = 0
|
||
|
for line in lines:
|
||
|
mo = self.begOfLineRe.search(line)
|
||
|
assert mo is not None
|
||
|
self.indent = mo.group()
|
||
|
lineWithoutBeginning = line[len(self.indent):]
|
||
|
mo = self.endOfLineRe.search(lineWithoutBeginning)
|
||
|
if mo is None:
|
||
|
endOfLine = ''
|
||
|
else:
|
||
|
endOfLine = mo.group()
|
||
|
oldAsm = line[len(self.indent):len(line) - len(endOfLine)]
|
||
|
self.originalLine = line.rstrip()
|
||
|
if line.strip() == '':
|
||
|
self.oldAsmEmptyLineCount += 1
|
||
|
self.TranslateAsm(oldAsm, endOfLine)
|
||
|
if line.strip() != '':
|
||
|
self.oldAsmEmptyLineCount = 0
|
||
|
|
||
|
procDeclRe = re.compile(r'''
|
||
|
(?: ASM_PFX \s* [(] \s* )?
|
||
|
([\w@][\w@0-9]*) \s*
|
||
|
[)]? \s+
|
||
|
PROC
|
||
|
(?: \s+ NEAR | FAR )?
|
||
|
(?: \s+ C )?
|
||
|
(?: \s+ (PUBLIC | PRIVATE) )?
|
||
|
(?: \s+ USES ( (?: \s+ \w[\w0-9]* )+ ) )?
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
procEndRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+
|
||
|
ENDP
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
varAndTypeSubRe = r' (?: [\w@][\w@0-9]* ) (?: \s* : \s* \w+ )? '
|
||
|
publicRe = re.compile(r'''
|
||
|
PUBLIC \s+
|
||
|
( %s (?: \s* , \s* %s )* )
|
||
|
\s* $
|
||
|
''' % (varAndTypeSubRe, varAndTypeSubRe),
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
varAndTypeSubRe = re.compile(varAndTypeSubRe, re.VERBOSE | re.IGNORECASE)
|
||
|
|
||
|
macroDeclRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+
|
||
|
MACRO
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
sectionDeclRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+
|
||
|
( SECTION | ENDS )
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
externRe = re.compile(r'''
|
||
|
EXTE?RN \s+ (?: C \s+ )?
|
||
|
([\w@][\w@0-9]*) \s* : \s* (\w+)
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
externdefRe = re.compile(r'''
|
||
|
EXTERNDEF \s+ (?: C \s+ )?
|
||
|
([\w@][\w@0-9]*) \s* : \s* (\w+)
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
protoRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+
|
||
|
PROTO
|
||
|
(?: \s+ .* )?
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
defineDataRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+
|
||
|
( db | dw | dd | dq ) \s+
|
||
|
( .*? )
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
equRe = re.compile(r'''
|
||
|
([\w@][\w@0-9]*) \s+ EQU \s+ (\S.*?)
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
ignoreRe = re.compile(r'''
|
||
|
\. (?: const |
|
||
|
mmx |
|
||
|
model |
|
||
|
xmm |
|
||
|
x?list |
|
||
|
[3-6]86p?
|
||
|
) |
|
||
|
page
|
||
|
(?: \s+ .* )?
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
whitespaceRe = re.compile(r'\s+', re.MULTILINE)
|
||
|
|
||
|
def TranslateAsm(self, oldAsm, endOfLine):
|
||
|
assert(oldAsm.strip() == oldAsm)
|
||
|
|
||
|
endOfLine = endOfLine.replace(self.inputFileBase, self.outputFileBase)
|
||
|
|
||
|
oldOp = oldAsm.split()
|
||
|
if len(oldOp) >= 1:
|
||
|
oldOp = oldOp[0]
|
||
|
else:
|
||
|
oldOp = ''
|
||
|
|
||
|
if oldAsm == '':
|
||
|
newAsm = oldAsm
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif oldOp in ('#include', ):
|
||
|
newAsm = oldAsm
|
||
|
self.EmitLine(oldAsm + endOfLine)
|
||
|
elif oldOp.lower() in ('end', 'title', 'text'):
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif oldAsm.lower() == '@@:':
|
||
|
self.anonLabelCount += 1
|
||
|
self.EmitLine(self.anonLabel(self.anonLabelCount) + ':')
|
||
|
elif self.MatchAndSetMo(self.ignoreRe, oldAsm):
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif oldAsm.lower() == 'ret':
|
||
|
for i in range(len(self.uses) - 1, -1, -1):
|
||
|
register = self.uses[i]
|
||
|
self.EmitNewContent('pop ' + register)
|
||
|
newAsm = 'ret'
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
self.uses = tuple()
|
||
|
elif oldOp.lower() == 'lea':
|
||
|
newAsm = self.ConvertLea(oldAsm)
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif oldAsm.lower() == 'end':
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
self.uses = tuple()
|
||
|
elif self.MatchAndSetMo(self.equRe, oldAsm):
|
||
|
equ = self.mo.group(1)
|
||
|
newAsm = '%%define %s %s' % (equ, self.mo.group(2))
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.externRe, oldAsm) or \
|
||
|
self.MatchAndSetMo(self.protoRe, oldAsm):
|
||
|
extern = self.mo.group(1)
|
||
|
self.NewGlobal(extern)
|
||
|
newAsm = 'extern ' + extern
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.externdefRe, oldAsm):
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.macroDeclRe, oldAsm):
|
||
|
newAsm = '%%macro %s 0' % self.mo.group(1)
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif oldOp.lower() == 'endm':
|
||
|
newAsm = r'%endmacro'
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.sectionDeclRe, oldAsm):
|
||
|
name = self.mo.group(1)
|
||
|
ty = self.mo.group(2)
|
||
|
if ty.lower() == 'section':
|
||
|
newAsm = '.' + name
|
||
|
else:
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.procDeclRe, oldAsm):
|
||
|
proc = self.proc = self.mo.group(1)
|
||
|
visibility = self.mo.group(2)
|
||
|
if visibility is None:
|
||
|
visibility = ''
|
||
|
else:
|
||
|
visibility = visibility.lower()
|
||
|
if visibility != 'private':
|
||
|
self.NewGlobal(self.proc)
|
||
|
proc = 'ASM_PFX(' + proc + ')'
|
||
|
self.EmitNewContent('global ' + proc)
|
||
|
newAsm = proc + ':'
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
uses = self.mo.group(3)
|
||
|
if uses is not None:
|
||
|
uses = tuple(filter(None, uses.split()))
|
||
|
else:
|
||
|
uses = tuple()
|
||
|
self.uses = uses
|
||
|
for register in self.uses:
|
||
|
self.EmitNewContent(' push ' + register)
|
||
|
elif self.MatchAndSetMo(self.procEndRe, oldAsm):
|
||
|
newAsm = ''
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.publicRe, oldAsm):
|
||
|
publics = re.findall(self.varAndTypeSubRe, self.mo.group(1))
|
||
|
publics = tuple(map(lambda p: p.split(':')[0].strip(), publics))
|
||
|
for i in range(len(publics) - 1):
|
||
|
name = publics[i]
|
||
|
self.EmitNewContent('global ASM_PFX(%s)' % publics[i])
|
||
|
self.NewGlobal(name)
|
||
|
name = publics[-1]
|
||
|
self.NewGlobal(name)
|
||
|
newAsm = 'global ASM_PFX(%s)' % name
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
elif self.MatchAndSetMo(self.defineDataRe, oldAsm):
|
||
|
name = self.mo.group(1)
|
||
|
ty = self.mo.group(2)
|
||
|
value = self.mo.group(3)
|
||
|
if value == '?':
|
||
|
value = 0
|
||
|
newAsm = '%s: %s %s' % (name, ty, value)
|
||
|
newAsm = self.CommonConversions(newAsm)
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
else:
|
||
|
newAsm = self.CommonConversions(oldAsm)
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
|
||
|
def NewGlobal(self, name):
|
||
|
regex = re.compile(r'(?<![_\w\d])(?<!ASM_PFX\()(' + re.escape(name) +
|
||
|
r')(?![_\w\d])')
|
||
|
self.globals.add(regex)
|
||
|
|
||
|
def ConvertAnonymousLabels(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
anonLabel = self.anonLabel(self.anonLabelCount)
|
||
|
newAsm = newAsm.replace('@b', anonLabel)
|
||
|
newAsm = newAsm.replace('@B', anonLabel)
|
||
|
anonLabel = self.anonLabel(self.anonLabelCount + 1)
|
||
|
newAsm = newAsm.replace('@f', anonLabel)
|
||
|
newAsm = newAsm.replace('@F', anonLabel)
|
||
|
return newAsm
|
||
|
|
||
|
def anonLabel(self, count):
|
||
|
return '.%d' % count
|
||
|
|
||
|
def EmitString(self, string):
|
||
|
self.output.write(string.encode('utf-8', 'ignore'))
|
||
|
|
||
|
def EmitLineWithDiff(self, old, new):
|
||
|
newLine = (self.indent + new).rstrip()
|
||
|
if self.diff:
|
||
|
if old is None:
|
||
|
print('+%s' % newLine)
|
||
|
elif newLine != old:
|
||
|
print('-%s' % old)
|
||
|
print('+%s' % newLine)
|
||
|
else:
|
||
|
print('', newLine)
|
||
|
if newLine != '':
|
||
|
self.newAsmEmptyLineCount = 0
|
||
|
self.EmitString(newLine + '\r\n')
|
||
|
|
||
|
def EmitLine(self, string):
|
||
|
self.EmitLineWithDiff(self.originalLine, string)
|
||
|
|
||
|
def EmitNewContent(self, string):
|
||
|
self.EmitLineWithDiff(None, string)
|
||
|
|
||
|
def EmitAsmReplaceOp(self, oldAsm, oldOp, newOp, endOfLine):
|
||
|
newAsm = oldAsm.replace(oldOp, newOp, 1)
|
||
|
self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
|
||
|
|
||
|
hexNumRe = re.compile(r'0*((?=[\da-f])\d*(?<=\d)[\da-f]*)h', re.IGNORECASE)
|
||
|
|
||
|
def EmitAsmWithComment(self, oldAsm, newAsm, endOfLine):
|
||
|
for glblRe in self.globals:
|
||
|
newAsm = glblRe.sub(r'ASM_PFX(\1)', newAsm)
|
||
|
|
||
|
newAsm = self.hexNumRe.sub(r'0x\1', newAsm)
|
||
|
|
||
|
newLine = newAsm + endOfLine
|
||
|
emitNewLine = ((newLine.strip() != '') or
|
||
|
((oldAsm + endOfLine).strip() == ''))
|
||
|
if emitNewLine and newLine.strip() == '':
|
||
|
self.newAsmEmptyLineCount += 1
|
||
|
if self.newAsmEmptyLineCount > 1:
|
||
|
emitNewLine = False
|
||
|
if emitNewLine:
|
||
|
self.EmitLine(newLine.rstrip())
|
||
|
elif self.diff:
|
||
|
print('-%s' % self.originalLine)
|
||
|
|
||
|
leaRe = re.compile(r'''
|
||
|
(lea \s+) ([\w@][\w@0-9]*) \s* , \s* (\S (?:.*\S)?)
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def ConvertLea(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
if self.MatchAndSetMo(self.leaRe, oldAsm):
|
||
|
lea = self.mo.group(1)
|
||
|
dst = self.mo.group(2)
|
||
|
src = self.mo.group(3)
|
||
|
if src.find('[') < 0:
|
||
|
src = '[' + src + ']'
|
||
|
newAsm = lea + dst + ', ' + src
|
||
|
newAsm = self.CommonConversions(newAsm)
|
||
|
return newAsm
|
||
|
|
||
|
ptrRe = re.compile(r'''
|
||
|
(?<! \S )
|
||
|
([dfq]?word|byte) \s+ (?: ptr ) (\s*)
|
||
|
(?= [[\s] )
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def ConvertPtr(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
while self.SearchAndSetMo(self.ptrRe, newAsm):
|
||
|
ty = self.mo.group(1)
|
||
|
if ty.lower() == 'fword':
|
||
|
ty = ''
|
||
|
else:
|
||
|
ty += self.mo.group(2)
|
||
|
newAsm = newAsm[:self.mo.start(0)] + ty + newAsm[self.mo.end(0):]
|
||
|
return newAsm
|
||
|
|
||
|
labelByteRe = re.compile(r'''
|
||
|
(?: \s+ label \s+ (?: [dfq]?word | byte ) )
|
||
|
(?! \S )
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def ConvertLabelByte(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
if self.SearchAndSetMo(self.labelByteRe, newAsm):
|
||
|
newAsm = newAsm[:self.mo.start(0)] + ':' + newAsm[self.mo.end(0):]
|
||
|
return newAsm
|
||
|
|
||
|
unaryBitwiseOpRe = re.compile(r'''
|
||
|
( NOT )
|
||
|
(?= \s+ \S )
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
binaryBitwiseOpRe = re.compile(r'''
|
||
|
( \S \s+ )
|
||
|
( AND | OR | SHL | SHR )
|
||
|
(?= \s+ \S )
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
bitwiseOpReplacements = {
|
||
|
'not': '~',
|
||
|
'and': '&',
|
||
|
'shl': '<<',
|
||
|
'shr': '>>',
|
||
|
'or': '|',
|
||
|
}
|
||
|
|
||
|
def ConvertBitwiseOp(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
while self.SearchAndSetMo(self.binaryBitwiseOpRe, newAsm):
|
||
|
prefix = self.mo.group(1)
|
||
|
op = self.bitwiseOpReplacements[self.mo.group(2).lower()]
|
||
|
newAsm = newAsm[:self.mo.start(0)] + prefix + op + \
|
||
|
newAsm[self.mo.end(0):]
|
||
|
while self.SearchAndSetMo(self.unaryBitwiseOpRe, newAsm):
|
||
|
op = self.bitwiseOpReplacements[self.mo.group(1).lower()]
|
||
|
newAsm = newAsm[:self.mo.start(0)] + op + newAsm[self.mo.end(0):]
|
||
|
return newAsm
|
||
|
|
||
|
sectionRe = re.compile(r'''
|
||
|
\. ( code |
|
||
|
data
|
||
|
)
|
||
|
(?: \s+ .* )?
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
segmentRe = re.compile(r'''
|
||
|
( code |
|
||
|
data )
|
||
|
(?: \s+ SEGMENT )
|
||
|
(?: \s+ .* )?
|
||
|
\s* $
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def ConvertSection(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
if self.MatchAndSetMo(self.sectionRe, newAsm) or \
|
||
|
self.MatchAndSetMo(self.segmentRe, newAsm):
|
||
|
name = self.mo.group(1).lower()
|
||
|
if name == 'code':
|
||
|
if self.x64:
|
||
|
self.EmitLine('DEFAULT REL')
|
||
|
name = 'text'
|
||
|
newAsm = 'SECTION .' + name
|
||
|
return newAsm
|
||
|
|
||
|
fwordRe = re.compile(r'''
|
||
|
(?<! \S )
|
||
|
fword
|
||
|
(?! \S )
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def FwordUnsupportedCheck(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
if self.SearchAndSetMo(self.fwordRe, newAsm):
|
||
|
newAsm = self.Unsupported(newAsm, 'fword used')
|
||
|
return newAsm
|
||
|
|
||
|
__common_conversion_routines__ = (
|
||
|
ConvertAnonymousLabels,
|
||
|
ConvertPtr,
|
||
|
FwordUnsupportedCheck,
|
||
|
ConvertBitwiseOp,
|
||
|
ConvertLabelByte,
|
||
|
ConvertSection,
|
||
|
)
|
||
|
|
||
|
def CommonConversions(self, oldAsm):
|
||
|
newAsm = oldAsm
|
||
|
for conv in self.__common_conversion_routines__:
|
||
|
newAsm = conv(self, newAsm)
|
||
|
return newAsm
|
||
|
|
||
|
def Unsupported(self, asm, message=None):
|
||
|
if not self.force:
|
||
|
raise UnsupportedConversion
|
||
|
|
||
|
self.unsupportedSyntaxSeen = True
|
||
|
newAsm = '%error conversion unsupported'
|
||
|
if message:
|
||
|
newAsm += '; ' + message
|
||
|
newAsm += ': ' + asm
|
||
|
return newAsm
|
||
|
|
||
|
|
||
|
class ConvertInfFile(CommonUtils):
|
||
|
|
||
|
def __init__(self, inf, clone):
|
||
|
CommonUtils.__init__(self, clone)
|
||
|
self.inf = inf
|
||
|
self.ScanInfAsmFiles()
|
||
|
if self.infmode:
|
||
|
self.ConvertInfAsmFiles()
|
||
|
|
||
|
infSrcRe = re.compile(r'''
|
||
|
\s*
|
||
|
( [\w@][\w@0-9/]* \.(asm|s) )
|
||
|
\s* (?: \| [^#]* )?
|
||
|
\s* (?: \# .* )?
|
||
|
$
|
||
|
''',
|
||
|
re.VERBOSE | re.IGNORECASE
|
||
|
)
|
||
|
|
||
|
def GetInfAsmFileMapping(self):
|
||
|
srcToDst = {'order': []}
|
||
|
for line in self.lines:
|
||
|
line = line.rstrip()
|
||
|
if self.MatchAndSetMo(self.infSrcRe, line):
|
||
|
src = self.mo.group(1)
|
||
|
srcExt = self.mo.group(2)
|
||
|
dst = os.path.splitext(src)[0] + '.nasm'
|
||
|
fullDst = os.path.join(self.dir, dst)
|
||
|
if src not in srcToDst and not os.path.exists(fullDst):
|
||
|
srcToDst[src] = dst
|
||
|
srcToDst['order'].append(src)
|
||
|
return srcToDst
|
||
|
|
||
|
def ScanInfAsmFiles(self):
|
||
|
src = self.inf
|
||
|
assert os.path.isfile(src)
|
||
|
f = io.open(src, 'rt')
|
||
|
self.lines = f.readlines()
|
||
|
f.close()
|
||
|
|
||
|
path = os.path.realpath(self.inf)
|
||
|
(self.dir, inf) = os.path.split(path)
|
||
|
parent = os.path.normpath(self.dir)
|
||
|
(lastpath, self.moduleName) = os.path.split(parent)
|
||
|
self.packageName = None
|
||
|
while True:
|
||
|
lastpath = os.path.normpath(lastpath)
|
||
|
(parent, basename) = os.path.split(lastpath)
|
||
|
if parent == lastpath:
|
||
|
break
|
||
|
if basename.endswith('Pkg'):
|
||
|
self.packageName = basename
|
||
|
break
|
||
|
lastpath = parent
|
||
|
|
||
|
self.srcToDst = self.GetInfAsmFileMapping()
|
||
|
|
||
|
self.dstToSrc = {'order': []}
|
||
|
for src in self.srcToDst['order']:
|
||
|
srcExt = os.path.splitext(src)[1]
|
||
|
dst = self.srcToDst[src]
|
||
|
if dst not in self.dstToSrc:
|
||
|
self.dstToSrc[dst] = [src]
|
||
|
self.dstToSrc['order'].append(dst)
|
||
|
else:
|
||
|
self.dstToSrc[dst].append(src)
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.dstToSrc['order'])
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(self.dstToSrc['order'])
|
||
|
|
||
|
def ConvertInfAsmFiles(self):
|
||
|
notConverted = []
|
||
|
unsupportedArchCount = 0
|
||
|
for dst in self:
|
||
|
didSomething = False
|
||
|
try:
|
||
|
self.UpdateInfAsmFile(dst)
|
||
|
didSomething = True
|
||
|
except UnsupportedConversion:
|
||
|
if not self.args.quiet:
|
||
|
print('MASM=>NASM conversion unsupported for', dst)
|
||
|
notConverted.append(dst)
|
||
|
except NoSourceFile:
|
||
|
if not self.args.quiet:
|
||
|
print('Source file missing for', reldst)
|
||
|
notConverted.append(dst)
|
||
|
except UnsupportedArch:
|
||
|
unsupportedArchCount += 1
|
||
|
else:
|
||
|
if didSomething:
|
||
|
self.ConversionFinished(dst)
|
||
|
if len(notConverted) > 0 and not self.args.quiet:
|
||
|
for dst in notConverted:
|
||
|
reldst = self.RootRelative(dst)
|
||
|
print('Unabled to convert', reldst)
|
||
|
if unsupportedArchCount > 0 and not self.args.quiet:
|
||
|
print('Skipped', unsupportedArchCount, 'files based on architecture')
|
||
|
|
||
|
def UpdateInfAsmFile(self, dst, IgnoreMissingAsm=False):
|
||
|
infPath = os.path.split(os.path.realpath(self.inf))[0]
|
||
|
asmSrc = os.path.splitext(dst)[0] + '.asm'
|
||
|
fullSrc = os.path.join(infPath, asmSrc)
|
||
|
fullDst = os.path.join(infPath, dst)
|
||
|
srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
|
||
|
if srcParentDir.lower() in UnsupportedArch.unsupported:
|
||
|
raise UnsupportedArch
|
||
|
elif not os.path.exists(fullSrc):
|
||
|
if not IgnoreMissingAsm:
|
||
|
raise NoSourceFile
|
||
|
else: # not os.path.exists(fullDst):
|
||
|
conv = ConvertAsmFile(fullSrc, fullDst, self)
|
||
|
self.unsupportedSyntaxSeen = conv.unsupportedSyntaxSeen
|
||
|
|
||
|
fileChanged = False
|
||
|
recentSources = list()
|
||
|
i = 0
|
||
|
while i < len(self.lines):
|
||
|
line = self.lines[i].rstrip()
|
||
|
updatedLine = line
|
||
|
lineChanged = False
|
||
|
preserveOldSource = False
|
||
|
for src in self.dstToSrc[dst]:
|
||
|
assert self.srcToDst[src] == dst
|
||
|
updatedLine = self.ReplacePreserveSpacing(
|
||
|
updatedLine, src, dst)
|
||
|
lineChanged = updatedLine != line
|
||
|
if lineChanged:
|
||
|
preserveOldSource = self.ShouldKeepFile(src)
|
||
|
break
|
||
|
|
||
|
if lineChanged:
|
||
|
if preserveOldSource:
|
||
|
if updatedLine.strip() not in recentSources:
|
||
|
self.lines.insert(i, updatedLine + '\n')
|
||
|
recentSources.append(updatedLine.strip())
|
||
|
i += 1
|
||
|
if self.diff:
|
||
|
print('+%s' % updatedLine)
|
||
|
if self.diff:
|
||
|
print('', line)
|
||
|
else:
|
||
|
if self.diff:
|
||
|
print('-%s' % line)
|
||
|
if updatedLine.strip() in recentSources:
|
||
|
self.lines[i] = None
|
||
|
else:
|
||
|
self.lines[i] = updatedLine + '\n'
|
||
|
recentSources.append(updatedLine.strip())
|
||
|
if self.diff:
|
||
|
print('+%s' % updatedLine)
|
||
|
else:
|
||
|
if len(recentSources) > 0:
|
||
|
recentSources = list()
|
||
|
if self.diff:
|
||
|
print('', line)
|
||
|
|
||
|
fileChanged |= lineChanged
|
||
|
i += 1
|
||
|
|
||
|
if fileChanged:
|
||
|
self.lines = list(filter(lambda l: l is not None, self.lines))
|
||
|
|
||
|
for src in self.dstToSrc[dst]:
|
||
|
if not src.endswith('.asm'):
|
||
|
fullSrc = os.path.join(infPath, src)
|
||
|
if os.path.exists(fullSrc):
|
||
|
self.RemoveFile(fullSrc)
|
||
|
|
||
|
if fileChanged:
|
||
|
f = io.open(self.inf, 'w', newline='\r\n')
|
||
|
f.writelines(self.lines)
|
||
|
f.close()
|
||
|
self.FileUpdated(self.inf)
|
||
|
|
||
|
def ConversionFinished(self, dst):
|
||
|
asmSrc = os.path.splitext(dst)[0] + '.asm'
|
||
|
self.FileConversionFinished(
|
||
|
self.packageName, self.moduleName, asmSrc, dst)
|
||
|
|
||
|
|
||
|
class ConvertInfFiles(CommonUtils):
|
||
|
|
||
|
def __init__(self, infs, clone):
|
||
|
CommonUtils.__init__(self, clone)
|
||
|
infs = map(lambda i: ConvertInfFile(i, self), infs)
|
||
|
infs = filter(lambda i: len(i) > 0, infs)
|
||
|
dstToInfs = {'order': []}
|
||
|
for inf in infs:
|
||
|
for dst in inf:
|
||
|
fulldst = os.path.realpath(os.path.join(inf.dir, dst))
|
||
|
pair = (inf, dst)
|
||
|
if fulldst in dstToInfs:
|
||
|
dstToInfs[fulldst].append(pair)
|
||
|
else:
|
||
|
dstToInfs['order'].append(fulldst)
|
||
|
dstToInfs[fulldst] = [pair]
|
||
|
|
||
|
notConverted = []
|
||
|
unsupportedArchCount = 0
|
||
|
for dst in dstToInfs['order']:
|
||
|
didSomething = False
|
||
|
try:
|
||
|
for inf, reldst in dstToInfs[dst]:
|
||
|
inf.UpdateInfAsmFile(reldst, IgnoreMissingAsm=didSomething)
|
||
|
didSomething = True
|
||
|
except UnsupportedConversion:
|
||
|
if not self.args.quiet:
|
||
|
print('MASM=>NASM conversion unsupported for', reldst)
|
||
|
notConverted.append(dst)
|
||
|
except NoSourceFile:
|
||
|
if not self.args.quiet:
|
||
|
print('Source file missing for', reldst)
|
||
|
notConverted.append(dst)
|
||
|
except UnsupportedArch:
|
||
|
unsupportedArchCount += 1
|
||
|
else:
|
||
|
if didSomething:
|
||
|
inf.ConversionFinished(reldst)
|
||
|
if len(notConverted) > 0 and not self.args.quiet:
|
||
|
for dst in notConverted:
|
||
|
reldst = self.RootRelative(dst)
|
||
|
print('Unabled to convert', reldst)
|
||
|
if unsupportedArchCount > 0 and not self.args.quiet:
|
||
|
print('Skipped', unsupportedArchCount, 'files based on architecture')
|
||
|
|
||
|
|
||
|
class ConvertDirectories(CommonUtils):
|
||
|
|
||
|
def __init__(self, paths, clone):
|
||
|
CommonUtils.__init__(self, clone)
|
||
|
self.paths = paths
|
||
|
self.ConvertInfAndAsmFiles()
|
||
|
|
||
|
def ConvertInfAndAsmFiles(self):
|
||
|
infs = list()
|
||
|
for path in self.paths:
|
||
|
assert(os.path.exists(path))
|
||
|
for path in self.paths:
|
||
|
for root, dirs, files in os.walk(path):
|
||
|
for d in ('.svn', '.git'):
|
||
|
if d in dirs:
|
||
|
dirs.remove(d)
|
||
|
for f in files:
|
||
|
if f.lower().endswith('.inf'):
|
||
|
inf = os.path.realpath(os.path.join(root, f))
|
||
|
infs.append(inf)
|
||
|
|
||
|
ConvertInfFiles(infs, self)
|
||
|
|
||
|
|
||
|
class ConvertAsmApp(CommonUtils):
|
||
|
|
||
|
def __init__(self):
|
||
|
CommonUtils.__init__(self)
|
||
|
|
||
|
src = self.args.source
|
||
|
dst = self.args.dest
|
||
|
if self.infmode:
|
||
|
ConvertInfFiles((src,), self)
|
||
|
elif self.dirmode:
|
||
|
ConvertDirectories((src,), self)
|
||
|
elif not self.dirmode:
|
||
|
ConvertAsmFile(src, dst, self)
|
||
|
|
||
|
ConvertAsmApp()
|