1 /*
2     Copyright (c) 2005-2020 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #include "tbb/tbb_config.h"
18 #if !__TBB_WIN8UI_SUPPORT
19 #define TBB_PREVIEW_RUNTIME_LOADER 1
20 #include "tbb/runtime_loader.h"
21 #include "tbb/tbb_stddef.h"
22 #include "tbb_environment.h"
23 
24 // C standard headers.
25 #include <cctype>            // isspace
26 #include <cstdarg>           // va_list, etc.
27 #include <cstdio>            // fprintf, stderr, etc.
28 #include <cstdlib>           // malloc, free, abort.
29 #include <cstring>           // strlen, etc.
30 
31 // C++ standard headers.
32 #include <typeinfo>
33 
34 // OS-specific includes.
35 #if _WIN32 || _WIN64
36     #include <windows.h>
37     #define snprintf _snprintf
38     #undef max
39 #else
40     #include <dlfcn.h>    // dlopen, dlsym, dlclose, dlerror.
41 #endif
42 
43 #if TBB_USE_ASSERT
44     // We cannot use __TBB_ASSERT as it is because it calls a function from tbb library which may
45     // be not yet loaded. Redefine __TBB_ASSERT not to call tbb functions.
46     #undef __TBB_ASSERT
47     #define __TBB_ASSERT( cond, msg ) {                                                            \
48         if ( ! (cond) ) {                                                                          \
49             say( "%s:%d: Assertion failed: %s.", __FILE__, __LINE__, (msg) );                      \
50         } /* if */                                                                                 \
51         /* TODO: abort? */                                                                         \
52     }
53 #endif
54 
55 // Declare here, define at the bottom.
56 extern "C" int __tbb_internal_runtime_loader_stub();
57 
58 namespace tbb {
59 
60 namespace interface6 {
61 
62 namespace internal {
63 
64 namespace runtime_loader {
65 
66 
67 /*
68     ------------------------------------------------------------------------------------------------
69     User interaction utilities.
70     ------------------------------------------------------------------------------------------------
71 */
72 
73 
74 // Print message to stderr. Do not call it directly, use say() or tell() instead.
_say(char const * format,va_list args)75 static void _say( char const * format, va_list args ) {
76     /*
77         On 64-bit Linux* OS, vsnprintf() modifies args argument,
78         so vsnprintf() crashes if it is called for the second time with the same args.
79         To prevent the crash, we have to pass a fresh intact copy of args to vsnprintf() each time.
80 
81         On Windows* OS, unfortunately, standard va_copy() macro is not available. However, it
82         seems vsnprintf() does not modify args argument.
83     */
84     #if ! ( _WIN32 || _WIN64 )
85         va_list _args;
86         __va_copy( _args, args );  // Make copy of args.
87         #define args _args         // Substitute args with its copy, _args.
88     #endif
89     int len = vsnprintf( NULL, 0, format, args );
90     #if ! ( _WIN32 || _WIN64 )
91         #undef args                // Remove substitution.
92         va_end( _args );
93     #endif
94     char * buf = reinterpret_cast< char * >( malloc( len + 1 ) );
95     if ( buf != NULL ) {
96         vsnprintf( buf, len + 1, format, args );
97         fprintf( stderr, "TBB: %s\n", buf );
98         free( buf );
99     } else {
100         fprintf( stderr, "TBB: Not enough memory for message: %s\n", format );
101     }
102 } // _say
103 
104 
105 // Debug/test/troubleshooting printing controlled by TBB_VERSION environment variable.
106 // To enable printing, the variable must be set and not empty.
107 // Do not call it directly, use tell() instead.
_tell(char const * format,va_list args)108 static void _tell( char const * format, va_list args ) {
109     if ( tbb::internal::GetBoolEnvironmentVariable("TBB_VERSION") ) {
110         _say( format, args );
111     } // if
112 } // _tell
113 
114 
115 // Print message to stderr unconditionally.
say(char const * format,...)116 static void say( char const * format, ... ) {
117     va_list args;
118     va_start( args, format );
119     _say( format, args );
120     va_end( args );
121 } // say
122 
123 
124 // Debug/test/troubleshooting printing controlled by TBB_VERSION environment variable.
125 // To enable printing, the variable must be set and not empty.
tell(char const * format,...)126 static void tell( char const * format, ... ) {
127     va_list args;
128     va_start( args, format );
129     _tell( format, args );
130     va_end( args );
131 } // tell
132 
133 
134 // Error reporting utility. Behavior depends on mode.
error(tbb::runtime_loader::error_mode mode,tbb::runtime_loader::error_code err,char const * format,...)135 static tbb::runtime_loader::error_code error( tbb::runtime_loader::error_mode mode, tbb::runtime_loader::error_code err, char const * format, ... ) {
136     va_list args;
137     va_start( args, format );
138     if ( mode == tbb::runtime_loader::em_abort ) {
139         // In em_abort mode error message printed unconditionally.
140         _say( format, args );
141     } else {
142         // In other modes printing depends on TBB_VERSION environment variable.
143         _tell( format, args );
144     } // if
145     va_end( args );
146     switch ( mode ) {
147         case tbb::runtime_loader::em_abort : {
148             say( "Aborting..." );
149             #if TBB_USE_DEBUG && ( _WIN32 || _WIN64 )
150                 DebugBreak();
151             #endif
152             abort();
153         } break;
154         case tbb::runtime_loader::em_throw : {
155             throw err;
156         } break;
157         case tbb::runtime_loader::em_status : {
158             // Do nothing.
159         } break;
160     } // switch
161     return err;
162 } // error
163 
164 
165 /*
166     ------------------------------------------------------------------------------------------------
167     General-purpose string manipulation utilities.
168     ------------------------------------------------------------------------------------------------
169 */
170 
171 
172 // Delete character ch from string str in-place.
strip(char * str,char ch)173 static void strip( char * str, char ch ) {
174     int in  = 0;  // Input character index.
175     int out = 0;  // Output character index.
176     for ( ; ; ) {
177         if ( str[ in ] != ch ) {
178             str[ out ] = str[ in ];
179             ++ out;
180         } // if
181         if ( str[ in ] == 0 ) {
182             break;
183         } // if
184         ++ in;
185     } // forever
186 } // func strip
187 
188 
189 // Strip trailing whitespaces in-place.
trim(char * str)190 static void trim( char * str ) {
191     size_t len = strlen( str );
192     while ( len > 0 && isspace( str[ len - 1 ] ) ) {
193         -- len;
194     } // while
195     str[ len ] = 0;
196 } // func trim
197 
198 
199 #if _WIN32 || _WIN64
200     // "When specifying a path, be sure to use backslashes (\), not forward slashes (/)."
201     // (see http://msdn.microsoft.com/en-us/library/ms886736.aspx).
202     const char proper_slash = '\\';
char_or_slash(char c)203     inline char char_or_slash( char c ) { return c=='/'? '\\': c; }
204 #else
205     const char proper_slash = '/';
char_or_slash(char c)206     inline char char_or_slash( char c ) { return c; }
207 #endif
208 
209 // Concatenate name of directory and name of file.
cat_file(char const * dir,char const * file,char * buffer,size_t len)210 void cat_file( char const * dir, char const * file, char * buffer, size_t len ) {
211     size_t i = 0;
212     // Copy directory name
213     for( ; i<len && *dir; ++i, ++dir ) {
214         buffer[i] = char_or_slash(*dir);
215     }
216     // Append trailing slash if missed.
217     if( i>0 && i<len && buffer[i-1]!=proper_slash ) {
218         buffer[i++] = proper_slash;
219     }
220     // Copy file name
221     __TBB_ASSERT( char_or_slash(*file)!=proper_slash, "File name starts with a slash" );
222     for( ; i<len && *file; ++i, ++file ) {
223         buffer[i] = *file;
224     }
225     // Append null terminator
226     buffer[ i<len? i: len-1 ] = '\0';
227 } // cat_file
228 
229 
230 /*
231     ------------------------------------------------------------------------------------------------
232     Windows implementation of dlopen, dlclose, dlsym, dlerror.
233     ------------------------------------------------------------------------------------------------
234 */
235 
236 
237 #if _WIN32 || _WIN64
238 
239     // Implement Unix-like interface (dlopen, dlclose, dlsym, dlerror) via Win32 API functions.
240 
241     // Type of dlopen result.
242     typedef HMODULE handle_t;
243 
244     enum rtld_flags_t {
245         RTLD_NOW,
246         RTLD_GLOBAL
247     }; // enum rtld_flags_t
248 
249     // Unix-like dlopen().
dlopen(char const * name,rtld_flags_t)250     static handle_t dlopen( char const * name, rtld_flags_t ) {
251         return LoadLibrary( name );
252     } // dlopen
253 
254     // Unix-like dlsym().
dlsym(handle_t lib,char const * sym)255     static void * dlsym( handle_t lib, char const * sym ) {
256         return (void*)GetProcAddress( lib, sym );
257     } // dlsym
258 
259     // Unix-like dlclose().
dlclose(handle_t lib)260     static int dlclose( handle_t lib ) {
261         return ! FreeLibrary( lib );
262     } // dlclose
263 
264     // The function mimics Unix dlerror() function.
265     // Note: Not thread-safe due to statically allocated buffer.
dlerror()266     static char * dlerror() {
267 
268         static char buffer[ 2048 ];  // Note: statically allocated buffer.
269 
270         DWORD err = GetLastError();
271         if ( err == ERROR_SUCCESS ) {
272             return NULL;
273         } // if
274 
275         DWORD rc;
276         rc =
277             FormatMessage(
278                 FORMAT_MESSAGE_FROM_SYSTEM,
279                 NULL,
280                 err,
281                 MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language.
282                 reinterpret_cast< LPTSTR >( & buffer ),
283                 sizeof( buffer ),
284                 NULL
285             );
286         if ( rc == 0 ) {
287             // FormatMessage() failed to format system error message. Buffer to short or another issue.
288             snprintf( buffer, sizeof( buffer ), "System error %u.", err );
289         } else {
290             /*
291                 FormatMessage() returns Windows-style end-of-lines, "\r\n". When string is printed,
292                 printf() also replaces all the occurrences of "\n" with "\r\n" (again!), so sequences
293                 like "\r\r\r\n" appear in output. It is not too good. Stripping all "\r" normalizes
294                 string and returns it to canonical form, so printf() will produce correct end-of-line
295                 sequences.
296             */
297             strip( buffer, '\r' );   // Delete carriage returns if any.
298             trim( buffer );          // Delete trailing newlines and spaces.
299         } // if
300 
301         return buffer;
302 
303     } // dlerror
304 
305 #else
306 
307     // Type of dlopen() result.
308     typedef void * handle_t;
309 
310 #endif
311 
312 
313 /*
314     ------------------------------------------------------------------------------------------------
315     Runtime loader stuff.
316     ------------------------------------------------------------------------------------------------
317 */
318 
319 
320 // Descriptor table declaration. It is defined in assembler file.
321 enum symbol_type_t {
322     st_object   = 0,
323     st_function = 1
324 }; // enum symbol_type_t
325 struct symbol_t {
326     void *        addr;
327     char const *  name;
328     int           size;
329     symbol_type_t type;
330 }; // symbol_t
331 extern "C" symbol_t __tbb_internal_runtime_loader_symbols[];
332 
333 // Hooks for internal use (e. g. for testing).
334 tbb::runtime_loader::error_mode stub_mode = tbb::runtime_loader::em_abort;
335 
336 static char const * tbb_dll_name = __TBB_STRING(__TBB_DLL_NAME);  // Name of TBB library.
337 static handle_t     handle       = NULL;                          // Handle of loaded TBB library or NULL.
338 static int          version      = 0;                             // Version of the loaded library.
339 static int          counter      = 0;                             // Number of runtime_loader objects using the loaded library.
340 
341 #define ANOTHER_RTL "probably multiple runtime_loader objects work in parallel"
342 
343 
344 // One attempt to load library (dll_name can be a full path or just a file name).
_load(char const * dll_name,int min_ver,int max_ver)345 static tbb::runtime_loader::error_code _load( char const * dll_name, int min_ver, int max_ver ) {
346 
347     tbb::runtime_loader::error_mode mode = tbb::runtime_loader::em_status;
348     tbb::runtime_loader::error_code code = tbb::runtime_loader::ec_ok;
349 
350     /*
351         If these variables declared at the first usage, Intel(R) C++ Compiler may issue warning(s):
352             transfer of control [goto error] bypasses initialization of: ...
353         Declaring variables at the beginning of the function eliminates warnings.
354     */
355     typedef int (*int_func_t)( void );
356     char const * get_ver_name = "TBB_runtime_interface_version"; // Name of function.
357     int_func_t   get_ver_func = NULL;                            // Pointer to function.
358     handle_t     _handle      = NULL;
359     int          _version     = 0;
360     int          total        = 0;
361     int          not_found    = 0;
362 
363     // This function should be called iff there is no loaded library.
364     __TBB_ASSERT( handle  == NULL, "Handle is invalid; "  ANOTHER_RTL );
365     __TBB_ASSERT( version == 0,    "Version is invalid; " ANOTHER_RTL );
366     __TBB_ASSERT( counter == 0,    "Counter is invalid; " ANOTHER_RTL );
367 
368     tell( "Loading \"%s\"...", dll_name );
369 
370     // First load the library.
371     _handle = dlopen( dll_name, RTLD_NOW );
372     if ( _handle == NULL ) {
373         const char * msg = dlerror();
374         code = error( mode, tbb::runtime_loader::ec_no_lib, "Loading \"%s\" failed; system error: %s", dll_name, msg );
375         goto error;
376     } // if
377 
378     // Then try to find out its version.
379     /*
380         g++ 3.4 issues error:
381             ISO C++ forbids casting between pointer-to-function and pointer-to-object
382         on reinterpret_cast<>. Thus, we have no choice but using C-style type cast.
383     */
384     get_ver_func = (int_func_t) dlsym( _handle, get_ver_name );
385     if ( get_ver_func == NULL ) {
386         code = error( mode, tbb::runtime_loader::ec_bad_lib, "Symbol \"%s\" not found; library rejected.", get_ver_name );
387         goto error;
388     } // if
389     _version = get_ver_func();
390     if ( ! ( min_ver <= _version && _version <= max_ver ) ) {
391         code = error( mode, tbb::runtime_loader::ec_bad_ver, "Version %d is out of requested range; library rejected.", _version );
392         goto error;
393     } // if
394 
395     // Library is suitable. Mark it as loaded.
396     handle   = _handle;
397     version  = _version;
398     counter += 1;
399     __TBB_ASSERT( counter == 1, "Counter is invalid; " ANOTHER_RTL );
400 
401     // Now search for all known symbols.
402     for ( int i = 0; __tbb_internal_runtime_loader_symbols[ i ].name != NULL; ++ i ) {
403         symbol_t & symbol = __tbb_internal_runtime_loader_symbols[ i ];
404         // Verify symbol descriptor.
405         __TBB_ASSERT( symbol.type == st_object || symbol.type == st_function, "Invalid symbol type" );
406         #if _WIN32 || _WIN64
407             __TBB_ASSERT( symbol.type == st_function, "Should not be symbols of object type on Windows" );
408         #endif
409         if ( symbol.type == st_object ) {
410             __TBB_ASSERT( symbol.addr != NULL, "Object address invalid" );
411             __TBB_ASSERT( symbol.size > 0, "Symbol size must be > 0" );
412             __TBB_ASSERT( symbol.size <= 0x1000, "Symbol size too big" );
413         } else {                     // Function
414             // __TBB_ASSERT( symbol.addr == reinterpret_cast< void * >( & stub ), "Invalid symbol address" );
415             __TBB_ASSERT( symbol.size == sizeof( void * ), "Invalid symbol size" );
416         } // if
417         void * addr = dlsym( _handle, symbol.name );
418         if ( addr != NULL ) {
419             if ( symbol.type == st_object ) {
420                 if ( strncmp( symbol.name, "_ZTS", 4 ) == 0 ) {
421                     // If object name begins with "_ZTS", it is a string, mangled type name.
422                     // Its value must equal to name of symbol without "_ZTS" prefix.
423                     char const * name = static_cast< char const * >( addr );
424                     __TBB_ASSERT( strlen( name ) + 1 == size_t( symbol.size ), "Unexpected size of typeinfo name" );
425                     __TBB_ASSERT( strcmp( symbol.name + 4, name ) == 0, "Unexpected content of typeinfo name" );
426                     strncpy( reinterpret_cast< char * >( symbol.addr ), name, symbol.size );
427                     reinterpret_cast< char * >( symbol.addr )[ symbol.size - 1 ] = 0;
428                 } else {
429                     #if TBB_USE_ASSERT
430                         // If object name begins with "_ZTI", it is an object of std::type_info class.
431                         // Its protected value must equal to name of symbol without "_ZTI" prefix.
432                         if ( strncmp( symbol.name, "_ZTI", 4 ) == 0 ) {
433                             std::type_info const * info = static_cast< std::type_info const * >( addr );
434                             __TBB_ASSERT( size_t( symbol.size ) >= sizeof( std::type_info ), "typeinfo size is too small" );
435                             // std::type_info::name is not a virtual method, it is safe to call it.
436                             __TBB_ASSERT( strcmp( symbol.name + 4, info->name() ) == 0, "Unexpected content of typeinfo" );
437                         } // if
438                     #endif
439                     // Copy object content from libtbb into runtime_loader.
440                     memcpy( symbol.addr, addr, symbol.size );
441                 }; // if
442             } else {                     // Function
443                 symbol.addr = addr;
444             } // if
445         } else {
446             char const * msg = dlerror();
447             tell( "Symbol \"%s\" not found; system error: %s", symbol.name, msg );
448             ++ not_found;
449         } // if
450         ++ total;
451     } // for i
452 
453     if ( not_found > 0 ) {
454         tell( "%d of %d symbols not found.", not_found, total );
455     } // if
456 
457     tell( "The library successfully loaded." );
458     return code;
459 
460     error:
461         if ( _handle != NULL ) {
462             int rc = dlclose( _handle );
463             if ( rc != 0 ) {
464                 // Error occurred.
465                 __TBB_ASSERT( rc != 0, "Unexpected error: dlclose() failed" );
466             } // if
467         } // if
468         _handle = NULL;
469         return code;
470 
471 } // _load
472 
473 
load(tbb::runtime_loader::error_mode mode,char const * path[],int min_ver,int max_ver)474 static tbb::runtime_loader::error_code load( tbb::runtime_loader::error_mode mode, char const * path[], int min_ver, int max_ver ) {
475     // Check arguments first.
476     if ( min_ver <= 0 ) {
477         return error( mode, tbb::runtime_loader::ec_bad_arg, "tbb::runtime_loader::load(): Invalid value of min_ver argument: %d.", min_ver );
478     } // if
479     if ( max_ver <= 0 ) {
480         return error( mode, tbb::runtime_loader::ec_bad_arg, "tbb::runtime_loader::load(): Invalid value of max_ver argument: %d.", max_ver );
481     } // if
482     if ( min_ver > max_ver ) {
483         return error( mode, tbb::runtime_loader::ec_bad_arg, "tbb::runtime_loader::load(): min_ver and max_ver specify empty range: [%d, %d].", min_ver, max_ver );
484     } // if
485     if ( min_ver == max_ver ) {
486         tell( "Searching for \"%s\" version %d...", tbb_dll_name, min_ver );
487     } else if ( max_ver == INT_MAX ) {
488         tell( "Searching for \"%s\" version %d+...", tbb_dll_name, min_ver );
489     } else {
490         tell( "Searching for \"%s\" version in range [%d, %d]...", tbb_dll_name, min_ver, max_ver );
491     } // if
492     // Then check whether a library already loaded.
493     if ( handle != NULL ) {
494         // Library already loaded. Check whether the version is compatible.
495         __TBB_ASSERT( version > 0, "Version is invalid; " ANOTHER_RTL );
496         __TBB_ASSERT( counter > 0, "Counter is invalid; " ANOTHER_RTL );
497         if ( min_ver <= version && version <= max_ver ) {
498             // Version is ok, let us use this library.
499             tell( "Library version %d is already loaded.", version );
500             counter += 1;
501             return tbb::runtime_loader::ec_ok;
502         } else {
503             // Version is not suitable.
504             return error( mode, tbb::runtime_loader::ec_bad_ver, "Library version %d is already loaded.", version );
505         } // if
506     } // if
507     // There is no loaded library, try to load it using provided directories.
508     __TBB_ASSERT( version == 0, "Version is invalid; " ANOTHER_RTL );
509     __TBB_ASSERT( counter == 0, "Counter is invalid; " ANOTHER_RTL );
510     size_t namelen = strlen(tbb_dll_name);
511     size_t buflen = 0;
512     char * buffer = NULL;
513     for ( int i = 0; path[i] != NULL; ++ i ) {
514         size_t len = strlen(path[i]) + namelen + 2; // 1 for slash and 1 for null terminator
515         if( buflen<len ) {
516             free( buffer );
517             buflen = len;
518             buffer = (char*)malloc( buflen );
519             if( !buffer )
520                 return error( mode, tbb::runtime_loader::ec_no_lib, "Not enough memory." );
521         }
522         cat_file( path[i], tbb_dll_name, buffer, buflen );
523         __TBB_ASSERT(strstr(buffer,tbb_dll_name), "Name concatenation error");
524         tbb::runtime_loader::error_code ec = _load( buffer, min_ver, max_ver );
525         if ( ec == tbb::runtime_loader::ec_ok ) {
526             return ec;       // Success. Exiting...
527         } // if
528     } // for i
529     free( buffer );
530     return error( mode, tbb::runtime_loader::ec_no_lib, "No suitable library found." );
531 } // load
532 
533 
534 
535 
536 // Suppress "defined but not used" compiler warnings.
537 static void const * dummy[] = {
538     (void *) & strip,
539     (void *) & trim,
540     & dummy,
541     NULL
542 };
543 
544 
545 } // namespace runtime_loader
546 
547 } // namespace internal
548 
549 
runtime_loader(error_mode mode)550 runtime_loader::runtime_loader( error_mode mode ) :
551     my_mode( mode ),
552     my_status( ec_ok ),
553     my_loaded( false )
554 {
555 } // ctor
556 
557 
runtime_loader(char const * path[],int min_ver,int max_ver,error_mode mode)558 runtime_loader::runtime_loader( char const * path[], int min_ver, int max_ver, error_mode mode ) :
559     my_mode( mode ),
560     my_status( ec_ok ),
561     my_loaded( false )
562 {
563     load( path, min_ver, max_ver );
564 } // ctor
565 
566 
~runtime_loader()567 runtime_loader::~runtime_loader() {
568 } // dtor
569 
570 
load(char const * path[],int min_ver,int max_ver)571 tbb::runtime_loader::error_code runtime_loader::load( char const * path[], int min_ver, int max_ver ) {
572     if ( my_loaded ) {
573         my_status = tbb::interface6::internal::runtime_loader::error( my_mode, ec_bad_call, "tbb::runtime_loader::load(): Library already loaded by this runtime_loader object." );
574     } else {
575         my_status = internal::runtime_loader::load( my_mode, path, min_ver, max_ver );
576         if ( my_status == ec_ok ) {
577             my_loaded = true;
578         } // if
579     } // if
580     return my_status;
581 } // load
582 
583 
584 
585 
status()586 tbb::runtime_loader::error_code runtime_loader::status() {
587     return my_status;
588 } // status
589 
590 
591 } // namespace interface6
592 
593 } // namespace tbb
594 
595 
596 // Stub function replaces all TBB entry points when no library is loaded.
__tbb_internal_runtime_loader_stub()597 int __tbb_internal_runtime_loader_stub() {
598     char const * msg = NULL;
599     if ( tbb::interface6::internal::runtime_loader::handle == NULL ) {
600         msg = "A function is called while TBB library is not loaded";
601     } else {
602         msg = "A function is called which is not present in loaded TBB library";
603     } // if
604     return tbb::interface6::internal::runtime_loader::error( tbb::interface6::internal::runtime_loader::stub_mode, tbb::runtime_loader::ec_no_lib, msg );
605 } // stub
606 
607 #endif // !__TBB_WIN8UI_SUPPORT //
608 // end of file //
609