1 //===- COFFImportFile.cpp - COFF short import file implementation ---------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file defines the writeImportLibrary function.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "llvm/Object/COFFImportFile.h"
15 #include "llvm/ADT/ArrayRef.h"
16 #include "llvm/Object/Archive.h"
17 #include "llvm/Object/ArchiveWriter.h"
18 #include "llvm/Object/COFF.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Support/Path.h"
21 
22 #include <cstdint>
23 #include <map>
24 #include <set>
25 #include <string>
26 #include <vector>
27 
28 using namespace llvm::COFF;
29 using namespace llvm::object;
30 using namespace llvm;
31 
32 namespace llvm {
33 namespace object {
34 
35 static bool is32bit(MachineTypes Machine) {
36   switch (Machine) {
37   default:
38     llvm_unreachable("unsupported machine");
39   case IMAGE_FILE_MACHINE_ARM64:
40   case IMAGE_FILE_MACHINE_AMD64:
41     return false;
42   case IMAGE_FILE_MACHINE_ARMNT:
43   case IMAGE_FILE_MACHINE_I386:
44     return true;
45   }
46 }
47 
48 static uint16_t getImgRelRelocation(MachineTypes Machine) {
49   switch (Machine) {
50   default:
51     llvm_unreachable("unsupported machine");
52   case IMAGE_FILE_MACHINE_AMD64:
53     return IMAGE_REL_AMD64_ADDR32NB;
54   case IMAGE_FILE_MACHINE_ARMNT:
55     return IMAGE_REL_ARM_ADDR32NB;
56   case IMAGE_FILE_MACHINE_ARM64:
57     return IMAGE_REL_ARM64_ADDR32NB;
58   case IMAGE_FILE_MACHINE_I386:
59     return IMAGE_REL_I386_DIR32NB;
60   }
61 }
62 
63 template <class T> static void append(std::vector<uint8_t> &B, const T &Data) {
64   size_t S = B.size();
65   B.resize(S + sizeof(T));
66   memcpy(&B[S], &Data, sizeof(T));
67 }
68 
69 static void writeStringTable(std::vector<uint8_t> &B,
70                              ArrayRef<const std::string> Strings) {
71   // The COFF string table consists of a 4-byte value which is the size of the
72   // table, including the length field itself.  This value is followed by the
73   // string content itself, which is an array of null-terminated C-style
74   // strings.  The termination is important as they are referenced to by offset
75   // by the symbol entity in the file format.
76 
77   size_t Pos = B.size();
78   size_t Offset = B.size();
79 
80   // Skip over the length field, we will fill it in later as we will have
81   // computed the length while emitting the string content itself.
82   Pos += sizeof(uint32_t);
83 
84   for (const auto &S : Strings) {
85     B.resize(Pos + S.length() + 1);
86     strcpy(reinterpret_cast<char *>(&B[Pos]), S.c_str());
87     Pos += S.length() + 1;
88   }
89 
90   // Backfill the length of the table now that it has been computed.
91   support::ulittle32_t Length(B.size() - Offset);
92   support::endian::write32le(&B[Offset], Length);
93 }
94 
95 static ImportNameType getNameType(StringRef Sym, StringRef ExtName,
96                                   MachineTypes Machine) {
97   if (Sym != ExtName)
98     return IMPORT_NAME_UNDECORATE;
99   if (Machine == IMAGE_FILE_MACHINE_I386 && Sym.startswith("_"))
100     return IMPORT_NAME_NOPREFIX;
101   return IMPORT_NAME;
102 }
103 
104 static Expected<std::string> replace(StringRef S, StringRef From,
105                                      StringRef To) {
106   size_t Pos = S.find(From);
107 
108   // From and To may be mangled, but substrings in S may not.
109   if (Pos == StringRef::npos && From.startswith("_") && To.startswith("_")) {
110     From = From.substr(1);
111     To = To.substr(1);
112     Pos = S.find(From);
113   }
114 
115   if (Pos == StringRef::npos) {
116     return make_error<StringError>(
117       StringRef(Twine(S + ": replacing '" + From +
118         "' with '" + To + "' failed").str()), object_error::parse_failed);
119   }
120 
121   return (Twine(S.substr(0, Pos)) + To + S.substr(Pos + From.size())).str();
122 }
123 
124 static const std::string NullImportDescriptorSymbolName =
125     "__NULL_IMPORT_DESCRIPTOR";
126 
127 namespace {
128 // This class constructs various small object files necessary to support linking
129 // symbols imported from a DLL.  The contents are pretty strictly defined and
130 // nearly entirely static.  The details of the structures files are defined in
131 // WINNT.h and the PE/COFF specification.
132 class ObjectFactory {
133   using u16 = support::ulittle16_t;
134   using u32 = support::ulittle32_t;
135   MachineTypes Machine;
136   BumpPtrAllocator Alloc;
137   StringRef ImportName;
138   StringRef Library;
139   std::string ImportDescriptorSymbolName;
140   std::string NullThunkSymbolName;
141 
142 public:
143   ObjectFactory(StringRef S, MachineTypes M)
144       : Machine(M), ImportName(S), Library(S.drop_back(4)),
145         ImportDescriptorSymbolName(("__IMPORT_DESCRIPTOR_" + Library).str()),
146         NullThunkSymbolName(("\x7f" + Library + "_NULL_THUNK_DATA").str()) {}
147 
148   // Creates an Import Descriptor.  This is a small object file which contains a
149   // reference to the terminators and contains the library name (entry) for the
150   // import name table.  It will force the linker to construct the necessary
151   // structure to import symbols from the DLL.
152   NewArchiveMember createImportDescriptor(std::vector<uint8_t> &Buffer);
153 
154   // Creates a NULL import descriptor.  This is a small object file whcih
155   // contains a NULL import descriptor.  It is used to terminate the imports
156   // from a specific DLL.
157   NewArchiveMember createNullImportDescriptor(std::vector<uint8_t> &Buffer);
158 
159   // Create a NULL Thunk Entry.  This is a small object file which contains a
160   // NULL Import Address Table entry and a NULL Import Lookup Table Entry.  It
161   // is used to terminate the IAT and ILT.
162   NewArchiveMember createNullThunk(std::vector<uint8_t> &Buffer);
163 
164   // Create a short import file which is described in PE/COFF spec 7. Import
165   // Library Format.
166   NewArchiveMember createShortImport(StringRef Sym, uint16_t Ordinal,
167                                      ImportType Type, ImportNameType NameType);
168 
169   // Create a weak external file which is described in PE/COFF Aux Format 3.
170   NewArchiveMember createWeakExternal(StringRef Sym, StringRef Weak, bool Imp);
171 };
172 } // namespace
173 
174 NewArchiveMember
175 ObjectFactory::createImportDescriptor(std::vector<uint8_t> &Buffer) {
176   const uint32_t NumberOfSections = 2;
177   const uint32_t NumberOfSymbols = 7;
178   const uint32_t NumberOfRelocations = 3;
179 
180   // COFF Header
181   coff_file_header Header{
182       u16(Machine),
183       u16(NumberOfSections),
184       u32(0),
185       u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) +
186           // .idata$2
187           sizeof(coff_import_directory_table_entry) +
188           NumberOfRelocations * sizeof(coff_relocation) +
189           // .idata$4
190           (ImportName.size() + 1)),
191       u32(NumberOfSymbols),
192       u16(0),
193       u16(is32bit(Machine) ? IMAGE_FILE_32BIT_MACHINE : 0),
194   };
195   append(Buffer, Header);
196 
197   // Section Header Table
198   const coff_section SectionTable[NumberOfSections] = {
199       {{'.', 'i', 'd', 'a', 't', 'a', '$', '2'},
200        u32(0),
201        u32(0),
202        u32(sizeof(coff_import_directory_table_entry)),
203        u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section)),
204        u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) +
205            sizeof(coff_import_directory_table_entry)),
206        u32(0),
207        u16(NumberOfRelocations),
208        u16(0),
209        u32(IMAGE_SCN_ALIGN_4BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA |
210            IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)},
211       {{'.', 'i', 'd', 'a', 't', 'a', '$', '6'},
212        u32(0),
213        u32(0),
214        u32(ImportName.size() + 1),
215        u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) +
216            sizeof(coff_import_directory_table_entry) +
217            NumberOfRelocations * sizeof(coff_relocation)),
218        u32(0),
219        u32(0),
220        u16(0),
221        u16(0),
222        u32(IMAGE_SCN_ALIGN_2BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA |
223            IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)},
224   };
225   append(Buffer, SectionTable);
226 
227   // .idata$2
228   const coff_import_directory_table_entry ImportDescriptor{
229       u32(0), u32(0), u32(0), u32(0), u32(0),
230   };
231   append(Buffer, ImportDescriptor);
232 
233   const coff_relocation RelocationTable[NumberOfRelocations] = {
234       {u32(offsetof(coff_import_directory_table_entry, NameRVA)), u32(2),
235        u16(getImgRelRelocation(Machine))},
236       {u32(offsetof(coff_import_directory_table_entry, ImportLookupTableRVA)),
237        u32(3), u16(getImgRelRelocation(Machine))},
238       {u32(offsetof(coff_import_directory_table_entry, ImportAddressTableRVA)),
239        u32(4), u16(getImgRelRelocation(Machine))},
240   };
241   append(Buffer, RelocationTable);
242 
243   // .idata$6
244   auto S = Buffer.size();
245   Buffer.resize(S + ImportName.size() + 1);
246   memcpy(&Buffer[S], ImportName.data(), ImportName.size());
247   Buffer[S + ImportName.size()] = '\0';
248 
249   // Symbol Table
250   coff_symbol16 SymbolTable[NumberOfSymbols] = {
251       {{{0, 0, 0, 0, 0, 0, 0, 0}},
252        u32(0),
253        u16(1),
254        u16(0),
255        IMAGE_SYM_CLASS_EXTERNAL,
256        0},
257       {{{'.', 'i', 'd', 'a', 't', 'a', '$', '2'}},
258        u32(0),
259        u16(1),
260        u16(0),
261        IMAGE_SYM_CLASS_SECTION,
262        0},
263       {{{'.', 'i', 'd', 'a', 't', 'a', '$', '6'}},
264        u32(0),
265        u16(2),
266        u16(0),
267        IMAGE_SYM_CLASS_STATIC,
268        0},
269       {{{'.', 'i', 'd', 'a', 't', 'a', '$', '4'}},
270        u32(0),
271        u16(0),
272        u16(0),
273        IMAGE_SYM_CLASS_SECTION,
274        0},
275       {{{'.', 'i', 'd', 'a', 't', 'a', '$', '5'}},
276        u32(0),
277        u16(0),
278        u16(0),
279        IMAGE_SYM_CLASS_SECTION,
280        0},
281       {{{0, 0, 0, 0, 0, 0, 0, 0}},
282        u32(0),
283        u16(0),
284        u16(0),
285        IMAGE_SYM_CLASS_EXTERNAL,
286        0},
287       {{{0, 0, 0, 0, 0, 0, 0, 0}},
288        u32(0),
289        u16(0),
290        u16(0),
291        IMAGE_SYM_CLASS_EXTERNAL,
292        0},
293   };
294   // TODO: Name.Offset.Offset here and in the all similar places below
295   // suggests a names refactoring. Maybe StringTableOffset.Value?
296   SymbolTable[0].Name.Offset.Offset =
297       sizeof(uint32_t);
298   SymbolTable[5].Name.Offset.Offset =
299       sizeof(uint32_t) + ImportDescriptorSymbolName.length() + 1;
300   SymbolTable[6].Name.Offset.Offset =
301       sizeof(uint32_t) + ImportDescriptorSymbolName.length() + 1 +
302       NullImportDescriptorSymbolName.length() + 1;
303   append(Buffer, SymbolTable);
304 
305   // String Table
306   writeStringTable(Buffer,
307                    {ImportDescriptorSymbolName, NullImportDescriptorSymbolName,
308                     NullThunkSymbolName});
309 
310   StringRef F{reinterpret_cast<const char *>(Buffer.data()), Buffer.size()};
311   return {MemoryBufferRef(F, ImportName)};
312 }
313 
314 NewArchiveMember
315 ObjectFactory::createNullImportDescriptor(std::vector<uint8_t> &Buffer) {
316   const uint32_t NumberOfSections = 1;
317   const uint32_t NumberOfSymbols = 1;
318 
319   // COFF Header
320   coff_file_header Header{
321       u16(Machine),
322       u16(NumberOfSections),
323       u32(0),
324       u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) +
325           // .idata$3
326           sizeof(coff_import_directory_table_entry)),
327       u32(NumberOfSymbols),
328       u16(0),
329       u16(is32bit(Machine) ? IMAGE_FILE_32BIT_MACHINE : 0),
330   };
331   append(Buffer, Header);
332 
333   // Section Header Table
334   const coff_section SectionTable[NumberOfSections] = {
335       {{'.', 'i', 'd', 'a', 't', 'a', '$', '3'},
336        u32(0),
337        u32(0),
338        u32(sizeof(coff_import_directory_table_entry)),
339        u32(sizeof(coff_file_header) +
340            (NumberOfSections * sizeof(coff_section))),
341        u32(0),
342        u32(0),
343        u16(0),
344        u16(0),
345        u32(IMAGE_SCN_ALIGN_4BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA |
346            IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)},
347   };
348   append(Buffer, SectionTable);
349 
350   // .idata$3
351   const coff_import_directory_table_entry ImportDescriptor{
352       u32(0), u32(0), u32(0), u32(0), u32(0),
353   };
354   append(Buffer, ImportDescriptor);
355 
356   // Symbol Table
357   coff_symbol16 SymbolTable[NumberOfSymbols] = {
358       {{{0, 0, 0, 0, 0, 0, 0, 0}},
359        u32(0),
360        u16(1),
361        u16(0),
362        IMAGE_SYM_CLASS_EXTERNAL,
363        0},
364   };
365   SymbolTable[0].Name.Offset.Offset = sizeof(uint32_t);
366   append(Buffer, SymbolTable);
367 
368   // String Table
369   writeStringTable(Buffer, {NullImportDescriptorSymbolName});
370 
371   StringRef F{reinterpret_cast<const char *>(Buffer.data()), Buffer.size()};
372   return {MemoryBufferRef(F, ImportName)};
373 }
374 
375 NewArchiveMember ObjectFactory::createNullThunk(std::vector<uint8_t> &Buffer) {
376   const uint32_t NumberOfSections = 2;
377   const uint32_t NumberOfSymbols = 1;
378   uint32_t VASize = is32bit(Machine) ? 4 : 8;
379 
380   // COFF Header
381   coff_file_header Header{
382       u16(Machine),
383       u16(NumberOfSections),
384       u32(0),
385       u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) +
386           // .idata$5
387           VASize +
388           // .idata$4
389           VASize),
390       u32(NumberOfSymbols),
391       u16(0),
392       u16(is32bit(Machine) ? IMAGE_FILE_32BIT_MACHINE : 0),
393   };
394   append(Buffer, Header);
395 
396   // Section Header Table
397   const coff_section SectionTable[NumberOfSections] = {
398       {{'.', 'i', 'd', 'a', 't', 'a', '$', '5'},
399        u32(0),
400        u32(0),
401        u32(VASize),
402        u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section)),
403        u32(0),
404        u32(0),
405        u16(0),
406        u16(0),
407        u32((is32bit(Machine) ? IMAGE_SCN_ALIGN_4BYTES
408                              : IMAGE_SCN_ALIGN_8BYTES) |
409            IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |
410            IMAGE_SCN_MEM_WRITE)},
411       {{'.', 'i', 'd', 'a', 't', 'a', '$', '4'},
412        u32(0),
413        u32(0),
414        u32(VASize),
415        u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) +
416            VASize),
417        u32(0),
418        u32(0),
419        u16(0),
420        u16(0),
421        u32((is32bit(Machine) ? IMAGE_SCN_ALIGN_4BYTES
422                              : IMAGE_SCN_ALIGN_8BYTES) |
423            IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |
424            IMAGE_SCN_MEM_WRITE)},
425   };
426   append(Buffer, SectionTable);
427 
428   // .idata$5, ILT
429   append(Buffer, u32(0));
430   if (!is32bit(Machine))
431     append(Buffer, u32(0));
432 
433   // .idata$4, IAT
434   append(Buffer, u32(0));
435   if (!is32bit(Machine))
436     append(Buffer, u32(0));
437 
438   // Symbol Table
439   coff_symbol16 SymbolTable[NumberOfSymbols] = {
440       {{{0, 0, 0, 0, 0, 0, 0, 0}},
441        u32(0),
442        u16(1),
443        u16(0),
444        IMAGE_SYM_CLASS_EXTERNAL,
445        0},
446   };
447   SymbolTable[0].Name.Offset.Offset = sizeof(uint32_t);
448   append(Buffer, SymbolTable);
449 
450   // String Table
451   writeStringTable(Buffer, {NullThunkSymbolName});
452 
453   StringRef F{reinterpret_cast<const char *>(Buffer.data()), Buffer.size()};
454   return {MemoryBufferRef{F, ImportName}};
455 }
456 
457 NewArchiveMember ObjectFactory::createShortImport(StringRef Sym,
458                                                   uint16_t Ordinal,
459                                                   ImportType ImportType,
460                                                   ImportNameType NameType) {
461   size_t ImpSize = ImportName.size() + Sym.size() + 2; // +2 for NULs
462   size_t Size = sizeof(coff_import_header) + ImpSize;
463   char *Buf = Alloc.Allocate<char>(Size);
464   memset(Buf, 0, Size);
465   char *P = Buf;
466 
467   // Write short import library.
468   auto *Imp = reinterpret_cast<coff_import_header *>(P);
469   P += sizeof(*Imp);
470   Imp->Sig2 = 0xFFFF;
471   Imp->Machine = Machine;
472   Imp->SizeOfData = ImpSize;
473   if (Ordinal > 0)
474     Imp->OrdinalHint = Ordinal;
475   Imp->TypeInfo = (NameType << 2) | ImportType;
476 
477   // Write symbol name and DLL name.
478   memcpy(P, Sym.data(), Sym.size());
479   P += Sym.size() + 1;
480   memcpy(P, ImportName.data(), ImportName.size());
481 
482   return {MemoryBufferRef(StringRef(Buf, Size), ImportName)};
483 }
484 
485 NewArchiveMember ObjectFactory::createWeakExternal(StringRef Sym,
486                                                    StringRef Weak, bool Imp) {
487   std::vector<uint8_t> Buffer;
488   const uint32_t NumberOfSections = 1;
489   const uint32_t NumberOfSymbols = 5;
490 
491   // COFF Header
492   coff_file_header Header{
493       u16(0),
494       u16(NumberOfSections),
495       u32(0),
496       u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section))),
497       u32(NumberOfSymbols),
498       u16(0),
499       u16(0),
500   };
501   append(Buffer, Header);
502 
503   // Section Header Table
504   const coff_section SectionTable[NumberOfSections] = {
505       {{'.', 'd', 'r', 'e', 'c', 't', 'v', 'e'},
506        u32(0),
507        u32(0),
508        u32(0),
509        u32(0),
510        u32(0),
511        u32(0),
512        u16(0),
513        u16(0),
514        u32(IMAGE_SCN_LNK_INFO | IMAGE_SCN_LNK_REMOVE)}};
515   append(Buffer, SectionTable);
516 
517   // Symbol Table
518   coff_symbol16 SymbolTable[NumberOfSymbols] = {
519       {{{'@', 'c', 'o', 'm', 'p', '.', 'i', 'd'}},
520        u32(0),
521        u16(0xFFFF),
522        u16(0),
523        IMAGE_SYM_CLASS_STATIC,
524        0},
525       {{{'@', 'f', 'e', 'a', 't', '.', '0', '0'}},
526        u32(0),
527        u16(0xFFFF),
528        u16(0),
529        IMAGE_SYM_CLASS_STATIC,
530        0},
531       {{{0, 0, 0, 0, 0, 0, 0, 0}},
532        u32(0),
533        u16(0),
534        u16(0),
535        IMAGE_SYM_CLASS_EXTERNAL,
536        0},
537       {{{0, 0, 0, 0, 0, 0, 0, 0}},
538        u32(0),
539        u16(0),
540        u16(0),
541        IMAGE_SYM_CLASS_WEAK_EXTERNAL,
542        1},
543       {{{2, 0, 0, 0, 3, 0, 0, 0}}, u32(0), u16(0), u16(0), uint8_t(0), 0},
544   };
545   SymbolTable[2].Name.Offset.Offset = sizeof(uint32_t);
546 
547   //__imp_ String Table
548   StringRef Prefix = Imp ? "__imp_" : "";
549   SymbolTable[3].Name.Offset.Offset =
550       sizeof(uint32_t) + Sym.size() + Prefix.size() + 1;
551   append(Buffer, SymbolTable);
552   writeStringTable(Buffer, {(Prefix + Sym).str(),
553                             (Prefix + Weak).str()});
554 
555   // Copied here so we can still use writeStringTable
556   char *Buf = Alloc.Allocate<char>(Buffer.size());
557   memcpy(Buf, Buffer.data(), Buffer.size());
558   return {MemoryBufferRef(StringRef(Buf, Buffer.size()), ImportName)};
559 }
560 
561 std::error_code writeImportLibrary(StringRef ImportName, StringRef Path,
562                                    ArrayRef<COFFShortExport> Exports,
563                                    MachineTypes Machine, bool MakeWeakAliases) {
564 
565   std::vector<NewArchiveMember> Members;
566   ObjectFactory OF(llvm::sys::path::filename(ImportName), Machine);
567 
568   std::vector<uint8_t> ImportDescriptor;
569   Members.push_back(OF.createImportDescriptor(ImportDescriptor));
570 
571   std::vector<uint8_t> NullImportDescriptor;
572   Members.push_back(OF.createNullImportDescriptor(NullImportDescriptor));
573 
574   std::vector<uint8_t> NullThunk;
575   Members.push_back(OF.createNullThunk(NullThunk));
576 
577   for (COFFShortExport E : Exports) {
578     if (E.Private)
579       continue;
580 
581     if (E.isWeak() && MakeWeakAliases) {
582       Members.push_back(OF.createWeakExternal(E.Name, E.ExtName, false));
583       Members.push_back(OF.createWeakExternal(E.Name, E.ExtName, true));
584       continue;
585     }
586 
587     ImportType ImportType = IMPORT_CODE;
588     if (E.Data)
589       ImportType = IMPORT_DATA;
590     if (E.Constant)
591       ImportType = IMPORT_CONST;
592 
593     StringRef SymbolName = E.SymbolName.empty() ? E.Name : E.SymbolName;
594     ImportNameType NameType = getNameType(SymbolName, E.Name, Machine);
595     Expected<std::string> Name = E.ExtName.empty()
596                                      ? SymbolName
597                                      : replace(SymbolName, E.Name, E.ExtName);
598 
599     if (!Name) {
600       return errorToErrorCode(Name.takeError());
601     }
602 
603     Members.push_back(
604         OF.createShortImport(*Name, E.Ordinal, ImportType, NameType));
605   }
606 
607   return writeArchive(Path, Members, /*WriteSymtab*/ true,
608                       object::Archive::K_GNU,
609                       /*Deterministic*/ true, /*Thin*/ false);
610 }
611 
612 } // namespace object
613 } // namespace llvm
614