]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Scripts/ConvertMasmToNasm.py
BaseTools: Replace BSD License with BSD+Patent License
[mirror_edk2.git] / BaseTools / Scripts / ConvertMasmToNasm.py
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
9 from __future__ import print_function
10
11 #
12 # Import Modules
13 #
14 import argparse
15 import io
16 import os.path
17 import re
18 import subprocess
19 import sys
20
21
22 class UnsupportedConversion(Exception):
23 pass
24
25
26 class NoSourceFile(Exception):
27 pass
28
29
30 class UnsupportedArch(Exception):
31 unsupported = ('aarch64', 'arm', 'ebc', 'ipf')
32
33
34 class 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
213 class 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
738 class 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
921 class 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
967 class 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
991 class 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
1005 ConvertAsmApp()