Created
November 15, 2012 13:01
-
-
Save pravic/4078536 to your computer and use it in GitHub Desktop.
Clang MicrosoftMangle verification script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!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