Skip to content

Instantly share code, notes, and snippets.

@pravic
Created November 15, 2012 13:01
Show Gist options
  • Save pravic/4078536 to your computer and use it in GitHub Desktop.
Save pravic/4078536 to your computer and use it in GitHub Desktop.
Clang MicrosoftMangle verification script
#!python
"""
Clang MicrosoftMangle verification script
Requirements:
* cl.exe and clang.exe must be accessible via %PATH%
* strings.exe utility from SysInternals suite
* xargs.exe from gnuwin32 or mingw sys
Description:
Script takes cpp files from command line arguments or current directory
then compiles them by MSVC compiler with specified modes (x86 and/or x64)
and extracts all (or specified) mangled names from compiled object files.
Then compiles sources by Clang compiler with Microsoft mode
and verifies that generated mangled names is similar to names from VC.
You can run this script in batch mode to save results in separated log files
for %f in (*.cpp) do python mangle.py 64 %f > %~nf.log 2<&1
in batch file:
for %%f in (*.cpp) do python mangle.py 64 %%f > %%~nf.log 2<&1
History:
* description changes
* do not take thunk names like '$unwind$?name@'
* initial version
"""
import os, sys, re, glob
from subprocess import Popen, PIPE
def usage():
sys.stderr.write("""Usage:
mangle.py [32|64] [--names] [-iGlobIndex] [cpp] [names_to_verify*]
--names print mangled names from VC
-i file index among cpp files in current directory
""")
sys.exit(1)
def error(msg):
sys.stderr.write(msg+'\n')
sys.exit(1)
# spawn command lines
SpawnVC_32 = r'cl.exe /nologo /c %s.cpp && strings -n 6 %s.obj | grep -P "^\?" | xargs undname'
SpawnVC_64 = r'cl.exe /nologo /c %s.cpp && strings -n 6 %s.obj | grep -P "^\?" | xargs undname'
SpawnCL_32 = r"clang.exe -cc1 -emit-llvm -o - -std=c++11 -fms-extensions -cxx-abi microsoft -triple=i386-pc-win32 %s.cpp"
SpawnCL_64 = r"clang.exe -cc1 -emit-llvm -o - -std=c++11 -fms-extensions -cxx-abi microsoft -triple=x86_64-pc-win32 %s.cpp"
VC = {32: SpawnVC_32, 64: SpawnVC_64 }
CL = {32: SpawnCL_32, 64: SpawnCL_64 }
# modes to process
modes = []
PrintNames = False
def main():
args = sys.argv[1:]
fileindex = None
files = []
names = []
for arg in args:
if arg == '/?' or arg == '-h' or arg == '--help':
usage()
elif arg == '32' or arg == '64':
global modes
modes.append(int(arg))
elif arg == '--names':
global PrintNames
PrintNames = True
elif arg.startswith('-i'):
fileindex = int(arg.replace('-i',''))
elif arg.endswith('.cpp') and os.path.isfile(arg):
files.append(arg)
names = []
else:
names.append(arg)
#end
#end
####################################################
if not modes: # setup modes
modes.extend((32,64))
clpath = find_cl() # setup VC paths
if not clpath:
error("cl.exe not found!")
for m in clpath:
VC[m] = VC[m].replace('cl.exe', clpath[m])
clpath = which('clang.exe') # setup Clang path, take a first clang.exe occurence in $path
if not clpath:
error("clang.exe not found!")
for m in CL:
CL[m] = CL[m].replace('clang.exe', clpath[0])
if not files: # find files
files = glob.glob('*.cpp')
if not files:
usage()
if not fileindex is None:
process_file(files[fileindex], names)
else:
for file in files:
process_file(file, names)
print("DONE")
#end
def process_file(file, names):
# process 'file.cpp' without extension
for mode in modes:
process_mode(mode, file[:-4], names)
#end
#end
def process_mode(mode, file, names):
print("PROCESS %s.cpp x%d" % (file,mode))
# first spawn VC and parse undname result
cl = VC[mode].replace('%s', file)
pipe = Popen(cl, stdout=PIPE, shell=True).stdout.read().decode("1251")
chunks = pipe.split(r'Undecoration of :- ')[1:]
cnames = [re.findall(r'"([^"]+)"', chunk) for chunk in chunks]
if os.path.isfile(file+'.obj'): os.remove(file+'.obj')
def name_from(mangled):
m = re.search(r'\?[^\w]*(.+?)@@', mangled) # from '?' til '@@'
return m.group(1)# if m else mangled
def findVC(name):
# return (mangled, unmangled) or None
for n in cnames: # exact match first
u = name_from(n[0])
if u == name:
return n
for n in cnames:
if name in n[0]: # then try substr
return n
return None
def printNames():
if PrintNames:
print('NAMES:\n' + '\n'.join(map(lambda n: '%s %s' % (n[0], n[1]), cnames)))
return
if not names:
names = [name_from(n[0]) for n in cnames]
# then spawn Clang
cl = CL[mode].replace('%s', file)
pipe = Popen(cl, stdout=PIPE).stdout.read().decode("1251")
lnames = re.findall(r'@"\\01([^"]+)"', pipe)
if not lnames:
print("error: no output from clang")
return printNames()
def findCL(name):
# find mangled
for n in lnames: # exact match first
u = name_from(n)
if u == name:
return n
for n in lnames:
if name in n: # then try substr
return n
return None
# compare it
nf = False
for name in names:
vc = findVC(name)
if not vc:
print('error: VC name not found "%s"' % name)
continue
cl = findCL(name)
mangled, func = vc
if not cl:
print('error: CL name not found "%s" in "%s" of "%s"' % (name, mangled, func))
nf = True
continue
if mangled != cl:
print('error: not equal in "%s":\n%s\n%s\n' % (func, mangled, cl))
# next
#end
printNames()
if nf:
print("\n",pipe)
print("")
#end
def which(program):
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
programs = []
fpath, fname = os.path.split(program)
if fpath and is_exe(program):
programs.append(program)
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
programs.append(exe_file)
return programs
#end
def find_cl():
h = {}
quote = lambda p: '"'+p+'"'
for cl in which('cl.exe'):
cdir = os.path.dirname(cl)
if cdir.lower().endswith('amd64'):
h[64] = quote(cl)
h[32] = quote(os.path.join(os.path.dirname(cdir), 'cl.exe')) # remove x86_amd64 or amd64
else:
h[32] = quote(cl)
mdir = 'x86_amd64'
if os.environ['PROGRAMW6432']:
mdir = 'amd64'
h[64] = quote(os.path.join(cdir, mdir, 'cl.exe')) # add x86_amd64 or amd64
break
if len(h) == 2:
return h
else:
return None
#end
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment