Skip to content

Instantly share code, notes, and snippets.

@marpie
Created April 8, 2012 14:50
Show Gist options
  • Save marpie/2337717 to your computer and use it in GitHub Desktop.
Save marpie/2337717 to your computer and use it in GitHub Desktop.
convKDdef takes a file of KD/WinDBG "Define Type" (dt) command output and tries to convert them to C structs.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" convKDdef
convKDdef takes a file of KD/WinDBG "Define Type" (dt) command output
and tries to convert them to C structs.
It uses a two pass approch to parse and resolv all dependencies. After
reading the file content and parsing it to Type() objects the
Types().parse() function gets calls to invoke the two pass algo:
1. Parse the raw WinDBG output and create Field() objects for every
field of a Type() object.
2. Iterate over all Type() objects and check if every Type() is
recognized as either a build-in type (TYPES) or a parsed Type().
Then the script searches for "gaps" in the definition (shadowed fields)
and prints the C structs.
Author: marpie (marpie@a12d404.net)
Last Update: 20120408
Created: 20120407
"""
from sys import exit, argv
from os.path import isfile
# Version Information
__version__ = "0.0.1"
__program__ = "convKDdef v" + __version__
__author__ = "marpie"
__email__ = "marpie+convKDdef@a12d404.net"
__license__ = "BSD License"
__copyright__ = "Copyright 2012, a12d404.net"
__status__ = "Prototype" # ("Prototype", "Development", "Testing", "Production")
#SCRIPT_PATH = os.path.dirname( os.path.realpath( __file__ ) )
EOE = -1
TYPES = {
"UChar": ("UCHAR", 1,),
"Uint2B": ("WORD", 2,),
"Uint4B": ("DWORD", 4,),
"Int4B": ("INT32", 4,),
"Ptr32": ("", 4,),
"Ptr32 Char": ("PSTR", 4,),
"Ptr32 Uint2B": ("PWSTR", 4,),
"Ptr32 Uint4B": ("PUINT32", 8,),
"Ptr32 Void": ("PVOID", 4,),
"Ptr32 Ptr32 Void": ("PVOID *", 4,),
"Uint8B": ("UINT64", 8,),
"Int8B": ("INT64", 8,),
"Ptr64": ("", 8,),
"Ptr64 Char": ("PSTR", 8,),
"Ptr64 Uint2B": ("PWSTR", 8,),
"Ptr64 Uint4B": ("PUINT64", 8,),
"Ptr64 Void": ("PVOID64", 8,),
"Ptr64 Ptr64 Void": ("PVOID64 *", 8,),
# special
"_LARGE_INTEGER": ("LARGE_INTEGER", 64,),
"_ULARGE_INTEGER": ("ULARGE_INTEGER", 64,),
}
class Error(object):
def __init__(self, description):
self.description = description
def __str__(self):
return self.description
class Field(object):
def __init__(self, rawData):
self.fName = None # str
self.fType = None # str
self.fArrayLen = None # int
self.fSupposedAddr = None # int
self.parsed = False
self.rawData = rawData
def Name(self):
return self.fName
def Type(self, undecorate = False):
"""
Type returns type name. If undecorate is True the plain type
without Ptr* is returned.
"""
if not undecorate:
return self.fType
if self.fType.startswith("Ptr") and (" " in self.fType):
rest, name = self.fType.split(" ", 1)
return name
return self.fType
def SupposedAddr(self):
if self.fSupposedAddr:
return int(self.fSupposedAddr, 0)
return 0
def __repr__(self):
return self.__str__()
def __str__(self):
if self.parsed:
arrStr = ""
if self.fArrayLen:
arrStr = "[%d]" % self.fArrayLen
return "\t%s %s%s" % (self.fType, self.fName, arrStr)
else:
return "*not parsed*"
def parse(self):
""" parse returns None on success or a Error() otherwise. """
try:
self.fSupposedAddr, rest = self.rawData.split(" ", 1)
except:
return Error("Couldn't parse 'Address in Structure'")
try:
self.fName, self.fType = [entry.strip() for entry in rest.split(": ", 2)]
except:
return Error("Couldn't parse Name or Type")
if self.fType.startswith("["):
# array found
idx = self.fType.index("]")
self.fArrayLen = int(self.fType[1:idx])
self.fType = self.fType[idx+2:]
self.parsed = True
return None
class Type(object):
def __init__(self, entryName, rawData):
self.name = entryName
self.rawData = rawData
self.parsed = False
self.fields = []
def Name(self):
return self.name
def Size(self, entries, resolverPath=""):
""" Size returns the in memory size of this object. """
lastField = self.fields[-1]
size = lastField.SupposedAddr()
# get the size of the last field
resolverPath += self.name + ";"
fieldType = lastField.Type(False)
try:
_, fieldSize = TYPES[fieldType]
except KeyError:
# not a build-in type
if fieldType.startswith("Ptr") and (" " in fieldType):
# pointer found
ptrType, _ = fieldType.split(" ", 1)
try:
_, fieldSize = TYPES[ptrType]
except KeyError:
print("Unknown pointer type: " + ptrType)
exit(1)
else:
tmpStr = fieldType + ";"
if tmpStr in resolverPath:
print("[" + self.name + "Recursion detected for field: " + fieldType)
exit(1)
entry = entries[fieldType]
fieldSize = entry.Size(entries, resolverPath)
size += fieldSize
return size
def __repr__(self):
return self.__str__()
def __str__(self):
return "[" + self.name + "]\n\t" + '\n\t'.join([str(field) for field in self.fields])
def parse(self):
""" parse returns None on success or a Error() otherwise. """
for line in self.rawData:
field = Field(line)
if field.parse():
# error occured
return Error("[" + self.name + "] Error while parsing line: " + line)
self.fields.append(field)
if len(self.fields) < 1:
return Error("[" + self.name + "] Empty type definition!")
self.parsed = True
return None
def parseTitle(rawTitle):
""" parseTitle returns None on success or Error() otherwise. """
if "!" in rawTitle:
return rawTitle.split("!", 1)[1]
return rawTitle
def parseFile(fileName):
""" parseFile returns a tuple of (Entries{}, Error()) """
if not isfile(fileName):
return False
with open(fileName, 'r') as f:
content = [line.strip() for line in f.readlines()]
content.append("")
entries = {}
lineCounter = 0
start = EOE
for line in content:
if line == "":
if start == EOE:
# empty line
continue
else:
# EoE (end of entry) reached
end = lineCounter
title = parseTitle(content[start])
entry = Type(title, content[start+1:end])
try:
entries[title]
return None, Error("Duplicate Entry '%s' on line: %d" % (title, start+1,))
except KeyError:
entries[title] = entry
start = EOE
elif start == EOE:
start = lineCounter
lineCounter += 1
return entries, None
def processEntries(entries):
"""
processEntries creates a parsing "tree" and resolves all
"dependencies".
returns: None on success or Error() otherwise.
"""
# pass 1 - parse fields
for entry in entries.itervalues():
err = entry.parse()
if err:
return err
# pass 2 - check dependencies
for entry in entries.itervalues():
for field in entry.fields:
tName = field.Type(True)
tNameUn = field.Type(False)
if (tName in TYPES) or (tName in entries) or (tNameUn in TYPES):
continue
return Error("[" + entry.Name() + "] [Field: " + field.Name() + "] '" + tNameUn + "' unknown!")
return None
def convertToC(entries):
""" convertToC converts the Types() hash to C structs. """
for t in entries.itervalues():
print("typedef struct " + t.Name() + " {")
size = 0
nextAddr = 0
reservedCounter = 1
for field in t.fields:
# check for "hidden" fields
if field.SupposedAddr() <> nextAddr:
hiddenSize = field.SupposedAddr()-nextAddr
print("\t/* +0x%04x */ BYTE Reserved%d[%d];" % (nextAddr, reservedCounter, hiddenSize))
reservedCounter += 1
nextAddr += hiddenSize
fieldType = field.Type(False)
try:
fieldType, fieldSize = TYPES[fieldType]
except KeyError:
# not a build-in type
# check if it's a pointer
if fieldType.startswith("Ptr") and (" " in fieldType):
# pointer found
ptrType, fieldType = fieldType.split(" ", 1)
fieldType = "P" + fieldType[1:]
try:
_, fieldSize = TYPES[ptrType]
except KeyError:
return Error("Unknown pointer type: " + ptrType)
else:
fieldSize = entries[fieldType].Size(entries)
fieldType = fieldType[1:]
arrStr = ""
if field.fArrayLen:
arrStr = "[%d]" % field.fArrayLen
fieldSize *= field.fArrayLen
print("\t/* +0x%04x */ %s %s%s;" % (nextAddr, fieldType, field.Name(), arrStr,))
nextAddr += fieldSize
tName = t.Name()[1:]
print("} " + tName + ", *P" + tName + "; /* struct size: " + str(t.Size(entries)) + " bytes */ \n")
# Main
def main(argv):
#ptr64 = False
if len(argv) < 2:
print( __doc__ )
print("convKDdef.py [input-file]")
return False
entries, err = parseFile(argv[1])
if err:
print("Error: " + str(err))
return False
err = processEntries(entries)
if err:
print(err)
return False
err = convertToC(entries)
if err:
print(err)
return False
#if ptr64:
# print("\n\nBe aware that it's possbile that the pointer types 'Ptr32'/'Ptr64' should be encoded as PVOID instead of PVOID32/PVOID64 to work with 32bit and 64bit environments.")
return True
if __name__ == "__main__":
exit( not main( argv ) )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment