LCOV - code coverage report
Current view: top level - source/ps - DllLoader.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 38 61 62.3 %
Date: 2022-06-14 00:41:00 Functions: 8 10 80.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "DllLoader.h"
      21             : 
      22             : #include "lib/timer.h"
      23             : #include "lib/posix/posix_dlfcn.h"
      24             : #include "ps/CStr.h"
      25             : 
      26             : #if OS_MACOSX
      27             : # include "lib/sysdep/os/osx/osx_bundle.h"
      28             : #endif
      29             : 
      30             : static void* const HANDLE_UNAVAILABLE = (void*)-1;
      31             : 
      32             : // directory to search for libraries (optionally set by --libdir at build-time,
      33             : // optionally overridden by -libdir at run-time in the test executable);
      34             : // if we don't have an explicit libdir then the linker will look in DT_RUNPATH
      35             : // (which we set to $ORIGIN) to find it in the executable's directory
      36             : #ifdef INSTALLED_LIBDIR
      37             : static CStr g_Libdir = STRINGIZE(INSTALLED_LIBDIR);
      38             : #else
      39             : static CStr g_Libdir = "";
      40             : #endif
      41             : 
      42             : // note: on Linux, lib is prepended to the SO file name
      43             : #if OS_UNIX
      44             : static CStr g_DllPrefix = "lib";
      45             : #else
      46             : static CStr g_DllPrefix = "";
      47             : #endif
      48             : 
      49             : // we usually want to use the same debug/release build type as the
      50             : // main executable (the library should also be efficient in release builds and
      51             : // allow easy symbol access in debug builds). however, that version of the
      52             : // library might be missing, so we check for both.
      53             : // this works because the interface is binary-compatible.
      54             : #ifndef NDEBUG
      55             : static CStr suffixes[] = { "_dbg", "" };    // (order matters)
      56             : #else
      57             : static CStr suffixes[] = { "", "_dbg" };
      58             : #endif
      59             : 
      60             : // NB: our Windows dlopen() function changes the extension to .dll
      61             : static CStr extensions[] = {
      62             :     ".so",
      63             : #if OS_MACOSX
      64             :     ".dylib"  // supported by OS X dlopen
      65             : #endif
      66             : };
      67             : 
      68             : // (This class is currently only used by 'Collada' and 'AtlasUI' which follow
      69             : // the naming/location convention above - it'll need to be changed if we want
      70             : // to support other DLLs.)
      71             : 
      72           6 : static CStr GenerateFilename(const CStr& name, const CStr& suffix, const CStr& extension)
      73             : {
      74           6 :     CStr n;
      75             : 
      76          12 :     if (!g_Libdir.empty())
      77           0 :         n = g_Libdir + "/";
      78             : 
      79             : #if OS_MACOSX
      80             :     if (osx_IsAppBundleValid())
      81             :     {
      82             :         // We are in a bundle, in which case the lib directory is ../Frameworks
      83             :         // relative to the binary, so we use a helper function to get the system path
      84             :         // (alternately we could use @executable_path as below, but this seems better)
      85             :         CStr frameworksPath = osx_GetBundleFrameworksPath();
      86             :         if (!frameworksPath.empty())
      87             :             n = frameworksPath + "/";
      88             :     }
      89             :     else
      90             :     {
      91             :         // On OS X, dlopen will search the current working directory for the library to
      92             :         // to load if given only a filename. But the CWD is not guaranteed to be
      93             :         // binaries/system (where our dylibs are placed) and it's not when e.g. the user
      94             :         // launches the game from Finder.
      95             :         // To work around this, we use the @executable_path variable, which should always
      96             :         // resolve to binaries/system, if we're not a bundle.
      97             :         // (see Apple's dyld(1) and dlopen(3) man pages for more info)
      98             :         n = "@executable_path/";
      99             :     }
     100             : #endif
     101             : 
     102           6 :     n += g_DllPrefix + name + suffix + extension;
     103           6 :     return n;
     104             : }
     105             : 
     106             : 
     107             : 
     108             : // @param name base name of the library (excluding prefix/suffix/extension)
     109             : // @param errors receives descriptions of any and all errors encountered
     110             : // @return valid handle or 0
     111           6 : static void* LoadAnyVariant(const CStr& name, std::stringstream& errors)
     112             : {
     113           6 :     for (size_t idxSuffix = 0; idxSuffix < ARRAY_SIZE(suffixes); idxSuffix++)
     114             :     {
     115           6 :         for (size_t idxExtension = 0; idxExtension < ARRAY_SIZE(extensions); idxExtension++)
     116             :         {
     117           6 :             CStr filename = GenerateFilename(name, suffixes[idxSuffix], extensions[idxExtension]);
     118             : 
     119             :             // we don't really care when relocations take place, but one of
     120             :             // {RTLD_NOW, RTLD_LAZY} must be specified. go with the former because
     121             :             // it is safer and matches the Windows load behavior.
     122           6 :             const int flags = RTLD_LOCAL|RTLD_NOW;
     123          12 :             void* handle = dlopen(filename.c_str(), flags);
     124           6 :             if (handle)
     125           6 :                 return handle;
     126             :             else
     127           0 :                 errors << "dlopen(" << filename << ") failed: " << dlerror() << "; ";
     128             :         }
     129             :     }
     130             : 
     131             :     return 0;   // none worked
     132             : }
     133             : 
     134             : 
     135          12 : DllLoader::DllLoader(const char* name, CLogger::ELogMethod loadErrorLogMethod)
     136          12 :     : m_Name(name), m_Handle(0), m_LoadErrorLogMethod(loadErrorLogMethod)
     137             : {
     138          12 : }
     139             : 
     140          24 : DllLoader::~DllLoader()
     141             : {
     142          24 :     if (IsLoaded())
     143           5 :         dlclose(m_Handle);
     144          12 : }
     145             : 
     146          17 : bool DllLoader::IsLoaded() const
     147             : {
     148          12 :     return (m_Handle != 0 && m_Handle != HANDLE_UNAVAILABLE);
     149             : }
     150             : 
     151           6 : bool DllLoader::LoadDLL()
     152             : {
     153             :     // first time: try to open the shared object
     154             :     // postcondition: m_Handle valid or == HANDLE_UNAVAILABLE.
     155           6 :     if (m_Handle == 0)
     156             :     {
     157          12 :         TIMER(L"LoadDLL");
     158             : 
     159          12 :         std::stringstream errors;
     160           6 :         m_Handle = LoadAnyVariant(m_Name, errors);
     161           6 :         if (!m_Handle)  // (only report errors if nothing worked)
     162             :         {
     163           0 :             LogLoadError(errors.str().c_str());
     164           0 :             m_Handle = HANDLE_UNAVAILABLE;
     165             :         }
     166             :     }
     167             : 
     168           6 :     return (m_Handle != HANDLE_UNAVAILABLE);
     169             : }
     170             : 
     171           1 : void DllLoader::Unload()
     172             : {
     173           1 :     if (!IsLoaded())
     174             :         return;
     175             : 
     176           1 :     dlclose(m_Handle);
     177           1 :     m_Handle = 0;
     178             : }
     179             : 
     180          24 : void DllLoader::LoadSymbolInternal(const char* name, void** fptr) const
     181             : {
     182          24 :     if (!IsLoaded())
     183             :     {
     184           0 :         debug_warn(L"Loading symbol from invalid DLL");
     185           0 :         *fptr = NULL;
     186           0 :         throw PSERROR_DllLoader_DllNotLoaded();
     187             :     }
     188             : 
     189          24 :     *fptr = dlsym(m_Handle, name);
     190          24 :     if (*fptr == NULL)
     191           0 :         throw PSERROR_DllLoader_SymbolNotFound();
     192          24 : }
     193             : 
     194           0 : void DllLoader::LogLoadError(const char* errors)
     195             : {
     196           0 :     switch (m_LoadErrorLogMethod)
     197             :     {
     198           0 :     case CLogger::Normal:
     199           0 :         LOGMESSAGE("DllLoader: %s", errors);
     200           0 :         break;
     201           0 :     case CLogger::Warning:
     202           0 :         LOGWARNING("DllLoader: %s", errors);
     203           0 :         break;
     204           0 :     case CLogger::Error:
     205           0 :         LOGERROR("DllLoader: %s", errors);
     206           0 :         break;
     207             :     }
     208           0 : }
     209             : 
     210           0 : void DllLoader::OverrideLibdir(const char* libdir)
     211             : {
     212           0 :     g_Libdir = libdir;
     213           0 : }

Generated by: LCOV version 1.13