From 836dd8e1f01318ac7f1149d399ce36b064404cb4 Mon Sep 17 00:00:00 2001 From: Eric Beckmann Date: Sat, 20 May 2017 01:49:19 +0000 Subject: [PATCH] Add functionality to cvtres to parse all entries in res file. Summary: Added the new modules in the Object/ folder. Updated the llvm-cvtres interface as well, and added additional tests. Subscribers: llvm-commits, mgorny Differential Revision: https://reviews.llvm.org/D33180 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@303480 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/Object/Binary.h | 4 + include/llvm/Object/WindowsResource.h | 82 ++++++++++++++ include/llvm/Support/BinaryStreamReader.h | 1 - include/llvm/Support/FileSystem.h | 2 +- lib/Object/Binary.cpp | 4 +- lib/Object/CMakeLists.txt | 1 + lib/Object/WindowsResource.cpp | 92 ++++++++++++++++ .../tools/llvm-cvtres/Inputs/cursor_small.bmp | Bin 0 -> 822 bytes test/tools/llvm-cvtres/Inputs/okay_small.bmp | Bin 0 -> 822 bytes .../tools/llvm-cvtres/Inputs/test_resource.rc | 44 ++++++++ .../llvm-cvtres/Inputs/test_resource.res | Bin 0 -> 2200 bytes test/tools/llvm-cvtres/resource.test | 7 ++ tools/llvm-cvtres/CMakeLists.txt | 1 + tools/llvm-cvtres/llvm-cvtres.cpp | 100 +++++++++++++++++- tools/llvm-cvtres/llvm-cvtres.h | 6 ++ 15 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 include/llvm/Object/WindowsResource.h create mode 100644 lib/Object/WindowsResource.cpp create mode 100644 test/tools/llvm-cvtres/Inputs/cursor_small.bmp create mode 100644 test/tools/llvm-cvtres/Inputs/okay_small.bmp create mode 100644 test/tools/llvm-cvtres/Inputs/test_resource.rc create mode 100644 test/tools/llvm-cvtres/Inputs/test_resource.res create mode 100644 test/tools/llvm-cvtres/resource.test diff --git a/include/llvm/Object/Binary.h b/include/llvm/Object/Binary.h index f42048e48ee..cf5d93ee9ed 100644 --- a/include/llvm/Object/Binary.h +++ b/include/llvm/Object/Binary.h @@ -57,6 +57,8 @@ protected: ID_MachO64L, // MachO 64-bit, little endian ID_MachO64B, // MachO 64-bit, big endian + ID_WinRes, // Windows resource (.res) file. + ID_Wasm, ID_EndObjects @@ -132,6 +134,8 @@ public: TypeID == ID_MachO32B || TypeID == ID_MachO64B); } + bool isWinRes() const { return TypeID == ID_WinRes; } + Triple::ObjectFormatType getTripleObjectFormat() const { if (isCOFF()) return Triple::COFF; diff --git a/include/llvm/Object/WindowsResource.h b/include/llvm/Object/WindowsResource.h new file mode 100644 index 00000000000..f94ad09ce0c --- /dev/null +++ b/include/llvm/Object/WindowsResource.h @@ -0,0 +1,82 @@ +//===-- WindowsResource.h ---------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// This file declares the .res file class. .res files are intermediate +// products of the typical resource-compilation process on Windows. This +// process is as follows: +// +// .rc file(s) ---(rc.exe)---> .res file(s) ---(cvtres.exe)---> COFF file +// +// .rc files are human-readable scripts that list all resources a program uses. +// +// They are compiled into .res files, which are a list of the resources in +// binary form. +// +// Finally the data stored in the .res is compiled into a COFF file, where it +// is organized in a directory tree structure for optimized access by the +// program during runtime. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648007(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_INCLUDE_LLVM_OBJECT_RESFILE_H +#define LLVM_INCLUDE_LLVM_OBJECT_RESFILE_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Object/Binary.h" +#include "llvm/Support/BinaryByteStream.h" +#include "llvm/Support/BinaryStreamReader.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace object { + +class WindowsResource; + +class ResourceEntryRef { +public: + Error moveNext(bool &End); + +private: + friend class WindowsResource; + + ResourceEntryRef(BinaryStreamRef Ref, const WindowsResource *Owner, + Error &Err); + Error loadNext(); + + BinaryStreamReader Reader; + BinaryStreamRef HeaderBytes; + BinaryStreamRef DataBytes; + const WindowsResource *OwningRes = nullptr; +}; + +class WindowsResource : public Binary { +public: + ~WindowsResource() override; + Expected getHeadEntry(); + + static bool classof(const Binary *V) { return V->isWinRes(); } + + static Expected> + createWindowsResource(MemoryBufferRef Source); + +private: + friend class ResourceEntryRef; + + WindowsResource(MemoryBufferRef Source); + + BinaryByteStream BBS; +}; + +} // namespace object +} // namespace llvm + +#endif diff --git a/include/llvm/Support/BinaryStreamReader.h b/include/llvm/Support/BinaryStreamReader.h index 75e96a999a1..56375f41d2c 100644 --- a/include/llvm/Support/BinaryStreamReader.h +++ b/include/llvm/Support/BinaryStreamReader.h @@ -16,7 +16,6 @@ #include "llvm/Support/BinaryStreamRef.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" -#include "llvm/Support/MathExtras.h" #include "llvm/Support/type_traits.h" #include diff --git a/include/llvm/Support/FileSystem.h b/include/llvm/Support/FileSystem.h index e3c5de7fbe6..7caefb5359b 100644 --- a/include/llvm/Support/FileSystem.h +++ b/include/llvm/Support/FileSystem.h @@ -261,7 +261,7 @@ struct file_magic { coff_object, ///< COFF object file coff_import_library, ///< COFF import library pecoff_executable, ///< PECOFF executable file - windows_resource, ///< Windows compiled resource file (.rc) + windows_resource, ///< Windows compiled resource file (.res) wasm_object ///< WebAssembly Object file }; diff --git a/lib/Object/Binary.cpp b/lib/Object/Binary.cpp index 2b44c4a82d2..116af3c917b 100644 --- a/lib/Object/Binary.cpp +++ b/lib/Object/Binary.cpp @@ -17,6 +17,7 @@ #include "llvm/Object/Error.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/ObjectFile.h" +#include "llvm/Object/WindowsResource.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorOr.h" @@ -71,9 +72,10 @@ Expected> object::createBinary(MemoryBufferRef Buffer, return ObjectFile::createSymbolicFile(Buffer, Type, Context); case sys::fs::file_magic::macho_universal_binary: return MachOUniversalBinary::create(Buffer); + case sys::fs::file_magic::windows_resource: + return WindowsResource::createWindowsResource(Buffer); case sys::fs::file_magic::unknown: case sys::fs::file_magic::coff_cl_gl_object: - case sys::fs::file_magic::windows_resource: // Unrecognized object file format. return errorCodeToError(object_error::invalid_file_type); } diff --git a/lib/Object/CMakeLists.txt b/lib/Object/CMakeLists.txt index 08365e71c2f..fa033589028 100644 --- a/lib/Object/CMakeLists.txt +++ b/lib/Object/CMakeLists.txt @@ -18,6 +18,7 @@ add_llvm_library(LLVMObject SymbolicFile.cpp SymbolSize.cpp WasmObjectFile.cpp + WindowsResource.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Object diff --git a/lib/Object/WindowsResource.cpp b/lib/Object/WindowsResource.cpp new file mode 100644 index 00000000000..29a02b8a27d --- /dev/null +++ b/lib/Object/WindowsResource.cpp @@ -0,0 +1,92 @@ +//===-- WindowsResource.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the .res file class. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/WindowsResource.h" +#include "llvm/Object/Error.h" +#include + +namespace llvm { +namespace object { + +static const char ResourceMagic[] = { + '\0', '\0', '\0', '\0', '\x20', '\0', '\0', '\0', + '\xff', '\xff', '\0', '\0', '\xff', '\xff', '\0', '\0'}; + +static const char NullEntry[16] = {'\0'}; + +#define RETURN_IF_ERROR(X) \ + if (auto EC = X) \ + return EC; + +WindowsResource::WindowsResource(MemoryBufferRef Source) + : Binary(Binary::ID_WinRes, Source) { + size_t LeadingSize = sizeof(ResourceMagic) + sizeof(NullEntry); + BBS = BinaryByteStream(Data.getBuffer().drop_front(LeadingSize), + support::little); +} + +WindowsResource::~WindowsResource() = default; + +Expected> +WindowsResource::createWindowsResource(MemoryBufferRef Source) { + if (Source.getBufferSize() < sizeof(ResourceMagic) + sizeof(NullEntry)) + return make_error( + "File too small to be a resource file", + object_error::invalid_file_type); + std::unique_ptr Ret(new WindowsResource(Source)); + return std::move(Ret); +} + +Expected WindowsResource::getHeadEntry() { + Error Err = Error::success(); + auto Ref = ResourceEntryRef(BinaryStreamRef(BBS), this, Err); + if (Err) + return std::move(Err); + return Ref; +} + +ResourceEntryRef::ResourceEntryRef(BinaryStreamRef Ref, + const WindowsResource *Owner, Error &Err) + : Reader(Ref), OwningRes(Owner) { + if (loadNext()) + Err = make_error("Could not read first entry.", + object_error::unexpected_eof); +} + +Error ResourceEntryRef::moveNext(bool &End) { + // Reached end of all the entries. + if (Reader.bytesRemaining() == 0) { + End = true; + return Error::success(); + } + RETURN_IF_ERROR(loadNext()); + + return Error::success(); +} + +Error ResourceEntryRef::loadNext() { + uint32_t DataSize; + RETURN_IF_ERROR(Reader.readInteger(DataSize)); + uint32_t HeaderSize; + RETURN_IF_ERROR(Reader.readInteger(HeaderSize)); + // The data and header size ints are themselves part of the header, so we must + // subtract them from the size. + RETURN_IF_ERROR( + Reader.readStreamRef(HeaderBytes, HeaderSize - 2 * sizeof(uint32_t))); + RETURN_IF_ERROR(Reader.readStreamRef(DataBytes, DataSize)); + RETURN_IF_ERROR(Reader.padToAlignment(sizeof(uint32_t))); + return Error::success(); +} + +} // namespace object +} // namespace llvm diff --git a/test/tools/llvm-cvtres/Inputs/cursor_small.bmp b/test/tools/llvm-cvtres/Inputs/cursor_small.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ce513261bc2c223d317d9fded11bec5c3b9e725b GIT binary patch literal 822 zcmc)F!HR-V6b4|2*$=avN0=w*0cJmsBP6IHM(DyoB6LyVLI{O$WfGwvSO{+1L_~{F zibT{v1eY_1?yO?Q+RcR{c+U49y&s>gXY6`f@O+1Q{i!GH?b^p+?!BNw4GjDGkJIUN zNRn)~+c=Ild|#3zG-X*<*Y*8=PY^^yWHcJdvJ4T9;{aQhMNw44enn9rG8hbIvsqb| z-ENm*mUO&wkH=vc7DXY7q96#!Ly)HFo2;g3u=aF1#c@0w4w2`1zSryBNc4(+ zBZ{KK;cz~mkH_P3x$O7*zhY>brV0C#BmtbJ>3Y4MOeTL}=r0zFs;aUq%kw-4g6VYH zfMI0Yc3sz(%LVRh7)CROsq4DnH=E6TKEI0rc%HZ0?HtE>1cUK?f3;dYh=IW!@8WN7 CMZZ4) literal 0 HcmV?d00001 diff --git a/test/tools/llvm-cvtres/Inputs/okay_small.bmp b/test/tools/llvm-cvtres/Inputs/okay_small.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e4005bf5ef97c42f4f43c8ea816aa948b0498260 GIT binary patch literal 822 zcmZ?rHDhJ~12Z700mK4O%*Y@C7H0s;AK`;whyVk_|6v3cJYLDTSpp3uUdYGgMW9NC z>&7y-J#YVfH2eK}ARCB)g80< zzyJRYeYb>w`nNL}V5t53`G5ca6HxzU7XuQS^ZN+_^}la3pdvi#=RaBjltn{$!V_1x H4Q6owcR8P# literal 0 HcmV?d00001 diff --git a/test/tools/llvm-cvtres/Inputs/test_resource.rc b/test/tools/llvm-cvtres/Inputs/test_resource.rc new file mode 100644 index 00000000000..fd616520dbe --- /dev/null +++ b/test/tools/llvm-cvtres/Inputs/test_resource.rc @@ -0,0 +1,44 @@ +#include "windows.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +myaccelerators ACCELERATORS +{ + "^C", 999, VIRTKEY, ALT + "D", 1100, VIRTKEY, CONTROL, SHIFT + "^R", 444, ASCII, NOINVERT +} + +cursor BITMAP "cursor_small.bmp" +okay BITMAP "okay_small.bmp" + +14432 MENU +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED +{ + MENUITEM "yu", 100 + MENUITEM "shala", 101 + MENUITEM "kaoya", 102 +} + +testdialog DIALOG 10, 10, 200, 300 +STYLE WS_POPUP | WS_BORDER +CAPTION "Test" +{ + CTEXT "Continue:", 1, 10, 10, 230, 14 + PUSHBUTTON "&OK", 2, 66, 134, 161, 13 +} + +12 ACCELERATORS +{ + "X", 164, VIRTKEY, ALT + "H", 5678, VIRTKEY, CONTROL, SHIFT + "^R", 444, ASCII, NOINVERT +} + +"eat" MENU +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +{ + MENUITEM "fish", 100 + MENUITEM "salad", 101 + MENUITEM "duck", 102 +} diff --git a/test/tools/llvm-cvtres/Inputs/test_resource.res b/test/tools/llvm-cvtres/Inputs/test_resource.res new file mode 100644 index 0000000000000000000000000000000000000000..c577ecc3d6333761ae52a556093568e136f97edd GIT binary patch literal 2200 zcmdUxUr1AN6vw}t3o=ryhwz~X@gaN=8Pr2WFa42{jG`RnThfZ8$cLIZ@V~P3K|w?u zG;P7cCfFWA#Y{<7_dNckcb2^ZT5$`}@JU zX8=$@l_W(u-6Q$5&Qm2R8`n{Z3%JCq6y?-gv?_3e&)dIZ^vKqwjAO_`Fbl}RWt7v~ zisSgG0gC7=mUC*E%OJ;#EGgNYqlnJDpQ?d)8w%syxr!+9?4>P#k+ z%Fp-r_baPyZEeHD!#2xyf+S;1m@9*pDQ~1u# zPK(8oMk=QA8mp_TD=RC(U@#B}OioUAcX$65rgL<3l=(L{HVAKSZqCik4Gs?O2h%?> zF%gT!LZMJN9G;(_9~v4`fw?p_H5HG?qtPh$>vFl&VXn^1%#dGLSQr}{%Lo&2yWLAm zOViWS2Z70WJf7Lv*#pAFxW|mJs3}n^lB8utUSnlerb+|q~^NUVA6{CLw({ zL>w9TuobECBK|okNg4Rq2kaWQL?8p7@L}&z;a~aRay4zJoacV$_AmQOSUm-DJ?U6e z_s)u9(&y<8i|39QVTMUIAK)1tp$bo^Kr$u{GZ{H+kHqO%rIMW2_XqnvTSdwTg@IOxm4Rg|3A>E(L*l<$rX qh1{3;M^Gq&Dcp;aNHKXg@?5IrlM*hm59P#+w8WVi`NsEoa=;HpTc=9^ literal 0 HcmV?d00001 diff --git a/test/tools/llvm-cvtres/resource.test b/test/tools/llvm-cvtres/resource.test new file mode 100644 index 00000000000..16970343c60 --- /dev/null +++ b/test/tools/llvm-cvtres/resource.test @@ -0,0 +1,7 @@ +// The input was generated with the following command, using the original Windows +// rc.exe: +// > rc /fo test_resource.res /nologo test_resource.rc + +RUN: llvm-cvtres %p/Inputs/test_resource.res | FileCheck %s + +CHECK: Number of resources: 7 diff --git a/tools/llvm-cvtres/CMakeLists.txt b/tools/llvm-cvtres/CMakeLists.txt index 52edccac816..e912030e205 100644 --- a/tools/llvm-cvtres/CMakeLists.txt +++ b/tools/llvm-cvtres/CMakeLists.txt @@ -1,4 +1,5 @@ set(LLVM_LINK_COMPONENTS + Object Option Support ) diff --git a/tools/llvm-cvtres/llvm-cvtres.cpp b/tools/llvm-cvtres/llvm-cvtres.cpp index f03e0b772e1..96f7437ab5f 100644 --- a/tools/llvm-cvtres/llvm-cvtres.cpp +++ b/tools/llvm-cvtres/llvm-cvtres.cpp @@ -14,17 +14,23 @@ #include "llvm-cvtres.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/WindowsResource.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" +#include "llvm/Support/BinaryStreamError.h" #include "llvm/Support/Error.h" #include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; +using namespace object; namespace { @@ -61,6 +67,28 @@ public: static ExitOnError ExitOnErr; } +LLVM_ATTRIBUTE_NORETURN void reportError(Twine Msg) { + errs() << Msg; + exit(1); +} + +static void reportError(StringRef Input, std::error_code EC) { + reportError(Twine(Input) + ": " + EC.message() + ".\n"); +} + +void error(std::error_code EC) { + if (!EC) + return; + reportError(EC.message() + ".\n"); +} + +void error(Error EC) { + if (!EC) + return; + handleAllErrors(std::move(EC), + [&](const ErrorInfoBase &EI) { reportError(EI.message()); }); +} + int main(int argc_, const char *argv_[]) { sys::PrintStackTraceOnErrorSignal(argv_[0]); PrettyStackTraceProgram X(argc_, argv_); @@ -76,11 +104,79 @@ int main(int argc_, const char *argv_[]) { CvtResOptTable T; unsigned MAI, MAC; - ArrayRef ArgsArr = makeArrayRef(argv_, argc_); + ArrayRef ArgsArr = makeArrayRef(argv_ + 1, argc_); opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); - if (InputArgs.hasArg(OPT_HELP)) + if (InputArgs.hasArg(OPT_HELP)) { T.PrintHelp(outs(), "cvtres", "Resource Converter", false); + return 0; + } + machine Machine; + + if (InputArgs.hasArg(OPT_MACHINE)) { + std::string MachineString = InputArgs.getLastArgValue(OPT_MACHINE).upper(); + Machine = StringSwitch(MachineString) + .Case("ARM", machine::ARM) + .Case("X64", machine::X64) + .Case("X86", machine::X86) + .Default(machine::UNKNOWN); + if (Machine == machine::UNKNOWN) + reportError("Unsupported machine architecture"); + } else { + outs() << "Machine architecture not specified; assumed X64.\n"; + Machine = machine::X64; + } + + std::vector InputFiles = InputArgs.getAllArgValues(OPT_INPUT); + + if (InputFiles.size() == 0) { + reportError("No input file specified"); + } + + SmallString<128> OutputFile; + + if (InputArgs.hasArg(OPT_OUT)) { + OutputFile = InputArgs.getLastArgValue(OPT_OUT); + } else { + OutputFile = StringRef(InputFiles[0]); + llvm::sys::path::replace_extension(OutputFile, ".obj"); + } + + for (const auto &File : InputFiles) { + Expected> BinaryOrErr = + object::createBinary(File); + if (!BinaryOrErr) + reportError(File, errorToErrorCode(BinaryOrErr.takeError())); + + Binary &Binary = *BinaryOrErr.get().getBinary(); + + WindowsResource *RF = dyn_cast(&Binary); + if (!RF) + reportError(File + ": unrecognized file format.\n"); + + int EntryNumber = 0; + Expected EntryOrErr = RF->getHeadEntry(); + if (!EntryOrErr) + error(EntryOrErr.takeError()); + ResourceEntryRef Entry = EntryOrErr.get(); + bool End = false; + while (!End) { + error(Entry.moveNext(End)); + EntryNumber++; + } + outs() << "Number of resources: " << EntryNumber << "\n"; + } + outs() << "Machine: "; + switch (Machine) { + case machine::ARM: + outs() << "ARM\n"; + break; + case machine::X86: + outs() << "X86\n"; + break; + default: + outs() << "X64\n"; + } return 0; } diff --git a/tools/llvm-cvtres/llvm-cvtres.h b/tools/llvm-cvtres/llvm-cvtres.h index eeaba196903..2e45b66461f 100644 --- a/tools/llvm-cvtres/llvm-cvtres.h +++ b/tools/llvm-cvtres/llvm-cvtres.h @@ -10,4 +10,10 @@ #ifndef LLVM_TOOLS_LLVMCVTRES_LLVMCVTRES_H #define LLVM_TOOLS_LLVMCVTRES_LLVMCVTRES_H +#include + +void error(std::error_code EC); + +enum class machine { UNKNOWN = 0, ARM, X64, X86 }; + #endif