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