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