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