Home | History | Annotate | Download | only in win
      1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // This file implements PEImage, a generic class to manipulate PE files.
      6 // This file was adapted from GreenBorder's Code.
      7 
      8 #include "base/win/pe_image.h"
      9 
     10 namespace base {
     11 namespace win {
     12 
     13 #if defined(_WIN64) && !defined(NACL_WIN64)
     14 // TODO(jschuh): crbug.com/167707 Make sure this is ok.
     15 #pragma message ("Warning: \
     16  This code is not tested on x64. Please make sure all the base unit tests\
     17  pass before doing any real work. The current unit tests don't test the\
     18  differences between 32- and 64-bits implementations. Bugs may slip through.\
     19  You need to improve the coverage before continuing.")
     20 #endif
     21 
     22 // Structure to perform imports enumerations.
     23 struct EnumAllImportsStorage {
     24   PEImage::EnumImportsFunction callback;
     25   PVOID cookie;
     26 };
     27 
     28 namespace {
     29 
     30   // Compare two strings byte by byte on an unsigned basis.
     31   //   if s1 == s2, return 0
     32   //   if s1 < s2, return negative
     33   //   if s1 > s2, return positive
     34   // Exception if inputs are invalid.
     35   int StrCmpByByte(LPCSTR s1, LPCSTR s2) {
     36     while (*s1 != '\0' && *s1 == *s2) {
     37       ++s1;
     38       ++s2;
     39     }
     40 
     41     return (*reinterpret_cast<const unsigned char*>(s1) -
     42             *reinterpret_cast<const unsigned char*>(s2));
     43   }
     44 
     45 }  // namespace
     46 
     47 // Callback used to enumerate imports. See EnumImportChunksFunction.
     48 bool ProcessImportChunk(const PEImage &image, LPCSTR module,
     49                         PIMAGE_THUNK_DATA name_table,
     50                         PIMAGE_THUNK_DATA iat, PVOID cookie) {
     51   EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>(
     52                                        cookie);
     53 
     54   return image.EnumOneImportChunk(storage.callback, module, name_table, iat,
     55                                   storage.cookie);
     56 }
     57 
     58 // Callback used to enumerate delay imports. See EnumDelayImportChunksFunction.
     59 bool ProcessDelayImportChunk(const PEImage &image,
     60                              PImgDelayDescr delay_descriptor,
     61                              LPCSTR module, PIMAGE_THUNK_DATA name_table,
     62                              PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat,
     63                              PIMAGE_THUNK_DATA unload_iat, PVOID cookie) {
     64   EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>(
     65                                        cookie);
     66 
     67   return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor,
     68                                        module, name_table, iat, bound_iat,
     69                                        unload_iat, storage.cookie);
     70 }
     71 
     72 void PEImage::set_module(HMODULE module) {
     73   module_ = module;
     74 }
     75 
     76 PIMAGE_DOS_HEADER PEImage::GetDosHeader() const {
     77   return reinterpret_cast<PIMAGE_DOS_HEADER>(module_);
     78 }
     79 
     80 PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const {
     81   PIMAGE_DOS_HEADER dos_header = GetDosHeader();
     82 
     83   return reinterpret_cast<PIMAGE_NT_HEADERS>(
     84       reinterpret_cast<char*>(dos_header) + dos_header->e_lfanew);
     85 }
     86 
     87 PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const {
     88   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
     89   PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers);
     90 
     91   if (section < nt_headers->FileHeader.NumberOfSections)
     92     return first_section + section;
     93   else
     94     return NULL;
     95 }
     96 
     97 WORD PEImage::GetNumSections() const {
     98   return GetNTHeaders()->FileHeader.NumberOfSections;
     99 }
    100 
    101 DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const {
    102   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
    103 
    104   return nt_headers->OptionalHeader.DataDirectory[directory].Size;
    105 }
    106 
    107 PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const {
    108   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
    109 
    110   return RVAToAddr(
    111       nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress);
    112 }
    113 
    114 PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const {
    115   PBYTE target = reinterpret_cast<PBYTE>(address);
    116   PIMAGE_SECTION_HEADER section;
    117 
    118   for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) {
    119     // Don't use the virtual RVAToAddr.
    120     PBYTE start = reinterpret_cast<PBYTE>(
    121                       PEImage::RVAToAddr(section->VirtualAddress));
    122 
    123     DWORD size = section->Misc.VirtualSize;
    124 
    125     if ((start <= target) && (start + size > target))
    126       return section;
    127   }
    128 
    129   return NULL;
    130 }
    131 
    132 PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName(
    133     LPCSTR section_name) const {
    134   if (NULL == section_name)
    135     return NULL;
    136 
    137   PIMAGE_SECTION_HEADER ret = NULL;
    138   int num_sections = GetNumSections();
    139 
    140   for (int i = 0; i < num_sections; i++) {
    141     PIMAGE_SECTION_HEADER section = GetSectionHeader(i);
    142     if (0 == _strnicmp(reinterpret_cast<LPCSTR>(section->Name), section_name,
    143                        sizeof(section->Name))) {
    144       ret = section;
    145       break;
    146     }
    147   }
    148 
    149   return ret;
    150 }
    151 
    152 PDWORD PEImage::GetExportEntry(LPCSTR name) const {
    153   PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory();
    154 
    155   if (NULL == exports)
    156     return NULL;
    157 
    158   WORD ordinal = 0;
    159   if (!GetProcOrdinal(name, &ordinal))
    160     return NULL;
    161 
    162   PDWORD functions = reinterpret_cast<PDWORD>(
    163                          RVAToAddr(exports->AddressOfFunctions));
    164 
    165   return functions + ordinal - exports->Base;
    166 }
    167 
    168 FARPROC PEImage::GetProcAddress(LPCSTR function_name) const {
    169   PDWORD export_entry = GetExportEntry(function_name);
    170   if (NULL == export_entry)
    171     return NULL;
    172 
    173   PBYTE function = reinterpret_cast<PBYTE>(RVAToAddr(*export_entry));
    174 
    175   PBYTE exports = reinterpret_cast<PBYTE>(
    176       GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT));
    177   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT);
    178 
    179   // Check for forwarded exports as a special case.
    180   if (exports <= function && exports + size > function)
    181 #pragma warning(push)
    182 #pragma warning(disable: 4312)
    183     // This cast generates a warning because it is 32 bit specific.
    184     return reinterpret_cast<FARPROC>(0xFFFFFFFF);
    185 #pragma warning(pop)
    186 
    187   return reinterpret_cast<FARPROC>(function);
    188 }
    189 
    190 bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const {
    191   if (NULL == ordinal)
    192     return false;
    193 
    194   PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory();
    195 
    196   if (NULL == exports)
    197     return false;
    198 
    199   if (IsOrdinal(function_name)) {
    200     *ordinal = ToOrdinal(function_name);
    201   } else {
    202     PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames));
    203     PDWORD lower = names;
    204     PDWORD upper = names + exports->NumberOfNames;
    205     int cmp = -1;
    206 
    207     // Binary Search for the name.
    208     while (lower != upper) {
    209       PDWORD middle = lower + (upper - lower) / 2;
    210       LPCSTR name = reinterpret_cast<LPCSTR>(RVAToAddr(*middle));
    211 
    212       // This may be called by sandbox before MSVCRT dll loads, so can't use
    213       // CRT function here.
    214       cmp = StrCmpByByte(function_name, name);
    215 
    216       if (cmp == 0) {
    217         lower = middle;
    218         break;
    219       }
    220 
    221       if (cmp > 0)
    222         lower = middle + 1;
    223       else
    224         upper = middle;
    225     }
    226 
    227     if (cmp != 0)
    228       return false;
    229 
    230 
    231     PWORD ordinals = reinterpret_cast<PWORD>(
    232                          RVAToAddr(exports->AddressOfNameOrdinals));
    233 
    234     *ordinal = ordinals[lower - names] + static_cast<WORD>(exports->Base);
    235   }
    236 
    237   return true;
    238 }
    239 
    240 bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const {
    241   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
    242   UINT num_sections = nt_headers->FileHeader.NumberOfSections;
    243   PIMAGE_SECTION_HEADER section = GetSectionHeader(0);
    244 
    245   for (UINT i = 0; i < num_sections; i++, section++) {
    246     PVOID section_start = RVAToAddr(section->VirtualAddress);
    247     DWORD size = section->Misc.VirtualSize;
    248 
    249     if (!callback(*this, section, section_start, size, cookie))
    250       return false;
    251   }
    252 
    253   return true;
    254 }
    255 
    256 bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const {
    257   PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT);
    258   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT);
    259 
    260   // Check if there are any exports at all.
    261   if (NULL == directory || 0 == size)
    262     return true;
    263 
    264   PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
    265                                         directory);
    266   UINT ordinal_base = exports->Base;
    267   UINT num_funcs = exports->NumberOfFunctions;
    268   UINT num_names = exports->NumberOfNames;
    269   PDWORD functions  = reinterpret_cast<PDWORD>(RVAToAddr(
    270                           exports->AddressOfFunctions));
    271   PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames));
    272   PWORD ordinals = reinterpret_cast<PWORD>(RVAToAddr(
    273                        exports->AddressOfNameOrdinals));
    274 
    275   for (UINT count = 0; count < num_funcs; count++) {
    276     PVOID func = RVAToAddr(functions[count]);
    277     if (NULL == func)
    278       continue;
    279 
    280     // Check for a name.
    281     LPCSTR name = NULL;
    282     UINT hint;
    283     for (hint = 0; hint < num_names; hint++) {
    284       if (ordinals[hint] == count) {
    285         name = reinterpret_cast<LPCSTR>(RVAToAddr(names[hint]));
    286         break;
    287       }
    288     }
    289 
    290     if (name == NULL)
    291       hint = 0;
    292 
    293     // Check for forwarded exports.
    294     LPCSTR forward = NULL;
    295     if (reinterpret_cast<char*>(func) >= reinterpret_cast<char*>(directory) &&
    296         reinterpret_cast<char*>(func) <= reinterpret_cast<char*>(directory) +
    297             size) {
    298       forward = reinterpret_cast<LPCSTR>(func);
    299       func = 0;
    300     }
    301 
    302     if (!callback(*this, ordinal_base + count, hint, name, func, forward,
    303                   cookie))
    304       return false;
    305   }
    306 
    307   return true;
    308 }
    309 
    310 bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const {
    311   PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC);
    312   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC);
    313   PIMAGE_BASE_RELOCATION base = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
    314       directory);
    315 
    316   if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION))
    317     return true;
    318 
    319   while (base->SizeOfBlock) {
    320     PWORD reloc = reinterpret_cast<PWORD>(base + 1);
    321     UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) /
    322         sizeof(WORD);
    323 
    324     for (UINT i = 0; i < num_relocs; i++, reloc++) {
    325       WORD type = *reloc >> 12;
    326       PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF));
    327 
    328       if (!callback(*this, type, address, cookie))
    329         return false;
    330     }
    331 
    332     base = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
    333                reinterpret_cast<char*>(base) + base->SizeOfBlock);
    334   }
    335 
    336   return true;
    337 }
    338 
    339 bool PEImage::EnumImportChunks(EnumImportChunksFunction callback,
    340                                PVOID cookie) const {
    341   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT);
    342   PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk();
    343 
    344   if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR))
    345     return true;
    346 
    347   for (; import->FirstThunk; import++) {
    348     LPCSTR module_name = reinterpret_cast<LPCSTR>(RVAToAddr(import->Name));
    349     PIMAGE_THUNK_DATA name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
    350                                        RVAToAddr(import->OriginalFirstThunk));
    351     PIMAGE_THUNK_DATA iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    352                                 RVAToAddr(import->FirstThunk));
    353 
    354     if (!callback(*this, module_name, name_table, iat, cookie))
    355       return false;
    356   }
    357 
    358   return true;
    359 }
    360 
    361 bool PEImage::EnumOneImportChunk(EnumImportsFunction callback,
    362                                  LPCSTR module_name,
    363                                  PIMAGE_THUNK_DATA name_table,
    364                                  PIMAGE_THUNK_DATA iat, PVOID cookie) const {
    365   if (NULL == name_table)
    366     return false;
    367 
    368   for (; name_table && name_table->u1.Ordinal; name_table++, iat++) {
    369     LPCSTR name = NULL;
    370     WORD ordinal = 0;
    371     WORD hint = 0;
    372 
    373     if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) {
    374       ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal));
    375     } else {
    376       PIMAGE_IMPORT_BY_NAME import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
    377           RVAToAddr(name_table->u1.ForwarderString));
    378 
    379       hint = import->Hint;
    380       name = reinterpret_cast<LPCSTR>(&import->Name);
    381     }
    382 
    383     if (!callback(*this, module_name, ordinal, name, hint, iat, cookie))
    384       return false;
    385   }
    386 
    387   return true;
    388 }
    389 
    390 bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const {
    391   EnumAllImportsStorage temp = { callback, cookie };
    392   return EnumImportChunks(ProcessImportChunk, &temp);
    393 }
    394 
    395 bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback,
    396                                     PVOID cookie) const {
    397   PVOID directory = GetImageDirectoryEntryAddr(
    398                         IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
    399   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
    400   PImgDelayDescr delay_descriptor = reinterpret_cast<PImgDelayDescr>(directory);
    401 
    402   if (directory == NULL || size == 0)
    403     return true;
    404 
    405   for (; delay_descriptor->rvaHmod; delay_descriptor++) {
    406     PIMAGE_THUNK_DATA name_table;
    407     PIMAGE_THUNK_DATA iat;
    408     PIMAGE_THUNK_DATA bound_iat;    // address of the optional bound IAT
    409     PIMAGE_THUNK_DATA unload_iat;   // address of optional copy of original IAT
    410     LPCSTR module_name;
    411 
    412     // check if VC7-style imports, using RVAs instead of
    413     // VC6-style addresses.
    414     bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0;
    415 
    416     if (rvas) {
    417       module_name = reinterpret_cast<LPCSTR>(
    418                         RVAToAddr(delay_descriptor->rvaDLLName));
    419       name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
    420                        RVAToAddr(delay_descriptor->rvaINT));
    421       iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    422                 RVAToAddr(delay_descriptor->rvaIAT));
    423       bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    424                       RVAToAddr(delay_descriptor->rvaBoundIAT));
    425       unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    426                        RVAToAddr(delay_descriptor->rvaUnloadIAT));
    427     } else {
    428 #pragma warning(push)
    429 #pragma warning(disable: 4312)
    430       // These casts generate warnings because they are 32 bit specific.
    431       module_name = reinterpret_cast<LPCSTR>(delay_descriptor->rvaDLLName);
    432       name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
    433                        delay_descriptor->rvaINT);
    434       iat = reinterpret_cast<PIMAGE_THUNK_DATA>(delay_descriptor->rvaIAT);
    435       bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    436                       delay_descriptor->rvaBoundIAT);
    437       unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
    438                        delay_descriptor->rvaUnloadIAT);
    439 #pragma warning(pop)
    440     }
    441 
    442     if (!callback(*this, delay_descriptor, module_name, name_table, iat,
    443                   bound_iat, unload_iat, cookie))
    444       return false;
    445   }
    446 
    447   return true;
    448 }
    449 
    450 bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback,
    451                                       PImgDelayDescr delay_descriptor,
    452                                       LPCSTR module_name,
    453                                       PIMAGE_THUNK_DATA name_table,
    454                                       PIMAGE_THUNK_DATA iat,
    455                                       PIMAGE_THUNK_DATA bound_iat,
    456                                       PIMAGE_THUNK_DATA unload_iat,
    457                                       PVOID cookie) const {
    458   UNREFERENCED_PARAMETER(bound_iat);
    459   UNREFERENCED_PARAMETER(unload_iat);
    460 
    461   for (; name_table->u1.Ordinal; name_table++, iat++) {
    462     LPCSTR name = NULL;
    463     WORD ordinal = 0;
    464     WORD hint = 0;
    465 
    466     if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) {
    467       ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal));
    468     } else {
    469       PIMAGE_IMPORT_BY_NAME import;
    470       bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0;
    471 
    472       if (rvas) {
    473         import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
    474                      RVAToAddr(name_table->u1.ForwarderString));
    475       } else {
    476 #pragma warning(push)
    477 #pragma warning(disable: 4312)
    478         // This cast generates a warning because it is 32 bit specific.
    479         import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
    480                      name_table->u1.ForwarderString);
    481 #pragma warning(pop)
    482       }
    483 
    484       hint = import->Hint;
    485       name = reinterpret_cast<LPCSTR>(&import->Name);
    486     }
    487 
    488     if (!callback(*this, module_name, ordinal, name, hint, iat, cookie))
    489       return false;
    490   }
    491 
    492   return true;
    493 }
    494 
    495 bool PEImage::EnumAllDelayImports(EnumImportsFunction callback,
    496                                   PVOID cookie) const {
    497   EnumAllImportsStorage temp = { callback, cookie };
    498   return EnumDelayImportChunks(ProcessDelayImportChunk, &temp);
    499 }
    500 
    501 bool PEImage::VerifyMagic() const {
    502   PIMAGE_DOS_HEADER dos_header = GetDosHeader();
    503 
    504   if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
    505     return false;
    506 
    507   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
    508 
    509   if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
    510     return false;
    511 
    512   if (nt_headers->FileHeader.SizeOfOptionalHeader !=
    513       sizeof(IMAGE_OPTIONAL_HEADER))
    514     return false;
    515 
    516   if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
    517     return false;
    518 
    519   return true;
    520 }
    521 
    522 bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const {
    523   LPVOID address = RVAToAddr(rva);
    524   return ImageAddrToOnDiskOffset(address, on_disk_offset);
    525 }
    526 
    527 bool PEImage::ImageAddrToOnDiskOffset(LPVOID address,
    528                                       DWORD *on_disk_offset) const {
    529   if (NULL == address)
    530     return false;
    531 
    532   // Get the section that this address belongs to.
    533   PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address);
    534   if (NULL == section_header)
    535     return false;
    536 
    537 #pragma warning(push)
    538 #pragma warning(disable: 4311)
    539   // These casts generate warnings because they are 32 bit specific.
    540   // Don't follow the virtual RVAToAddr, use the one on the base.
    541   DWORD offset_within_section = reinterpret_cast<DWORD>(address) -
    542                                     reinterpret_cast<DWORD>(PEImage::RVAToAddr(
    543                                         section_header->VirtualAddress));
    544 #pragma warning(pop)
    545 
    546   *on_disk_offset = section_header->PointerToRawData + offset_within_section;
    547   return true;
    548 }
    549 
    550 PVOID PEImage::RVAToAddr(DWORD rva) const {
    551   if (rva == 0)
    552     return NULL;
    553 
    554   return reinterpret_cast<char*>(module_) + rva;
    555 }
    556 
    557 PVOID PEImageAsData::RVAToAddr(DWORD rva) const {
    558   if (rva == 0)
    559     return NULL;
    560 
    561   PVOID in_memory = PEImage::RVAToAddr(rva);
    562   DWORD disk_offset;
    563 
    564   if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset))
    565     return NULL;
    566 
    567   return PEImage::RVAToAddr(disk_offset);
    568 }
    569 
    570 }  // namespace win
    571 }  // namespace base
    572