ConvertMasmToNasm.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. # @file ConvertMasmToNasm.py
  2. # This script assists with conversion of MASM assembly syntax to NASM
  3. #
  4. # Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.<BR>
  5. #
  6. # SPDX-License-Identifier: BSD-2-Clause-Patent
  7. #
  8. from __future__ import print_function
  9. #
  10. # Import Modules
  11. #
  12. import argparse
  13. import io
  14. import os.path
  15. import re
  16. import subprocess
  17. import sys
  18. class UnsupportedConversion(Exception):
  19. pass
  20. class NoSourceFile(Exception):
  21. pass
  22. class UnsupportedArch(Exception):
  23. unsupported = ('aarch64', 'arm', 'ebc', 'ipf')
  24. class CommonUtils:
  25. # Version and Copyright
  26. VersionNumber = "0.01"
  27. __version__ = "%prog Version " + VersionNumber
  28. __copyright__ = "Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved."
  29. __usage__ = "%prog [options] source.asm [destination.nasm]"
  30. def __init__(self, clone=None):
  31. if clone is None:
  32. self.args = self.ProcessCommandLine()
  33. else:
  34. self.args = clone.args
  35. self.unsupportedSyntaxSeen = False
  36. self.src = self.args.source
  37. self.keep = self.args.keep
  38. assert(os.path.exists(self.src))
  39. self.dirmode = os.path.isdir(self.src)
  40. srcExt = os.path.splitext(self.src)[1]
  41. assert (self.dirmode or srcExt != '.nasm')
  42. self.infmode = not self.dirmode and srcExt == '.inf'
  43. self.diff = self.args.diff
  44. self.git = self.args.git
  45. self.force = self.args.force
  46. if clone is None:
  47. self.rootdir = os.getcwd()
  48. self.DetectGit()
  49. else:
  50. self.rootdir = clone.rootdir
  51. self.gitdir = clone.gitdir
  52. self.gitemail = clone.gitemail
  53. def ProcessCommandLine(self):
  54. parser = argparse.ArgumentParser(description=self.__copyright__)
  55. parser.add_argument('--version', action='version',
  56. version='%(prog)s ' + self.VersionNumber)
  57. parser.add_argument("-q", "--quiet", action="store_true",
  58. help="Disable all messages except FATAL ERRORS.")
  59. parser.add_argument("--git", action="store_true",
  60. help="Use git to create commits for each file converted")
  61. parser.add_argument("--keep", action="append", choices=('asm', 's'),
  62. default=[],
  63. help="Don't remove files with this extension")
  64. parser.add_argument("--diff", action="store_true",
  65. help="Show diff of conversion")
  66. parser.add_argument("-f", "--force", action="store_true",
  67. help="Force conversion even if unsupported")
  68. parser.add_argument('source', help='MASM input file')
  69. parser.add_argument('dest', nargs='?',
  70. help='NASM output file (default=input.nasm; - for stdout)')
  71. return parser.parse_args()
  72. def RootRelative(self, path):
  73. result = path
  74. if result.startswith(self.rootdir):
  75. result = result[len(self.rootdir):]
  76. while len(result) > 0 and result[0] in '/\\':
  77. result = result[1:]
  78. return result
  79. def MatchAndSetMo(self, regexp, string):
  80. self.mo = regexp.match(string)
  81. return self.mo is not None
  82. def SearchAndSetMo(self, regexp, string):
  83. self.mo = regexp.search(string)
  84. return self.mo is not None
  85. def ReplacePreserveSpacing(self, string, find, replace):
  86. if len(find) >= len(replace):
  87. padded = replace + (' ' * (len(find) - len(replace)))
  88. return string.replace(find, padded)
  89. elif find.find(replace) >= 0:
  90. return string.replace(find, replace)
  91. else:
  92. lenDiff = len(replace) - len(find)
  93. result = string
  94. for i in range(lenDiff, -1, -1):
  95. padded = find + (' ' * i)
  96. result = result.replace(padded, replace)
  97. return result
  98. def DetectGit(self):
  99. lastpath = os.path.realpath(self.src)
  100. self.gitdir = None
  101. while True:
  102. path = os.path.split(lastpath)[0]
  103. if path == lastpath:
  104. self.gitemail = None
  105. return
  106. candidate = os.path.join(path, '.git')
  107. if os.path.isdir(candidate):
  108. self.gitdir = candidate
  109. self.gitemail = self.FormatGitEmailAddress()
  110. return
  111. lastpath = path
  112. def FormatGitEmailAddress(self):
  113. if not self.git or not self.gitdir:
  114. return ''
  115. cmd = ('git', 'config', 'user.name')
  116. name = self.RunAndCaptureOutput(cmd).strip()
  117. cmd = ('git', 'config', 'user.email')
  118. email = self.RunAndCaptureOutput(cmd).strip()
  119. if name.find(',') >= 0:
  120. name = '"' + name + '"'
  121. return name + ' <' + email + '>'
  122. def RunAndCaptureOutput(self, cmd, checkExitCode=True, pipeIn=None):
  123. if pipeIn:
  124. subpStdin = subprocess.PIPE
  125. else:
  126. subpStdin = None
  127. p = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stdin=subpStdin)
  128. (stdout, stderr) = p.communicate(pipeIn)
  129. if checkExitCode:
  130. if p.returncode != 0:
  131. print('command:', ' '.join(cmd))
  132. print('stdout:', stdout)
  133. print('stderr:', stderr)
  134. print('return:', p.returncode)
  135. assert p.returncode == 0
  136. return stdout.decode('utf-8', 'ignore')
  137. def FileUpdated(self, path):
  138. if not self.git or not self.gitdir:
  139. return
  140. cmd = ('git', 'add', path)
  141. self.RunAndCaptureOutput(cmd)
  142. def FileAdded(self, path):
  143. self.FileUpdated(path)
  144. def RemoveFile(self, path):
  145. if not self.git or not self.gitdir:
  146. return
  147. if self.ShouldKeepFile(path):
  148. return
  149. cmd = ('git', 'rm', path)
  150. self.RunAndCaptureOutput(cmd)
  151. def ShouldKeepFile(self, path):
  152. ext = os.path.splitext(path)[1].lower()
  153. if ext.startswith('.'):
  154. ext = ext[1:]
  155. return ext in self.keep
  156. def FileConversionFinished(self, pkg, module, src, dst):
  157. if not self.git or not self.gitdir:
  158. return
  159. if not self.args.quiet:
  160. print('Committing: Conversion of', dst)
  161. prefix = ' '.join(filter(lambda a: a, [pkg, module]))
  162. message = ''
  163. if self.unsupportedSyntaxSeen:
  164. message += 'ERROR! '
  165. message += '%s: Convert %s to NASM\n' % (prefix, src)
  166. message += '\n'
  167. message += 'The %s script was used to convert\n' % sys.argv[0]
  168. message += '%s to %s\n' % (src, dst)
  169. message += '\n'
  170. message += 'Contributed-under: TianoCore Contribution Agreement 1.0\n'
  171. assert(self.gitemail is not None)
  172. message += 'Signed-off-by: %s\n' % self.gitemail
  173. message = message.encode('utf-8', 'ignore')
  174. cmd = ('git', 'commit', '-F', '-')
  175. self.RunAndCaptureOutput(cmd, pipeIn=message)
  176. class ConvertAsmFile(CommonUtils):
  177. def __init__(self, src, dst, clone):
  178. CommonUtils.__init__(self, clone)
  179. self.ConvertAsmFile(src, dst)
  180. self.FileAdded(dst)
  181. self.RemoveFile(src)
  182. def ConvertAsmFile(self, inputFile, outputFile=None):
  183. self.globals = set()
  184. self.unsupportedSyntaxSeen = False
  185. self.inputFilename = inputFile
  186. if not outputFile:
  187. outputFile = os.path.splitext(inputFile)[0] + '.nasm'
  188. self.outputFilename = outputFile
  189. fullSrc = os.path.realpath(inputFile)
  190. srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
  191. maybeArch = srcParentDir.lower()
  192. if maybeArch in UnsupportedArch.unsupported:
  193. raise UnsupportedArch
  194. self.ia32 = maybeArch == 'ia32'
  195. self.x64 = maybeArch == 'x64'
  196. self.inputFileBase = os.path.basename(self.inputFilename)
  197. self.outputFileBase = os.path.basename(self.outputFilename)
  198. self.output = io.BytesIO()
  199. if not self.args.quiet:
  200. dirpath, src = os.path.split(self.inputFilename)
  201. dirpath = self.RootRelative(dirpath)
  202. dst = os.path.basename(self.outputFilename)
  203. print('Converting:', dirpath, src, '->', dst)
  204. lines = io.open(self.inputFilename).readlines()
  205. self.Convert(lines)
  206. if self.outputFilename == '-' and not self.diff:
  207. output_data = self.output.getvalue()
  208. if sys.version_info >= (3, 0):
  209. output_data = output_data.decode('utf-8', 'ignore')
  210. sys.stdout.write(output_data)
  211. self.output.close()
  212. else:
  213. f = io.open(self.outputFilename, 'wb')
  214. f.write(self.output.getvalue())
  215. f.close()
  216. self.output.close()
  217. endOfLineRe = re.compile(r'''
  218. \s* ( ; .* )? \n $
  219. ''',
  220. re.VERBOSE | re.MULTILINE
  221. )
  222. begOfLineRe = re.compile(r'''
  223. \s*
  224. ''',
  225. re.VERBOSE
  226. )
  227. def Convert(self, lines):
  228. self.proc = None
  229. self.anonLabelCount = -1
  230. output = self.output
  231. self.oldAsmEmptyLineCount = 0
  232. self.newAsmEmptyLineCount = 0
  233. for line in lines:
  234. mo = self.begOfLineRe.search(line)
  235. assert mo is not None
  236. self.indent = mo.group()
  237. lineWithoutBeginning = line[len(self.indent):]
  238. mo = self.endOfLineRe.search(lineWithoutBeginning)
  239. if mo is None:
  240. endOfLine = ''
  241. else:
  242. endOfLine = mo.group()
  243. oldAsm = line[len(self.indent):len(line) - len(endOfLine)]
  244. self.originalLine = line.rstrip()
  245. if line.strip() == '':
  246. self.oldAsmEmptyLineCount += 1
  247. self.TranslateAsm(oldAsm, endOfLine)
  248. if line.strip() != '':
  249. self.oldAsmEmptyLineCount = 0
  250. procDeclRe = re.compile(r'''
  251. (?: ASM_PFX \s* [(] \s* )?
  252. ([\w@][\w@0-9]*) \s*
  253. [)]? \s+
  254. PROC
  255. (?: \s+ NEAR | FAR )?
  256. (?: \s+ C )?
  257. (?: \s+ (PUBLIC | PRIVATE) )?
  258. (?: \s+ USES ( (?: \s+ \w[\w0-9]* )+ ) )?
  259. \s* $
  260. ''',
  261. re.VERBOSE | re.IGNORECASE
  262. )
  263. procEndRe = re.compile(r'''
  264. ([\w@][\w@0-9]*) \s+
  265. ENDP
  266. \s* $
  267. ''',
  268. re.VERBOSE | re.IGNORECASE
  269. )
  270. varAndTypeSubRe = r' (?: [\w@][\w@0-9]* ) (?: \s* : \s* \w+ )? '
  271. publicRe = re.compile(r'''
  272. PUBLIC \s+
  273. ( %s (?: \s* , \s* %s )* )
  274. \s* $
  275. ''' % (varAndTypeSubRe, varAndTypeSubRe),
  276. re.VERBOSE | re.IGNORECASE
  277. )
  278. varAndTypeSubRe = re.compile(varAndTypeSubRe, re.VERBOSE | re.IGNORECASE)
  279. macroDeclRe = re.compile(r'''
  280. ([\w@][\w@0-9]*) \s+
  281. MACRO
  282. \s* $
  283. ''',
  284. re.VERBOSE | re.IGNORECASE
  285. )
  286. sectionDeclRe = re.compile(r'''
  287. ([\w@][\w@0-9]*) \s+
  288. ( SECTION | ENDS )
  289. \s* $
  290. ''',
  291. re.VERBOSE | re.IGNORECASE
  292. )
  293. externRe = re.compile(r'''
  294. EXTE?RN \s+ (?: C \s+ )?
  295. ([\w@][\w@0-9]*) \s* : \s* (\w+)
  296. \s* $
  297. ''',
  298. re.VERBOSE | re.IGNORECASE
  299. )
  300. externdefRe = re.compile(r'''
  301. EXTERNDEF \s+ (?: C \s+ )?
  302. ([\w@][\w@0-9]*) \s* : \s* (\w+)
  303. \s* $
  304. ''',
  305. re.VERBOSE | re.IGNORECASE
  306. )
  307. protoRe = re.compile(r'''
  308. ([\w@][\w@0-9]*) \s+
  309. PROTO
  310. (?: \s+ .* )?
  311. \s* $
  312. ''',
  313. re.VERBOSE | re.IGNORECASE
  314. )
  315. defineDataRe = re.compile(r'''
  316. ([\w@][\w@0-9]*) \s+
  317. ( db | dw | dd | dq ) \s+
  318. ( .*? )
  319. \s* $
  320. ''',
  321. re.VERBOSE | re.IGNORECASE
  322. )
  323. equRe = re.compile(r'''
  324. ([\w@][\w@0-9]*) \s+ EQU \s+ (\S.*?)
  325. \s* $
  326. ''',
  327. re.VERBOSE | re.IGNORECASE
  328. )
  329. ignoreRe = re.compile(r'''
  330. \. (?: const |
  331. mmx |
  332. model |
  333. xmm |
  334. x?list |
  335. [3-6]86p?
  336. ) |
  337. page
  338. (?: \s+ .* )?
  339. \s* $
  340. ''',
  341. re.VERBOSE | re.IGNORECASE
  342. )
  343. whitespaceRe = re.compile(r'\s+', re.MULTILINE)
  344. def TranslateAsm(self, oldAsm, endOfLine):
  345. assert(oldAsm.strip() == oldAsm)
  346. endOfLine = endOfLine.replace(self.inputFileBase, self.outputFileBase)
  347. oldOp = oldAsm.split()
  348. if len(oldOp) >= 1:
  349. oldOp = oldOp[0]
  350. else:
  351. oldOp = ''
  352. if oldAsm == '':
  353. newAsm = oldAsm
  354. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  355. elif oldOp in ('#include', ):
  356. newAsm = oldAsm
  357. self.EmitLine(oldAsm + endOfLine)
  358. elif oldOp.lower() in ('end', 'title', 'text'):
  359. newAsm = ''
  360. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  361. elif oldAsm.lower() == '@@:':
  362. self.anonLabelCount += 1
  363. self.EmitLine(self.anonLabel(self.anonLabelCount) + ':')
  364. elif self.MatchAndSetMo(self.ignoreRe, oldAsm):
  365. newAsm = ''
  366. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  367. elif oldAsm.lower() == 'ret':
  368. for i in range(len(self.uses) - 1, -1, -1):
  369. register = self.uses[i]
  370. self.EmitNewContent('pop ' + register)
  371. newAsm = 'ret'
  372. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  373. self.uses = tuple()
  374. elif oldOp.lower() == 'lea':
  375. newAsm = self.ConvertLea(oldAsm)
  376. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  377. elif oldAsm.lower() == 'end':
  378. newAsm = ''
  379. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  380. self.uses = tuple()
  381. elif self.MatchAndSetMo(self.equRe, oldAsm):
  382. equ = self.mo.group(1)
  383. newAsm = '%%define %s %s' % (equ, self.mo.group(2))
  384. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  385. elif self.MatchAndSetMo(self.externRe, oldAsm) or \
  386. self.MatchAndSetMo(self.protoRe, oldAsm):
  387. extern = self.mo.group(1)
  388. self.NewGlobal(extern)
  389. newAsm = 'extern ' + extern
  390. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  391. elif self.MatchAndSetMo(self.externdefRe, oldAsm):
  392. newAsm = ''
  393. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  394. elif self.MatchAndSetMo(self.macroDeclRe, oldAsm):
  395. newAsm = '%%macro %s 0' % self.mo.group(1)
  396. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  397. elif oldOp.lower() == 'endm':
  398. newAsm = r'%endmacro'
  399. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  400. elif self.MatchAndSetMo(self.sectionDeclRe, oldAsm):
  401. name = self.mo.group(1)
  402. ty = self.mo.group(2)
  403. if ty.lower() == 'section':
  404. newAsm = '.' + name
  405. else:
  406. newAsm = ''
  407. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  408. elif self.MatchAndSetMo(self.procDeclRe, oldAsm):
  409. proc = self.proc = self.mo.group(1)
  410. visibility = self.mo.group(2)
  411. if visibility is None:
  412. visibility = ''
  413. else:
  414. visibility = visibility.lower()
  415. if visibility != 'private':
  416. self.NewGlobal(self.proc)
  417. proc = 'ASM_PFX(' + proc + ')'
  418. self.EmitNewContent('global ' + proc)
  419. newAsm = proc + ':'
  420. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  421. uses = self.mo.group(3)
  422. if uses is not None:
  423. uses = tuple(filter(None, uses.split()))
  424. else:
  425. uses = tuple()
  426. self.uses = uses
  427. for register in self.uses:
  428. self.EmitNewContent(' push ' + register)
  429. elif self.MatchAndSetMo(self.procEndRe, oldAsm):
  430. newAsm = ''
  431. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  432. elif self.MatchAndSetMo(self.publicRe, oldAsm):
  433. publics = re.findall(self.varAndTypeSubRe, self.mo.group(1))
  434. publics = tuple(map(lambda p: p.split(':')[0].strip(), publics))
  435. for i in range(len(publics) - 1):
  436. name = publics[i]
  437. self.EmitNewContent('global ASM_PFX(%s)' % publics[i])
  438. self.NewGlobal(name)
  439. name = publics[-1]
  440. self.NewGlobal(name)
  441. newAsm = 'global ASM_PFX(%s)' % name
  442. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  443. elif self.MatchAndSetMo(self.defineDataRe, oldAsm):
  444. name = self.mo.group(1)
  445. ty = self.mo.group(2)
  446. value = self.mo.group(3)
  447. if value == '?':
  448. value = 0
  449. newAsm = '%s: %s %s' % (name, ty, value)
  450. newAsm = self.CommonConversions(newAsm)
  451. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  452. else:
  453. newAsm = self.CommonConversions(oldAsm)
  454. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  455. def NewGlobal(self, name):
  456. regex = re.compile(r'(?<![_\w\d])(?<!ASM_PFX\()(' + re.escape(name) +
  457. r')(?![_\w\d])')
  458. self.globals.add(regex)
  459. def ConvertAnonymousLabels(self, oldAsm):
  460. newAsm = oldAsm
  461. anonLabel = self.anonLabel(self.anonLabelCount)
  462. newAsm = newAsm.replace('@b', anonLabel)
  463. newAsm = newAsm.replace('@B', anonLabel)
  464. anonLabel = self.anonLabel(self.anonLabelCount + 1)
  465. newAsm = newAsm.replace('@f', anonLabel)
  466. newAsm = newAsm.replace('@F', anonLabel)
  467. return newAsm
  468. def anonLabel(self, count):
  469. return '.%d' % count
  470. def EmitString(self, string):
  471. self.output.write(string.encode('utf-8', 'ignore'))
  472. def EmitLineWithDiff(self, old, new):
  473. newLine = (self.indent + new).rstrip()
  474. if self.diff:
  475. if old is None:
  476. print('+%s' % newLine)
  477. elif newLine != old:
  478. print('-%s' % old)
  479. print('+%s' % newLine)
  480. else:
  481. print('', newLine)
  482. if newLine != '':
  483. self.newAsmEmptyLineCount = 0
  484. self.EmitString(newLine + '\r\n')
  485. def EmitLine(self, string):
  486. self.EmitLineWithDiff(self.originalLine, string)
  487. def EmitNewContent(self, string):
  488. self.EmitLineWithDiff(None, string)
  489. def EmitAsmReplaceOp(self, oldAsm, oldOp, newOp, endOfLine):
  490. newAsm = oldAsm.replace(oldOp, newOp, 1)
  491. self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
  492. hexNumRe = re.compile(r'0*((?=[\da-f])\d*(?<=\d)[\da-f]*)h', re.IGNORECASE)
  493. def EmitAsmWithComment(self, oldAsm, newAsm, endOfLine):
  494. for glblRe in self.globals:
  495. newAsm = glblRe.sub(r'ASM_PFX(\1)', newAsm)
  496. newAsm = self.hexNumRe.sub(r'0x\1', newAsm)
  497. newLine = newAsm + endOfLine
  498. emitNewLine = ((newLine.strip() != '') or
  499. ((oldAsm + endOfLine).strip() == ''))
  500. if emitNewLine and newLine.strip() == '':
  501. self.newAsmEmptyLineCount += 1
  502. if self.newAsmEmptyLineCount > 1:
  503. emitNewLine = False
  504. if emitNewLine:
  505. self.EmitLine(newLine.rstrip())
  506. elif self.diff:
  507. print('-%s' % self.originalLine)
  508. leaRe = re.compile(r'''
  509. (lea \s+) ([\w@][\w@0-9]*) \s* , \s* (\S (?:.*\S)?)
  510. \s* $
  511. ''',
  512. re.VERBOSE | re.IGNORECASE
  513. )
  514. def ConvertLea(self, oldAsm):
  515. newAsm = oldAsm
  516. if self.MatchAndSetMo(self.leaRe, oldAsm):
  517. lea = self.mo.group(1)
  518. dst = self.mo.group(2)
  519. src = self.mo.group(3)
  520. if src.find('[') < 0:
  521. src = '[' + src + ']'
  522. newAsm = lea + dst + ', ' + src
  523. newAsm = self.CommonConversions(newAsm)
  524. return newAsm
  525. ptrRe = re.compile(r'''
  526. (?<! \S )
  527. ([dfq]?word|byte) \s+ (?: ptr ) (\s*)
  528. (?= [[\s] )
  529. ''',
  530. re.VERBOSE | re.IGNORECASE
  531. )
  532. def ConvertPtr(self, oldAsm):
  533. newAsm = oldAsm
  534. while self.SearchAndSetMo(self.ptrRe, newAsm):
  535. ty = self.mo.group(1)
  536. if ty.lower() == 'fword':
  537. ty = ''
  538. else:
  539. ty += self.mo.group(2)
  540. newAsm = newAsm[:self.mo.start(0)] + ty + newAsm[self.mo.end(0):]
  541. return newAsm
  542. labelByteRe = re.compile(r'''
  543. (?: \s+ label \s+ (?: [dfq]?word | byte ) )
  544. (?! \S )
  545. ''',
  546. re.VERBOSE | re.IGNORECASE
  547. )
  548. def ConvertLabelByte(self, oldAsm):
  549. newAsm = oldAsm
  550. if self.SearchAndSetMo(self.labelByteRe, newAsm):
  551. newAsm = newAsm[:self.mo.start(0)] + ':' + newAsm[self.mo.end(0):]
  552. return newAsm
  553. unaryBitwiseOpRe = re.compile(r'''
  554. ( NOT )
  555. (?= \s+ \S )
  556. ''',
  557. re.VERBOSE | re.IGNORECASE
  558. )
  559. binaryBitwiseOpRe = re.compile(r'''
  560. ( \S \s+ )
  561. ( AND | OR | SHL | SHR )
  562. (?= \s+ \S )
  563. ''',
  564. re.VERBOSE | re.IGNORECASE
  565. )
  566. bitwiseOpReplacements = {
  567. 'not': '~',
  568. 'and': '&',
  569. 'shl': '<<',
  570. 'shr': '>>',
  571. 'or': '|',
  572. }
  573. def ConvertBitwiseOp(self, oldAsm):
  574. newAsm = oldAsm
  575. while self.SearchAndSetMo(self.binaryBitwiseOpRe, newAsm):
  576. prefix = self.mo.group(1)
  577. op = self.bitwiseOpReplacements[self.mo.group(2).lower()]
  578. newAsm = newAsm[:self.mo.start(0)] + prefix + op + \
  579. newAsm[self.mo.end(0):]
  580. while self.SearchAndSetMo(self.unaryBitwiseOpRe, newAsm):
  581. op = self.bitwiseOpReplacements[self.mo.group(1).lower()]
  582. newAsm = newAsm[:self.mo.start(0)] + op + newAsm[self.mo.end(0):]
  583. return newAsm
  584. sectionRe = re.compile(r'''
  585. \. ( code |
  586. data
  587. )
  588. (?: \s+ .* )?
  589. \s* $
  590. ''',
  591. re.VERBOSE | re.IGNORECASE
  592. )
  593. segmentRe = re.compile(r'''
  594. ( code |
  595. data )
  596. (?: \s+ SEGMENT )
  597. (?: \s+ .* )?
  598. \s* $
  599. ''',
  600. re.VERBOSE | re.IGNORECASE
  601. )
  602. def ConvertSection(self, oldAsm):
  603. newAsm = oldAsm
  604. if self.MatchAndSetMo(self.sectionRe, newAsm) or \
  605. self.MatchAndSetMo(self.segmentRe, newAsm):
  606. name = self.mo.group(1).lower()
  607. if name == 'code':
  608. if self.x64:
  609. self.EmitLine('DEFAULT REL')
  610. name = 'text'
  611. newAsm = 'SECTION .' + name
  612. return newAsm
  613. fwordRe = re.compile(r'''
  614. (?<! \S )
  615. fword
  616. (?! \S )
  617. ''',
  618. re.VERBOSE | re.IGNORECASE
  619. )
  620. def FwordUnsupportedCheck(self, oldAsm):
  621. newAsm = oldAsm
  622. if self.SearchAndSetMo(self.fwordRe, newAsm):
  623. newAsm = self.Unsupported(newAsm, 'fword used')
  624. return newAsm
  625. __common_conversion_routines__ = (
  626. ConvertAnonymousLabels,
  627. ConvertPtr,
  628. FwordUnsupportedCheck,
  629. ConvertBitwiseOp,
  630. ConvertLabelByte,
  631. ConvertSection,
  632. )
  633. def CommonConversions(self, oldAsm):
  634. newAsm = oldAsm
  635. for conv in self.__common_conversion_routines__:
  636. newAsm = conv(self, newAsm)
  637. return newAsm
  638. def Unsupported(self, asm, message=None):
  639. if not self.force:
  640. raise UnsupportedConversion
  641. self.unsupportedSyntaxSeen = True
  642. newAsm = '%error conversion unsupported'
  643. if message:
  644. newAsm += '; ' + message
  645. newAsm += ': ' + asm
  646. return newAsm
  647. class ConvertInfFile(CommonUtils):
  648. def __init__(self, inf, clone):
  649. CommonUtils.__init__(self, clone)
  650. self.inf = inf
  651. self.ScanInfAsmFiles()
  652. if self.infmode:
  653. self.ConvertInfAsmFiles()
  654. infSrcRe = re.compile(r'''
  655. \s*
  656. ( [\w@][\w@0-9/]* \.(asm|s) )
  657. \s* (?: \| [^#]* )?
  658. \s* (?: \# .* )?
  659. $
  660. ''',
  661. re.VERBOSE | re.IGNORECASE
  662. )
  663. def GetInfAsmFileMapping(self):
  664. srcToDst = {'order': []}
  665. for line in self.lines:
  666. line = line.rstrip()
  667. if self.MatchAndSetMo(self.infSrcRe, line):
  668. src = self.mo.group(1)
  669. srcExt = self.mo.group(2)
  670. dst = os.path.splitext(src)[0] + '.nasm'
  671. fullDst = os.path.join(self.dir, dst)
  672. if src not in srcToDst and not os.path.exists(fullDst):
  673. srcToDst[src] = dst
  674. srcToDst['order'].append(src)
  675. return srcToDst
  676. def ScanInfAsmFiles(self):
  677. src = self.inf
  678. assert os.path.isfile(src)
  679. f = io.open(src, 'rt')
  680. self.lines = f.readlines()
  681. f.close()
  682. path = os.path.realpath(self.inf)
  683. (self.dir, inf) = os.path.split(path)
  684. parent = os.path.normpath(self.dir)
  685. (lastpath, self.moduleName) = os.path.split(parent)
  686. self.packageName = None
  687. while True:
  688. lastpath = os.path.normpath(lastpath)
  689. (parent, basename) = os.path.split(lastpath)
  690. if parent == lastpath:
  691. break
  692. if basename.endswith('Pkg'):
  693. self.packageName = basename
  694. break
  695. lastpath = parent
  696. self.srcToDst = self.GetInfAsmFileMapping()
  697. self.dstToSrc = {'order': []}
  698. for src in self.srcToDst['order']:
  699. srcExt = os.path.splitext(src)[1]
  700. dst = self.srcToDst[src]
  701. if dst not in self.dstToSrc:
  702. self.dstToSrc[dst] = [src]
  703. self.dstToSrc['order'].append(dst)
  704. else:
  705. self.dstToSrc[dst].append(src)
  706. def __len__(self):
  707. return len(self.dstToSrc['order'])
  708. def __iter__(self):
  709. return iter(self.dstToSrc['order'])
  710. def ConvertInfAsmFiles(self):
  711. notConverted = []
  712. unsupportedArchCount = 0
  713. for dst in self:
  714. didSomething = False
  715. try:
  716. self.UpdateInfAsmFile(dst)
  717. didSomething = True
  718. except UnsupportedConversion:
  719. if not self.args.quiet:
  720. print('MASM=>NASM conversion unsupported for', dst)
  721. notConverted.append(dst)
  722. except NoSourceFile:
  723. if not self.args.quiet:
  724. print('Source file missing for', reldst)
  725. notConverted.append(dst)
  726. except UnsupportedArch:
  727. unsupportedArchCount += 1
  728. else:
  729. if didSomething:
  730. self.ConversionFinished(dst)
  731. if len(notConverted) > 0 and not self.args.quiet:
  732. for dst in notConverted:
  733. reldst = self.RootRelative(dst)
  734. print('Unabled to convert', reldst)
  735. if unsupportedArchCount > 0 and not self.args.quiet:
  736. print('Skipped', unsupportedArchCount, 'files based on architecture')
  737. def UpdateInfAsmFile(self, dst, IgnoreMissingAsm=False):
  738. infPath = os.path.split(os.path.realpath(self.inf))[0]
  739. asmSrc = os.path.splitext(dst)[0] + '.asm'
  740. fullSrc = os.path.join(infPath, asmSrc)
  741. fullDst = os.path.join(infPath, dst)
  742. srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
  743. if srcParentDir.lower() in UnsupportedArch.unsupported:
  744. raise UnsupportedArch
  745. elif not os.path.exists(fullSrc):
  746. if not IgnoreMissingAsm:
  747. raise NoSourceFile
  748. else: # not os.path.exists(fullDst):
  749. conv = ConvertAsmFile(fullSrc, fullDst, self)
  750. self.unsupportedSyntaxSeen = conv.unsupportedSyntaxSeen
  751. fileChanged = False
  752. recentSources = list()
  753. i = 0
  754. while i < len(self.lines):
  755. line = self.lines[i].rstrip()
  756. updatedLine = line
  757. lineChanged = False
  758. preserveOldSource = False
  759. for src in self.dstToSrc[dst]:
  760. assert self.srcToDst[src] == dst
  761. updatedLine = self.ReplacePreserveSpacing(
  762. updatedLine, src, dst)
  763. lineChanged = updatedLine != line
  764. if lineChanged:
  765. preserveOldSource = self.ShouldKeepFile(src)
  766. break
  767. if lineChanged:
  768. if preserveOldSource:
  769. if updatedLine.strip() not in recentSources:
  770. self.lines.insert(i, updatedLine + '\n')
  771. recentSources.append(updatedLine.strip())
  772. i += 1
  773. if self.diff:
  774. print('+%s' % updatedLine)
  775. if self.diff:
  776. print('', line)
  777. else:
  778. if self.diff:
  779. print('-%s' % line)
  780. if updatedLine.strip() in recentSources:
  781. self.lines[i] = None
  782. else:
  783. self.lines[i] = updatedLine + '\n'
  784. recentSources.append(updatedLine.strip())
  785. if self.diff:
  786. print('+%s' % updatedLine)
  787. else:
  788. if len(recentSources) > 0:
  789. recentSources = list()
  790. if self.diff:
  791. print('', line)
  792. fileChanged |= lineChanged
  793. i += 1
  794. if fileChanged:
  795. self.lines = list(filter(lambda l: l is not None, self.lines))
  796. for src in self.dstToSrc[dst]:
  797. if not src.endswith('.asm'):
  798. fullSrc = os.path.join(infPath, src)
  799. if os.path.exists(fullSrc):
  800. self.RemoveFile(fullSrc)
  801. if fileChanged:
  802. f = io.open(self.inf, 'w', newline='\r\n')
  803. f.writelines(self.lines)
  804. f.close()
  805. self.FileUpdated(self.inf)
  806. def ConversionFinished(self, dst):
  807. asmSrc = os.path.splitext(dst)[0] + '.asm'
  808. self.FileConversionFinished(
  809. self.packageName, self.moduleName, asmSrc, dst)
  810. class ConvertInfFiles(CommonUtils):
  811. def __init__(self, infs, clone):
  812. CommonUtils.__init__(self, clone)
  813. infs = map(lambda i: ConvertInfFile(i, self), infs)
  814. infs = filter(lambda i: len(i) > 0, infs)
  815. dstToInfs = {'order': []}
  816. for inf in infs:
  817. for dst in inf:
  818. fulldst = os.path.realpath(os.path.join(inf.dir, dst))
  819. pair = (inf, dst)
  820. if fulldst in dstToInfs:
  821. dstToInfs[fulldst].append(pair)
  822. else:
  823. dstToInfs['order'].append(fulldst)
  824. dstToInfs[fulldst] = [pair]
  825. notConverted = []
  826. unsupportedArchCount = 0
  827. for dst in dstToInfs['order']:
  828. didSomething = False
  829. try:
  830. for inf, reldst in dstToInfs[dst]:
  831. inf.UpdateInfAsmFile(reldst, IgnoreMissingAsm=didSomething)
  832. didSomething = True
  833. except UnsupportedConversion:
  834. if not self.args.quiet:
  835. print('MASM=>NASM conversion unsupported for', reldst)
  836. notConverted.append(dst)
  837. except NoSourceFile:
  838. if not self.args.quiet:
  839. print('Source file missing for', reldst)
  840. notConverted.append(dst)
  841. except UnsupportedArch:
  842. unsupportedArchCount += 1
  843. else:
  844. if didSomething:
  845. inf.ConversionFinished(reldst)
  846. if len(notConverted) > 0 and not self.args.quiet:
  847. for dst in notConverted:
  848. reldst = self.RootRelative(dst)
  849. print('Unabled to convert', reldst)
  850. if unsupportedArchCount > 0 and not self.args.quiet:
  851. print('Skipped', unsupportedArchCount, 'files based on architecture')
  852. class ConvertDirectories(CommonUtils):
  853. def __init__(self, paths, clone):
  854. CommonUtils.__init__(self, clone)
  855. self.paths = paths
  856. self.ConvertInfAndAsmFiles()
  857. def ConvertInfAndAsmFiles(self):
  858. infs = list()
  859. for path in self.paths:
  860. assert(os.path.exists(path))
  861. for path in self.paths:
  862. for root, dirs, files in os.walk(path):
  863. for d in ('.svn', '.git'):
  864. if d in dirs:
  865. dirs.remove(d)
  866. for f in files:
  867. if f.lower().endswith('.inf'):
  868. inf = os.path.realpath(os.path.join(root, f))
  869. infs.append(inf)
  870. ConvertInfFiles(infs, self)
  871. class ConvertAsmApp(CommonUtils):
  872. def __init__(self):
  873. CommonUtils.__init__(self)
  874. src = self.args.source
  875. dst = self.args.dest
  876. if self.infmode:
  877. ConvertInfFiles((src,), self)
  878. elif self.dirmode:
  879. ConvertDirectories((src,), self)
  880. elif not self.dirmode:
  881. ConvertAsmFile(src, dst, self)
  882. ConvertAsmApp()