]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Scripts/ConvertMasmToNasm.py
BaseTools ConvertMasmToNasm: Support ASM_PFX in .asm files
[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 (?: ASM_PFX \s* [(] \s* )?
289 ([\w@][\w@0-9]*) \s*
290 [)]? \s+
291 PROC
292 (?: \s+ NEAR | FAR )?
293 (?: \s+ C )?
294 (?: \s+ (PUBLIC | PRIVATE) )?
295 (?: \s+ USES ( (?: \s+ \w[\w0-9]* )+ ) )?
296 \s* $
297 ''',
298 re.VERBOSE | re.IGNORECASE
299 )
300
301 procEndRe = re.compile(r'''
302 ([\w@][\w@0-9]*) \s+
303 ENDP
304 \s* $
305 ''',
306 re.VERBOSE | re.IGNORECASE
307 )
308
309 varAndTypeSubRe = r' (?: [\w@][\w@0-9]* ) (?: \s* : \s* \w+ )? '
310 publicRe = re.compile(r'''
311 PUBLIC \s+
312 ( %s (?: \s* , \s* %s )* )
313 \s* $
314 ''' % (varAndTypeSubRe, varAndTypeSubRe),
315 re.VERBOSE | re.IGNORECASE
316 )
317
318 varAndTypeSubRe = re.compile(varAndTypeSubRe, re.VERBOSE | re.IGNORECASE)
319
320 macroDeclRe = re.compile(r'''
321 ([\w@][\w@0-9]*) \s+
322 MACRO
323 \s* $
324 ''',
325 re.VERBOSE | re.IGNORECASE
326 )
327
328 sectionDeclRe = re.compile(r'''
329 ([\w@][\w@0-9]*) \s+
330 ( SECTION | ENDS )
331 \s* $
332 ''',
333 re.VERBOSE | re.IGNORECASE
334 )
335
336 externRe = re.compile(r'''
337 EXTE?RN \s+ (?: C \s+ )?
338 ([\w@][\w@0-9]*) \s* : \s* (\w+)
339 \s* $
340 ''',
341 re.VERBOSE | re.IGNORECASE
342 )
343
344 externdefRe = re.compile(r'''
345 EXTERNDEF \s+ (?: C \s+ )?
346 ([\w@][\w@0-9]*) \s* : \s* (\w+)
347 \s* $
348 ''',
349 re.VERBOSE | re.IGNORECASE
350 )
351
352 protoRe = re.compile(r'''
353 ([\w@][\w@0-9]*) \s+
354 PROTO
355 (?: \s+ .* )?
356 \s* $
357 ''',
358 re.VERBOSE | re.IGNORECASE
359 )
360
361 defineDataRe = re.compile(r'''
362 ([\w@][\w@0-9]*) \s+
363 ( db | dw | dd | dq ) \s+
364 ( .*? )
365 \s* $
366 ''',
367 re.VERBOSE | re.IGNORECASE
368 )
369
370 equRe = re.compile(r'''
371 ([\w@][\w@0-9]*) \s+ EQU \s+ (\S.*?)
372 \s* $
373 ''',
374 re.VERBOSE | re.IGNORECASE
375 )
376
377 ignoreRe = re.compile(r'''
378 \. (?: const |
379 mmx |
380 model |
381 xmm |
382 x?list |
383 [3-6]86p?
384 ) |
385 page
386 (?: \s+ .* )?
387 \s* $
388 ''',
389 re.VERBOSE | re.IGNORECASE
390 )
391
392 whitespaceRe = re.compile(r'\s+', re.MULTILINE)
393
394 def TranslateAsm(self, oldAsm, endOfLine):
395 assert(oldAsm.strip() == oldAsm)
396
397 endOfLine = endOfLine.replace(self.inputFileBase, self.outputFileBase)
398
399 oldOp = oldAsm.split()
400 if len(oldOp) >= 1:
401 oldOp = oldOp[0]
402 else:
403 oldOp = ''
404
405 if oldAsm == '':
406 newAsm = oldAsm
407 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
408 elif oldOp in ('#include', ):
409 newAsm = oldAsm
410 self.EmitLine(oldAsm + endOfLine)
411 elif oldOp.lower() in ('end', 'title', 'text'):
412 newAsm = ''
413 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
414 elif oldAsm.lower() == '@@:':
415 self.anonLabelCount += 1
416 self.EmitLine(self.anonLabel(self.anonLabelCount) + ':')
417 elif self.MatchAndSetMo(self.ignoreRe, oldAsm):
418 newAsm = ''
419 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
420 elif oldAsm.lower() == 'ret':
421 for i in range(len(self.uses) - 1, -1, -1):
422 register = self.uses[i]
423 self.EmitNewContent('pop ' + register)
424 newAsm = 'ret'
425 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
426 self.uses = tuple()
427 elif oldOp.lower() == 'lea':
428 newAsm = self.ConvertLea(oldAsm)
429 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
430 elif oldAsm.lower() == 'end':
431 newAsm = ''
432 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
433 self.uses = tuple()
434 elif self.MatchAndSetMo(self.equRe, oldAsm):
435 equ = self.mo.group(1)
436 newAsm = '%%define %s %s' % (equ, self.mo.group(2))
437 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
438 elif self.MatchAndSetMo(self.externRe, oldAsm) or \
439 self.MatchAndSetMo(self.protoRe, oldAsm):
440 extern = self.mo.group(1)
441 self.NewGlobal(extern)
442 newAsm = 'extern ' + extern
443 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
444 elif self.MatchAndSetMo(self.externdefRe, oldAsm):
445 newAsm = ''
446 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
447 elif self.MatchAndSetMo(self.macroDeclRe, oldAsm):
448 newAsm = '%%macro %s 0' % self.mo.group(1)
449 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
450 elif oldOp.lower() == 'endm':
451 newAsm = r'%endmacro'
452 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
453 elif self.MatchAndSetMo(self.sectionDeclRe, oldAsm):
454 name = self.mo.group(1)
455 ty = self.mo.group(2)
456 if ty.lower() == 'section':
457 newAsm = '.' + name
458 else:
459 newAsm = ''
460 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
461 elif self.MatchAndSetMo(self.procDeclRe, oldAsm):
462 proc = self.proc = self.mo.group(1)
463 visibility = self.mo.group(2)
464 if visibility is None:
465 visibility = ''
466 else:
467 visibility = visibility.lower()
468 if visibility != 'private':
469 self.NewGlobal(self.proc)
470 proc = 'ASM_PFX(' + proc + ')'
471 self.EmitNewContent('global ' + proc)
472 newAsm = proc + ':'
473 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
474 uses = self.mo.group(3)
475 if uses is not None:
476 uses = filter(None, uses.split())
477 else:
478 uses = tuple()
479 self.uses = uses
480 for register in self.uses:
481 self.EmitNewContent(' push ' + register)
482 elif self.MatchAndSetMo(self.procEndRe, oldAsm):
483 newAsm = ''
484 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
485 elif self.MatchAndSetMo(self.publicRe, oldAsm):
486 publics = re.findall(self.varAndTypeSubRe, self.mo.group(1))
487 publics = map(lambda p: p.split(':')[0].strip(), publics)
488 for i in range(len(publics) - 1):
489 name = publics[i]
490 self.EmitNewContent('global ASM_PFX(%s)' % publics[i])
491 self.NewGlobal(name)
492 name = publics[-1]
493 self.NewGlobal(name)
494 newAsm = 'global ASM_PFX(%s)' % name
495 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
496 elif self.MatchAndSetMo(self.defineDataRe, oldAsm):
497 name = self.mo.group(1)
498 ty = self.mo.group(2)
499 value = self.mo.group(3)
500 if value == '?':
501 value = 0
502 newAsm = '%s: %s %s' % (name, ty, value)
503 newAsm = self.CommonConversions(newAsm)
504 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
505 else:
506 newAsm = self.CommonConversions(oldAsm)
507 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
508
509 def NewGlobal(self, name):
510 regex = re.compile(r'(?<![_\w\d])(?<!ASM_PFX\()(' + re.escape(name) +
511 r')(?![_\w\d])')
512 self.globals.add(regex)
513
514 def ConvertAnonymousLabels(self, oldAsm):
515 newAsm = oldAsm
516 anonLabel = self.anonLabel(self.anonLabelCount)
517 newAsm = newAsm.replace('@b', anonLabel)
518 newAsm = newAsm.replace('@B', anonLabel)
519 anonLabel = self.anonLabel(self.anonLabelCount + 1)
520 newAsm = newAsm.replace('@f', anonLabel)
521 newAsm = newAsm.replace('@F', anonLabel)
522 return newAsm
523
524 def anonLabel(self, count):
525 return '.%d' % count
526
527 def EmitString(self, string):
528 self.output.write(string.encode('utf-8', 'ignore'))
529
530 def EmitLineWithDiff(self, old, new):
531 newLine = (self.indent + new).rstrip()
532 if self.diff:
533 if old is None:
534 print('+%s' % newLine)
535 elif newLine != old:
536 print('-%s' % old)
537 print('+%s' % newLine)
538 else:
539 print('', newLine)
540 if newLine != '':
541 self.newAsmEmptyLineCount = 0
542 self.EmitString(newLine + '\r\n')
543
544 def EmitLine(self, string):
545 self.EmitLineWithDiff(self.originalLine, string)
546
547 def EmitNewContent(self, string):
548 self.EmitLineWithDiff(None, string)
549
550 def EmitAsmReplaceOp(self, oldAsm, oldOp, newOp, endOfLine):
551 newAsm = oldAsm.replace(oldOp, newOp, 1)
552 self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
553
554 hexNumRe = re.compile(r'0*((?=[\da-f])\d*(?<=\d)[\da-f]*)h', re.IGNORECASE)
555
556 def EmitAsmWithComment(self, oldAsm, newAsm, endOfLine):
557 for glblRe in self.globals:
558 newAsm = glblRe.sub(r'ASM_PFX(\1)', newAsm)
559
560 newAsm = self.hexNumRe.sub(r'0x\1', newAsm)
561
562 newLine = newAsm + endOfLine
563 emitNewLine = ((newLine.strip() != '') or
564 ((oldAsm + endOfLine).strip() == ''))
565 if emitNewLine and newLine.strip() == '':
566 self.newAsmEmptyLineCount += 1
567 if self.newAsmEmptyLineCount > 1:
568 emitNewLine = False
569 if emitNewLine:
570 self.EmitLine(newLine.rstrip())
571 elif self.diff:
572 print('-%s' % self.originalLine)
573
574 leaRe = re.compile(r'''
575 (lea \s+) ([\w@][\w@0-9]*) \s* , \s* (\S (?:.*\S)?)
576 \s* $
577 ''',
578 re.VERBOSE | re.IGNORECASE
579 )
580
581 def ConvertLea(self, oldAsm):
582 newAsm = oldAsm
583 if self.MatchAndSetMo(self.leaRe, oldAsm):
584 lea = self.mo.group(1)
585 dst = self.mo.group(2)
586 src = self.mo.group(3)
587 if src.find('[') < 0:
588 src = '[' + src + ']'
589 newAsm = lea + dst + ', ' + src
590 newAsm = self.CommonConversions(newAsm)
591 return newAsm
592
593 ptrRe = re.compile(r'''
594 (?<! \S )
595 ([dfq]?word|byte) \s+ (?: ptr ) (\s*)
596 (?= [[\s] )
597 ''',
598 re.VERBOSE | re.IGNORECASE
599 )
600
601 def ConvertPtr(self, oldAsm):
602 newAsm = oldAsm
603 while self.SearchAndSetMo(self.ptrRe, newAsm):
604 ty = self.mo.group(1)
605 if ty.lower() == 'fword':
606 ty = ''
607 else:
608 ty += self.mo.group(2)
609 newAsm = newAsm[:self.mo.start(0)] + ty + newAsm[self.mo.end(0):]
610 return newAsm
611
612 labelByteRe = re.compile(r'''
613 (?: \s+ label \s+ (?: [dfq]?word | byte ) )
614 (?! \S )
615 ''',
616 re.VERBOSE | re.IGNORECASE
617 )
618
619 def ConvertLabelByte(self, oldAsm):
620 newAsm = oldAsm
621 if self.SearchAndSetMo(self.labelByteRe, newAsm):
622 newAsm = newAsm[:self.mo.start(0)] + ':' + newAsm[self.mo.end(0):]
623 return newAsm
624
625 unaryBitwiseOpRe = re.compile(r'''
626 ( NOT )
627 (?= \s+ \S )
628 ''',
629 re.VERBOSE | re.IGNORECASE
630 )
631 binaryBitwiseOpRe = re.compile(r'''
632 ( \S \s+ )
633 ( AND | OR | SHL | SHR )
634 (?= \s+ \S )
635 ''',
636 re.VERBOSE | re.IGNORECASE
637 )
638 bitwiseOpReplacements = {
639 'not': '~',
640 'and': '&',
641 'shl': '<<',
642 'shr': '>>',
643 'or': '|',
644 }
645
646 def ConvertBitwiseOp(self, oldAsm):
647 newAsm = oldAsm
648 while self.SearchAndSetMo(self.binaryBitwiseOpRe, newAsm):
649 prefix = self.mo.group(1)
650 op = self.bitwiseOpReplacements[self.mo.group(2).lower()]
651 newAsm = newAsm[:self.mo.start(0)] + prefix + op + \
652 newAsm[self.mo.end(0):]
653 while self.SearchAndSetMo(self.unaryBitwiseOpRe, newAsm):
654 op = self.bitwiseOpReplacements[self.mo.group(1).lower()]
655 newAsm = newAsm[:self.mo.start(0)] + op + newAsm[self.mo.end(0):]
656 return newAsm
657
658 sectionRe = re.compile(r'''
659 \. ( code |
660 data
661 )
662 (?: \s+ .* )?
663 \s* $
664 ''',
665 re.VERBOSE | re.IGNORECASE
666 )
667
668 segmentRe = re.compile(r'''
669 ( code |
670 data )
671 (?: \s+ SEGMENT )
672 (?: \s+ .* )?
673 \s* $
674 ''',
675 re.VERBOSE | re.IGNORECASE
676 )
677
678 def ConvertSection(self, oldAsm):
679 newAsm = oldAsm
680 if self.MatchAndSetMo(self.sectionRe, newAsm) or \
681 self.MatchAndSetMo(self.segmentRe, newAsm):
682 name = self.mo.group(1).lower()
683 if name == 'code':
684 if self.x64:
685 self.EmitLine('DEFAULT REL')
686 name = 'text'
687 newAsm = 'SECTION .' + name
688 return newAsm
689
690 fwordRe = re.compile(r'''
691 (?<! \S )
692 fword
693 (?! \S )
694 ''',
695 re.VERBOSE | re.IGNORECASE
696 )
697
698 def FwordUnsupportedCheck(self, oldAsm):
699 newAsm = oldAsm
700 if self.SearchAndSetMo(self.fwordRe, newAsm):
701 newAsm = self.Unsupported(newAsm, 'fword used')
702 return newAsm
703
704 __common_conversion_routines__ = (
705 ConvertAnonymousLabels,
706 ConvertPtr,
707 FwordUnsupportedCheck,
708 ConvertBitwiseOp,
709 ConvertLabelByte,
710 ConvertSection,
711 )
712
713 def CommonConversions(self, oldAsm):
714 newAsm = oldAsm
715 for conv in self.__common_conversion_routines__:
716 newAsm = conv(self, newAsm)
717 return newAsm
718
719 def Unsupported(self, asm, message=None):
720 if not self.force:
721 raise UnsupportedConversion
722
723 self.unsupportedSyntaxSeen = True
724 newAsm = '%error conversion unsupported'
725 if message:
726 newAsm += '; ' + message
727 newAsm += ': ' + asm
728 return newAsm
729
730
731 class ConvertInfFile(CommonUtils):
732
733 def __init__(self, inf, clone):
734 CommonUtils.__init__(self, clone)
735 self.inf = inf
736 self.ScanInfAsmFiles()
737 if self.infmode:
738 self.ConvertInfAsmFiles()
739
740 infSrcRe = re.compile(r'''
741 \s*
742 ( [\w@][\w@0-9/]* \.(asm|s) )
743 \s* (?: \| [^#]* )?
744 \s* (?: \# .* )?
745 $
746 ''',
747 re.VERBOSE | re.IGNORECASE
748 )
749
750 def GetInfAsmFileMapping(self):
751 srcToDst = {'order': []}
752 for line in self.lines:
753 line = line.rstrip()
754 if self.MatchAndSetMo(self.infSrcRe, line):
755 src = self.mo.group(1)
756 srcExt = self.mo.group(2)
757 dst = os.path.splitext(src)[0] + '.nasm'
758 if src not in srcToDst:
759 srcToDst[src] = dst
760 srcToDst['order'].append(src)
761 return srcToDst
762
763 def ScanInfAsmFiles(self):
764 src = self.inf
765 assert os.path.isfile(src)
766 f = io.open(src, 'rt')
767 self.lines = f.readlines()
768 f.close()
769
770 path = os.path.realpath(self.inf)
771 (self.dir, inf) = os.path.split(path)
772 parent = os.path.normpath(self.dir)
773 (lastpath, self.moduleName) = os.path.split(parent)
774 self.packageName = None
775 while True:
776 lastpath = os.path.normpath(lastpath)
777 (parent, basename) = os.path.split(lastpath)
778 if parent == lastpath:
779 break
780 if basename.endswith('Pkg'):
781 self.packageName = basename
782 break
783 lastpath = parent
784
785 self.srcToDst = self.GetInfAsmFileMapping()
786
787 self.dstToSrc = {'order': []}
788 for src in self.srcToDst['order']:
789 srcExt = os.path.splitext(src)[1]
790 dst = self.srcToDst[src]
791 if dst not in self.dstToSrc:
792 self.dstToSrc[dst] = [src]
793 self.dstToSrc['order'].append(dst)
794 else:
795 self.dstToSrc[dst].append(src)
796
797 def __len__(self):
798 return len(self.dstToSrc['order'])
799
800 def __iter__(self):
801 return iter(self.dstToSrc['order'])
802
803 def ConvertInfAsmFiles(self):
804 notConverted = []
805 unsupportedArchCount = 0
806 for dst in self:
807 didSomething = False
808 try:
809 self.UpdateInfAsmFile(dst)
810 didSomething = True
811 except UnsupportedConversion:
812 if not self.args.quiet:
813 print('MASM=>NASM conversion unsupported for', dst)
814 notConverted.append(dst)
815 except NoSourceFile:
816 if not self.args.quiet:
817 print('Source file missing for', reldst)
818 notConverted.append(dst)
819 except UnsupportedArch:
820 unsupportedArchCount += 1
821 else:
822 if didSomething:
823 self.ConversionFinished(dst)
824 if len(notConverted) > 0 and not self.args.quiet:
825 for dst in notConverted:
826 reldst = self.RootRelative(dst)
827 print('Unabled to convert', reldst)
828 if unsupportedArchCount > 0 and not self.args.quiet:
829 print('Skipped', unsupportedArchCount, 'files based on architecture')
830
831 def UpdateInfAsmFile(self, dst, IgnoreMissingAsm=False):
832 infPath = os.path.split(os.path.realpath(self.inf))[0]
833 asmSrc = os.path.splitext(dst)[0] + '.asm'
834 fullSrc = os.path.join(infPath, asmSrc)
835 fullDst = os.path.join(infPath, dst)
836 srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
837 if srcParentDir.lower() in UnsupportedArch.unsupported:
838 raise UnsupportedArch
839 elif not os.path.exists(fullSrc):
840 if not IgnoreMissingAsm:
841 raise NoSourceFile
842 else: # not os.path.exists(fullDst):
843 conv = ConvertAsmFile(fullSrc, fullDst, self)
844 self.unsupportedSyntaxSeen = conv.unsupportedSyntaxSeen
845
846 lastLine = ''
847 fileChanged = False
848 i = 0
849 for line in self.lines:
850 line = line.rstrip()
851 updatedLine = line
852 for src in self.dstToSrc[dst]:
853 assert self.srcToDst[src] == dst
854 updatedLine = self.ReplacePreserveSpacing(
855 updatedLine, src, dst)
856
857 lineChanged = updatedLine != line
858 if lineChanged:
859 if lastLine.strip() == updatedLine.strip():
860 self.lines[i] = None
861 else:
862 self.lines[i] = updatedLine + '\n'
863
864 if self.diff:
865 if lineChanged:
866 print('-%s' % line)
867 if self.lines[i] is not None:
868 print('+%s' % updatedLine)
869 else:
870 print('', line)
871
872 fileChanged |= lineChanged
873 if self.lines[i] is not None:
874 lastLine = self.lines[i]
875
876 i += 1
877
878 if fileChanged:
879 self.lines = list(filter(lambda l: l is not None, self.lines))
880
881 for src in self.dstToSrc[dst]:
882 if not src.endswith('.asm'):
883 fullSrc = os.path.join(infPath, src)
884 if os.path.exists(fullSrc):
885 self.RemoveFile(fullSrc)
886
887 if fileChanged:
888 f = io.open(self.inf, 'w', newline='\r\n')
889 f.writelines(self.lines)
890 f.close()
891 self.FileUpdated(self.inf)
892
893 def ConversionFinished(self, dst):
894 asmSrc = os.path.splitext(dst)[0] + '.asm'
895 self.FileConversionFinished(
896 self.packageName, self.moduleName, asmSrc, dst)
897
898
899 class ConvertInfFiles(CommonUtils):
900
901 def __init__(self, infs, clone):
902 CommonUtils.__init__(self, clone)
903 infs = map(lambda i: ConvertInfFile(i, self), infs)
904 infs = filter(lambda i: len(i) > 0, infs)
905 dstToInfs = {'order': []}
906 for inf in infs:
907 for dst in inf:
908 fulldst = os.path.realpath(os.path.join(inf.dir, dst))
909 pair = (inf, dst)
910 if fulldst in dstToInfs:
911 dstToInfs[fulldst].append(pair)
912 else:
913 dstToInfs['order'].append(fulldst)
914 dstToInfs[fulldst] = [pair]
915
916 notConverted = []
917 unsupportedArchCount = 0
918 for dst in dstToInfs['order']:
919 didSomething = False
920 try:
921 for inf, reldst in dstToInfs[dst]:
922 inf.UpdateInfAsmFile(reldst, IgnoreMissingAsm=didSomething)
923 didSomething = True
924 except UnsupportedConversion:
925 if not self.args.quiet:
926 print('MASM=>NASM conversion unsupported for', reldst)
927 notConverted.append(dst)
928 except NoSourceFile:
929 if not self.args.quiet:
930 print('Source file missing for', reldst)
931 notConverted.append(dst)
932 except UnsupportedArch:
933 unsupportedArchCount += 1
934 else:
935 if didSomething:
936 inf.ConversionFinished(reldst)
937 if len(notConverted) > 0 and not self.args.quiet:
938 for dst in notConverted:
939 reldst = self.RootRelative(dst)
940 print('Unabled to convert', reldst)
941 if unsupportedArchCount > 0 and not self.args.quiet:
942 print('Skipped', unsupportedArchCount, 'files based on architecture')
943
944
945 class ConvertDirectories(CommonUtils):
946
947 def __init__(self, paths, clone):
948 CommonUtils.__init__(self, clone)
949 self.paths = paths
950 self.ConvertInfAndAsmFiles()
951
952 def ConvertInfAndAsmFiles(self):
953 infs = list()
954 for path in self.paths:
955 assert(os.path.exists(path))
956 for path in self.paths:
957 for root, dirs, files in os.walk(path):
958 for d in ('.svn', '.git'):
959 if d in dirs:
960 dirs.remove(d)
961 for f in files:
962 if f.lower().endswith('.inf'):
963 inf = os.path.realpath(os.path.join(root, f))
964 infs.append(inf)
965
966 ConvertInfFiles(infs, self)
967
968
969 class ConvertAsmApp(CommonUtils):
970
971 def __init__(self):
972 CommonUtils.__init__(self)
973
974 src = self.args.source
975 dst = self.args.dest
976 if self.infmode:
977 ConvertInfFiles((src,), self)
978 elif self.dirmode:
979 ConvertDirectories((src,), self)
980 elif not self.dirmode:
981 ConvertAsmFile(src, dst, self)
982
983 ConvertAsmApp()