/* * Process Hacker * * Copyright (C) 2008 wj32 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.Text; namespace ProcessHacker.Structs { public class ParserException : Exception { public ParserException(int line, string message) : base("Line " + line.ToString() + ": " + message) { } } public class StructParser { private Dictionary _structs; private int _lineNumber = 1; private Dictionary _typeDefs = new Dictionary(); private bool _eatResult = false; public Dictionary Structs { get { return _structs; } } public StructParser(Dictionary structs) { _structs = structs; foreach (string s in Enum.GetNames(typeof(FieldType))) if (s != "Pointer") _typeDefs.Add(s.ToLower(), (FieldType)Enum.Parse(typeof(FieldType), s)); _typeDefs.Add("bool", FieldType.Bool32); _typeDefs.Add("boolean", FieldType.Bool8); _typeDefs.Add("char", FieldType.CharASCII); _typeDefs.Add("wchar", FieldType.CharUTF16); _typeDefs.Add("sbyte", FieldType.Int8); _typeDefs.Add("byte", FieldType.UInt8); _typeDefs.Add("short", FieldType.Int16); _typeDefs.Add("word", FieldType.Int16); _typeDefs.Add("ushort", FieldType.UInt16); _typeDefs.Add("int", FieldType.Int32); _typeDefs.Add("dword", FieldType.Int32); _typeDefs.Add("long", FieldType.Int32); _typeDefs.Add("uint", FieldType.UInt32); _typeDefs.Add("ulong", FieldType.UInt32); _typeDefs.Add("large_integer", FieldType.Int64); _typeDefs.Add("longlong", FieldType.Int64); _typeDefs.Add("qword", FieldType.Int64); _typeDefs.Add("ulonglong", FieldType.UInt64); _typeDefs.Add("str", FieldType.StringASCII); _typeDefs.Add("string", FieldType.StringASCII); _typeDefs.Add("wstr", FieldType.StringUTF16); _typeDefs.Add("wstring", FieldType.StringUTF16); } private FieldType GetType(string typeName) { if (_typeDefs.ContainsKey(typeName)) return _typeDefs[typeName]; else throw new ParserException(_lineNumber, "Unknown identifier '" + typeName + "' (type name)"); } private bool IsTypePointer(FieldType type) { return (type & FieldType.Pointer) != 0; } public void Parse(string text) { List defs = new List(); int i = 0; while (true) { if (EatWhitespace(text, ref i)) break; string structName = EatId(text, ref i); if (structName == "") throw new ParserException(_lineNumber, "Expected identifier (struct name or 'typedef')"); if (_structs.ContainsKey(structName)) throw new ParserException(_lineNumber, "Struct name '" + structName + "' already used"); if (structName == "typedef") { _eatResult = EatWhitespace(text, ref i); string existingType = EatId(text, ref i); if (_eatResult || existingType == "") throw new ParserException(_lineNumber, "Expected identifier (type name)"); if (!_typeDefs.ContainsKey(existingType)) throw new ParserException(_lineNumber, "Unknown identifier '" + existingType + "' (type name)"); // check for asterisk (pointer) _eatResult = EatWhitespace(text, ref i); string asterisk = EatSymbol(text, ref i); if (asterisk != "*" && asterisk.Length > 0) throw new ParserException(_lineNumber, "Unexpected '" + asterisk + "'"); _eatResult = EatWhitespace(text, ref i); string newType = EatId(text, ref i); if (_eatResult || existingType == "") throw new ParserException(_lineNumber, "Expected identifier (new type name)"); if (_typeDefs.ContainsKey(newType)) throw new ParserException(_lineNumber, "Type name '" + newType + "' already used"); if (this.IsTypePointer(this.GetType(existingType)) && asterisk == "*") throw new ParserException(_lineNumber, "Invalid '*'; type '" + existingType + "' is already a pointer"); _typeDefs.Add(newType, this.GetType(existingType) | (asterisk == "*" ? FieldType.Pointer : 0)); _eatResult = EatWhitespace(text, ref i); string endSemicolon = EatSymbol(text, ref i); if (_eatResult || endSemicolon != ";") throw new ParserException(_lineNumber, "Expected ';'"); } else { // add it first so that structs can be self-referential _structs.Add(structName, null); _structs[structName] = this.ParseDef(text, ref i); } } } private StructDef ParseDef(string text, ref int i) { StructDef def = new StructDef(); // { _eatResult = EatWhitespace(text, ref i); string openingBrace = EatSymbol(text, ref i); if (_eatResult || openingBrace != "{") throw new ParserException(_lineNumber, "Expected '{'"); while (true) { // } _eatResult = EatWhitespace(text, ref i); string endBrace = EatSymbol(text, ref i); if (_eatResult) throw new ParserException(_lineNumber, "Expected type name or '}'"); if (endBrace == "}") break; if (endBrace.Length > 0) throw new ParserException(_lineNumber, "Unexpected '" + endBrace + "'"); // TYPE _eatResult = EatWhitespace(text, ref i); string typeName = EatId(text, ref i); if (_eatResult || typeName == "") throw new ParserException(_lineNumber, "Expected type name"); FieldType type; if (_typeDefs.ContainsKey(typeName)) { type = this.GetType(typeName); } else { type = FieldType.Struct; if (!_structs.ContainsKey(typeName)) throw new ParserException(_lineNumber, "Unknown identifier '" + typeName + "' (type or struct name)"); } // type, without the pointer or array flag FieldType justType = type; // TYPE* // optional asterisk (pointer) _eatResult = EatWhitespace(text, ref i); if (EatSymbol(text, ref i) == "*") { if (this.IsTypePointer(type)) throw new ParserException(_lineNumber, "Invalid '*'; type '" + typeName + "' is already a pointer"); type |= FieldType.Pointer; } // TYPE* FIELDNAME _eatResult = EatWhitespace(text, ref i); string fieldName = EatId(text, ref i); if (_eatResult || fieldName == "") throw new ParserException(_lineNumber, "Expected identifier (struct field name)"); if (def.ContainsField(fieldName)) throw new ParserException(_lineNumber, "Field name '" + fieldName + "' already used"); _eatResult = EatWhitespace(text, ref i); string leftSqBracket = EatSymbol(text, ref i); int varLength = 0; if (leftSqBracket == "[") { _eatResult = EatWhitespace(text, ref i); string fieldRefName = EatId(text, ref i); string fieldSizeSpec = EatNumber(text, ref i); if (fieldRefName != "") { if (!def.ContainsField(fieldRefName)) throw new ParserException(_lineNumber, "Unknown identifier '" + fieldRefName + "' (field name)"); def.GetField(fieldRefName).SetsVarOn = fieldName; // const add/multiply int iSave = i; _eatResult = EatWhitespace(text, ref i); string plusOrMulOrDivSign = EatSymbol(text, ref i); if (plusOrMulOrDivSign == "+") { def.GetField(fieldRefName).SetsVarOnAdd = EatParseInt(text, ref i); } else if (plusOrMulOrDivSign == "*") { def.GetField(fieldRefName).SetsVarOnMultiply = EatParseFloat(text, ref i); int iSave2 = i; _eatResult = EatWhitespace(text, ref i); string plusSign = EatSymbol(text, ref i); if (plusSign == "+") def.GetField(fieldRefName).SetsVarOnAdd = EatParseInt(text, ref i); else i = iSave2; } else if (plusOrMulOrDivSign == "/") { // here we just set SetsVarOnMultiply to 1 / value def.GetField(fieldRefName).SetsVarOnMultiply = 1 / EatParseFloat(text, ref i); int iSave2 = i; _eatResult = EatWhitespace(text, ref i); string plusSign = EatSymbol(text, ref i); if (plusSign == "+") def.GetField(fieldRefName).SetsVarOnAdd = EatParseInt(text, ref i); else i = iSave2; } else { // that didn't work; restore the index i = iSave; } } else if (fieldSizeSpec != "") { try { varLength = (int)BaseConverter.ToNumberParse(fieldSizeSpec); varLength = (int)BaseConverter.ToNumberParse(fieldSizeSpec); } catch { throw new ParserException(_lineNumber, "Could not parse number '" + fieldSizeSpec + "'"); } } else { throw new ParserException(_lineNumber, "Number or identifier expected (size specifier)"); } // if it's not a string, it's an array if (justType != FieldType.StringASCII && justType != FieldType.StringUTF16) type |= FieldType.Array; _eatResult = EatWhitespace(text, ref i); string rightSqBracket = EatSymbol(text, ref i); if (_eatResult || rightSqBracket != "]") throw new ParserException(_lineNumber, "Expected ']'"); // fix up the semicolon _eatResult = EatWhitespace(text, ref i); leftSqBracket = EatSymbol(text, ref i); } // TYPE* FIELDNAME; string endSemicolon = leftSqBracket; if (_eatResult || endSemicolon != ";") throw new ParserException(_lineNumber, "Expected ';'"); StructField field = new StructField(fieldName, type); if (field.Type == FieldType.Struct) field.StructName = typeName; field.VarArrayLength = varLength; field.VarLength = varLength; def.AddField(field); } return def; } private float EatParseFloat(string text, ref int i) { _eatResult = EatWhitespace(text, ref i); string number = EatNumber(text, ref i); if (_eatResult || number == "") throw new ParserException(_lineNumber, "Expected floating-point number"); try { return float.Parse(number); } catch { throw new ParserException(_lineNumber, "Could not parse number '" + number + "'"); } } private int EatParseInt(string text, ref int i) { _eatResult = EatWhitespace(text, ref i); string number = EatNumber(text, ref i); if (_eatResult || number == "") throw new ParserException(_lineNumber, "Expected integer"); try { return (int)BaseConverter.ToNumberParse(number); } catch { throw new ParserException(_lineNumber, "Could not parse number '" + number + "'"); } } // my idea of a tokenizer follows... private bool EatWhitespace(string text, ref int i) // and comments { bool ranOut = true; bool preComment = false; // '/' bool inComment = false; // '*' bool prePostComment = false; // '*' while (i < text.Length) { if (inComment && text[i] == '*') { prePostComment = true; i++; continue; } else if (prePostComment && text[i] == '/') { prePostComment = false; inComment = false; i++; continue; } else if (!inComment && text[i] == '/') { preComment = true; i++; continue; } else if (preComment) { if (text[i] == '*') { preComment = false; inComment = true; i++; continue; } else { // it's a mistake, revert! i -= 1; break; } } else { preComment = false; prePostComment = false; } if (text[i] == '\n') _lineNumber++; if (!(text[i] == '\r' || text[i] == '\n' || text[i] == ' ' || text[i] == '\t') && !inComment) { ranOut = false; break; } i++; } return ranOut; } private string EatId(string text, ref int i) { StringBuilder sb = new StringBuilder(); while (i < text.Length) { // identifiers can't start with a number- if (sb.Length == 0) { if (!(char.IsLetter(text[i]) || text[i] == '_')) break; } else { if (!(char.IsLetterOrDigit(text[i]) || text[i] == '_')) break; } sb.Append(text[i]); i++; } return sb.ToString(); } private string EatNumber(string text, ref int i) { StringBuilder sb = new StringBuilder(); while (i < text.Length) { // allow hex numbers and floating-point numbers if (sb.Length == 1 && sb[0] == '0') { if (!char.IsDigit(text[i]) && char.ToLower(text[i]) != 'x' && text[i] != '.') break; } else if (sb.Length >= 2 && sb[0] == '0' && char.ToLower(sb[1]) == 'x') { if (!(char.IsDigit(text[i]) || char.ToLower(text[i]) == 'a' || char.ToLower(text[i]) == 'b' || char.ToLower(text[i]) == 'c' || char.ToLower(text[i]) == 'd' || char.ToLower(text[i]) == 'e' || char.ToLower(text[i]) == 'f')) break; } else { if (!char.IsDigit(text[i])) break; } sb.Append(text[i]); i++; } return sb.ToString(); } private string EatSymbol(string text, ref int i) { StringBuilder sb = new StringBuilder(); while (i < text.Length && sb.Length < 1) // we need a proper parser to solve this { char c = text[i]; if (c < ' ' || c > '~') // check if its an ASCII character break; if (char.IsLetterOrDigit(c) || c == '_') // check if its eligible to be an identifier break; sb.Append(c); i++; } return sb.ToString(); } } }