// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. // This is a very rough prototype of an utility that extracts syscall descriptions from header files. // It needs to extract struct/union descriptions, better analyze types, // analyze pointer directions (in, out), figure out len types (usually marked with sal). // The easiest way to build it is to build it as part of clang. Add the following lines to CMakeLists.txt: // +add_clang_executable(syz-declextract syz-declextract/syz-declextract.cpp) // +target_link_libraries(syz-declextract clangTooling) // It was used to extract windows descriptions: // syz-declextract -extra-arg="--driver-mode=cl" -extra-arg="-I/path/to/windows/headers" Windows.h #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Driver/Options.h" #include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" using namespace clang; using namespace clang::tooling; std::string convertType(ASTContext &C, QualType T) { auto name = T.getAsString(); if (name == "HANDLE") return name; if (T->isIntegralOrEnumerationType()) { int size = C.getTypeSize(T); char buf[10]; sprintf(buf, "int%d", size); return buf; } if (T->isVoidPointerType()) { return "ptr[inout, array[int8]]"; } if (T->isPointerType()) { auto inner = convertType(C, T->getPointeeType()); if (inner == "") return "ptr[inout, array[int8]]"; char buf[1024]; sprintf(buf, "ptr[inout, %s]", inner.c_str()); return buf; } return "intptr"; } class DeclExtractCallVisitor : public RecursiveASTVisitor<DeclExtractCallVisitor> { public: explicit DeclExtractCallVisitor(ASTContext *Context) : Context(*Context) {} bool VisitFunctionDecl(const FunctionDecl *D) { if (D->doesThisDeclarationHaveABody()) return true; // TODO(dvyukov): need to select only stdcall (WINAPI) functions. // But the following 2 approaches do not work. if (false) { if (auto *FPT = D->getType()->getAs<FunctionProtoType>()) { if (FPT->getExtInfo().getCC() != CC_X86StdCall) return true; } } if (false) { if (!D->hasAttr<StdCallAttr>()) return true; } // Tons of functions are bulk ignored below because they cause // static/dynamic link failures, reboot machine, etc. auto fn = D->getNameInfo().getAsString(); if (fn.empty()) return true; if (*fn.rbegin() == 'W') return true; // Unicode versions. const char *ignore_prefixes[] { "_", "Rtl", "IBind", "Ndr", "NDR", "SCard", }; for (auto prefix: ignore_prefixes) { if (strncmp(fn.c_str(), prefix, strlen(prefix)) == 0) return true; } const char *ignore_functions[] { "IEnum", "IStream", "IType", "IService", "IProperty", "ISequential", "IDispatch", "I_RPC", "I_Rpc", "CLEANLOCAL", "WinMain", "PropertySheet", "LookupAccountNameLocalA", "LookupAccountSidLocalA", "WTSGetServiceSessionId", "WTSIsServerContainer", "GetDisplayAutoRotationPreferencesByProcessId", "LoadStringByReference", "IdnToNameprepUnicode", "VerFindFileA", "VerInstallFileA", "GetFileVersionInfoSizeA", "GetFileVersionInfoA", "GetFileVersionInfoSizeExA", "GetFileVersionInfoExA", "VerQueryValueA", "sndOpenSound", "Netbios", "RpcBindingGetTrainingContextHandle", "RpcAsyncCleanupThread", "ShellMessageBoxA", "SHEnumerateUnreadMailAccountsA", "SHGetUnreadMailCountA", "SHSetUnreadMailCountA", "GetEncSChannel", "CryptExportPKCS8Ex", "FindCertsByIssuer", "CryptCancelAsyncRetrieval", "CryptGetTimeValidObject", "CryptFlushTimeValidObject", "CryptProtectDataNoUI", "CryptUnprotectDataNoUI", "NsServerBindSearch", "NsClientBindSearch", "NsClientBindDone", "GetOpenCardNameA", "SubscribeServiceChangeNotifications", "UnsubscribeServiceChangeNotifications", "GetThreadDescription", "SetThreadDescription", "DialogControlDpi", "SetDialogDpiChangeBehavior", "GetDialogDpiChangeBehavior", "RpcServer", "DecodePointer", "DecodeRemotePointer", "DecodeSystemPointer", "EncodePointer", "EncodeRemotePointer", "EncodeSystemPointer", "UnmapViewOfFile2", "MapViewOfFileNuma2", "DeriveCapabilitySidsFromName", "QueryAuxiliaryCounterFrequency", "ConvertPerformanceCounterToAuxiliaryCounter", "ConvertAuxiliaryCounterToPerformanceCounter", "FreePropVariantArray", "PropVariantCopy", "PropVariantClear", "InitiateShutdown", "ExitWindowsEx", "LockWorkStation", "InitiateSystemShutdown", "InitiateSystemShutdownEx", "shutdown", }; for (auto func: ignore_functions) { if (strstr(fn.c_str(), func)) return true; } // These are already described: const char *ignore_exact[] { "CreateFileA", "CloseHandle", "VirtualAlloc", }; for (auto func: ignore_exact) { if (strcmp(fn.c_str(), func) == 0) return true; } const char *ignore_files[] { "/um/ole", "htiface.h", "objbase.h", "HLink.h", "urlmon.h", "HlGuids.h", "unknwn.h", "unknwnbase.h", "coguid.h", "MsHtmHst.h", "msime.h", "ComSvcs.h", "combaseapi.h", "WbemGlue.h", "OCIdl.h", "mfapi.h", "CompPkgSup.h", "ole2.h", "Ole2.h", "oleidl.h", "ObjIdl.h", "WabDefs.h", "objidl.h", }; auto src = D->getSourceRange().getBegin().printToString(Context.getSourceManager()); if (strstr(src.c_str(), "/um/") == 0) return true; for (auto file: ignore_files) { if (strstr(src.c_str(), file)) return true; } for (const ParmVarDecl *P : D->parameters()) { auto typ = convertType(Context, P->getType()); if (typ == "") { llvm::outs() << D->getNameInfo().getAsString() << ": UNKNOWN TYPE: " << QualType(P->getType()).getAsString() << "\n"; return true; } } if (Generated[D->getNameInfo().getAsString()]) return true; Generated[D->getNameInfo().getAsString()] = true; llvm::outs() << D->getNameInfo().getAsString() << "("; int i = 0; for (const ParmVarDecl *P : D->parameters()) { if (i) llvm::outs() << ", "; auto name = P->getNameAsString(); if (name == "") { char buf[10]; sprintf(buf, "arg%d", i); name = buf; } llvm::outs() << name << " " << convertType(Context, P->getType()); i++; if (i == 9) break; } llvm::outs() << ")"; auto ret = convertType(Context, D->getReturnType()); if (ret == "HANDLE") llvm::outs() << " " << ret; llvm::outs() << "\n"; return true; } private: ASTContext &Context; std::map<std::string, bool> Generated; }; class DeclExtractCallConsumer : public clang::ASTConsumer { public: explicit DeclExtractCallConsumer(ASTContext *Context) : Visitor(Context) {} virtual void HandleTranslationUnit(clang::ASTContext &Context) { Visitor.TraverseDecl(Context.getTranslationUnitDecl()); } private: DeclExtractCallVisitor Visitor; }; class DeclExtractCallAction : public clang::ASTFrontendAction { public: DeclExtractCallAction() {} virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( clang::CompilerInstance &Compiler, llvm::StringRef InFile) { return std::unique_ptr<clang::ASTConsumer>( new DeclExtractCallConsumer(&Compiler.getASTContext())); } }; static llvm::cl::OptionCategory MyToolCategory("my-tool options"); int main(int argc, const char **argv) { CommonOptionsParser OptionsParser(argc, argv, MyToolCategory); ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList()); return Tool.run(newFrontendActionFactory<DeclExtractCallAction>().get()); }