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