1/* 2Copyright (c) 2002 Jorge Acereda <jacereda@users.sourceforge.net> 3 4 5Permission is hereby granted, free of charge, to any person obtaining 6a copy of this software and associated documentation files (the 7"Software"), to deal in the Software without restriction, including 8without limitation the rights to use, copy, modify, merge, publish, 9distribute, sublicense, and/or sell copies of the Software, and to 10permit persons to whom the Software is furnished to do so, subject to 11the following conditions: 12 13The above copyright notice and this permission notice shall be 14included in all copies or substantial portions of the Software. 15 16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23*/ 24 25static void * dlopen(const char *path, int mode); 26static void * dlsym(void * handle, const char *symbol); 27static const char * dlerror(void); 28static int dlclose(void * handle); 29 30#define RTLD_LAZY 0x1 31#define RTLD_NOW 0x2 32#define RTLD_LOCAL 0x4 33#define RTLD_GLOBAL 0x8 34#define RTLD_NOLOAD 0x10 35#define RTLD_NODELETE 0x80 36 37#include <stdio.h> 38#include <stdlib.h> 39#include <string.h> 40#include <sys/types.h> 41#include <sys/stat.h> 42#include <stdarg.h> 43#include <limits.h> 44#include <mach-o/dyld.h> 45 46/* Define this to make dlcompat reuse data block. This way in theory we save 47 * a little bit of overhead. However we then couldn't correctly catch excess 48 * calls to dlclose(). Hence we don't use this feature 49 */ 50#undef REUSE_STATUS 51 52/* Size of the internal error message buffer (used by dlerror()) */ 53#define ERR_STR_LEN 256 54 55/* Maximum number of search paths supported by getSearchPath */ 56#define MAX_SEARCH_PATHS 32 57 58 59#define MAGIC_DYLIB_OFI ((NSObjectFileImage) 'DYOF') 60#define MAGIC_DYLIB_MOD ((NSModule) 'DYMO') 61 62/* internal flags */ 63#define DL_IN_LIST 0x01 64 65/* This is our central data structure. Whenever a module is loaded via 66 * dlopen(), we create such a struct. 67 */ 68struct dlstatus 69{ 70 struct dlstatus * next; /* pointer to next element in the linked list */ 71 NSModule module; 72 const struct mach_header *lib; 73 int refs; /* reference count */ 74 int mode; /* mode in which this module was loaded */ 75 dev_t device; 76 ino_t inode; 77 int flags; /* Any internal flags we may need */ 78}; 79 80/* Head node of the dlstatus list */ 81static struct dlstatus mainStatus = 82 { 0, MAGIC_DYLIB_MOD,NULL, -1, RTLD_GLOBAL, 0, 0 }; 83static struct dlstatus * stqueue = &mainStatus; 84 85 86/* Storage for the last error message (used by dlerror()) */ 87static char err_str[ERR_STR_LEN]; 88static int err_filled = 0; 89 90 91/* Prototypes to internal functions */ 92static void debug(const char * fmt, ...); 93static void error(const char * str, ...); 94static const char * safegetenv(const char * s); 95static const char * searchList(void); 96static const char * getSearchPath(int i); 97static const char * getFullPath(int i, const char * file); 98static const struct stat * findFile(const char * file, const char ** fullPath); 99static int isValidStatus(struct dlstatus * status); 100static int isFlagSet(int mode, int flag); 101static struct dlstatus * lookupStatus(const struct stat * sbuf); 102static void insertStatus(struct dlstatus * dls, const struct stat * sbuf); 103static int promoteLocalToGlobal(struct dlstatus * dls); 104static void * reference (struct dlstatus * dls, int mode); 105static void * dlsymIntern(struct dlstatus * dls, const char *symbol, int canSetError); 106static struct dlstatus * allocStatus(void); 107static struct dlstatus * loadModule(const char * path, const struct stat * sbuf, int mode); 108 109/* Functions */ 110 111#ifdef MZ_PRECISE_GC 112START_XFORM_SKIP; 113#endif 114 115static void debug(const char * fmt, ...) 116{ 117#if DEBUG > 1 118 va_list arg; 119 va_start(arg, fmt); 120 fprintf(stderr, "DLDEBUG: "); 121 vfprintf(stderr, fmt, arg); 122 fprintf(stderr, "\n"); 123 fflush(stderr); 124 va_end(arg); 125#endif 126} 127 128static void error(const char * str, ...) 129{ 130 va_list arg; 131 va_start(arg, str); 132 strncpy(err_str, "dlcompat: ", ERR_STR_LEN); 133 vsnprintf(err_str+10, ERR_STR_LEN-10, str, arg); 134 va_end(arg); 135 debug("ERROR: %s\n", err_str); 136 err_filled = 1; 137} 138 139static void warning(const char * str) 140{ 141#if DEBUG > 0 142 fprintf(stderr, "WARNING: dlcompat: %s\n", str); 143#endif 144} 145 146static const char * safegetenv(const char * s) 147{ 148 const char * ss = getenv(s); 149 return ss? ss : ""; 150} 151 152/* Compute and return a list of all directories that we should search when 153 * trying to locate a module. We first look at the values of LD_LIBRARY_PATH 154 * and DYLD_LIBRARY_PATH, and then finally fall back to looking into 155 * /usr/lib and /lib. Since both of the environments variables can contain a 156 * list of colon separated paths, we simply concat them and the two other paths 157 * into one big string, which we then can easily parse. 158 * Splitting this string into the actual path list is done by getSearchPath() 159 */ 160static const char * searchList() 161{ 162 char * buf; 163 const char * ldlp = safegetenv("LD_LIBRARY_PATH"); 164 const char * dyldlp = safegetenv("DYLD_LIBRARY_PATH"); 165 size_t buf_size = strlen(ldlp) + strlen(dyldlp) + 20; 166 buf = malloc(buf_size); 167 snprintf(buf, buf_size, "%s:%s:/usr/lib:/lib", dyldlp, ldlp); 168 return buf; 169} 170 171/* Returns the ith search path from the list as computed by searchList() */ 172static const char * getSearchPath(int i) 173{ 174 static const char * list = 0; 175 static const char * path[MAX_SEARCH_PATHS] = {0}; 176 static int end = 0; 177 if (!list && !end) 178 list = searchList(); 179 while (!path[i] && !end) 180 { 181 path[i] = strsep((char**)&list, ":"); 182 if (path[i][0] == 0) 183 path[i] = 0; 184 end = list == 0; 185 } 186 return path[i]; 187} 188 189static const char * getFullPath(int i, const char * file) 190{ 191 static char buf[PATH_MAX]; 192 const char * path = getSearchPath(i); 193 if (path) 194 snprintf(buf, PATH_MAX, "%s/%s", path, file); 195 return path? buf : 0; 196} 197 198/* Given a file name, try to determine the full path for that file. Starts 199 * its search in the current directory, and then tries all paths in the 200 * search list in the order they are specified there. 201 */ 202static const struct stat * findFile(const char * file, const char ** fullPath) 203{ 204 int i = 0; 205 static struct stat sbuf; 206 debug("finding file %s",file); 207 *fullPath = file; 208 do 209 { 210 if (0 == stat(*fullPath, &sbuf)) 211 return &sbuf; 212 } 213 while ((*fullPath = getFullPath(i++, file))); 214 return 0; 215} 216 217/* Determine whether a given dlstatus is valid or not */ 218static int isValidStatus(struct dlstatus * status) 219{ 220 /* Walk the list to verify status is contained in it */ 221 struct dlstatus * dls = stqueue; 222 while (dls && status != dls) 223 dls = dls->next; 224 225 if (dls == 0) 226 error("invalid handle"); 227 else if (dls->module == 0) 228 error("handle to closed library"); 229 else 230 return TRUE; 231 return FALSE; 232} 233 234static int isFlagSet(int mode, int flag) 235{ 236 return (mode & flag) == flag; 237} 238 239static struct dlstatus * lookupStatus(const struct stat * sbuf) 240{ 241 struct dlstatus * dls = stqueue; 242 debug("looking for status"); 243 while (dls && ( /* isFlagSet(dls->mode, RTLD_UNSHARED) */ 0 244 || sbuf->st_dev != dls->device 245 || sbuf->st_ino != dls->inode) && dls->refs) 246 dls = dls->next; 247 return dls; 248} 249 250static void insertStatus(struct dlstatus * dls, const struct stat * sbuf) 251{ 252 debug("inserting status"); 253 dls->inode = sbuf->st_ino; 254 dls->device = sbuf->st_dev; 255 dls->refs = 0; 256 dls->mode = 0; 257 if ((dls->flags & DL_IN_LIST) == 0) { 258 dls->next = stqueue; 259 stqueue = dls; 260 dls->flags |= DL_IN_LIST; 261 } 262} 263 264static struct dlstatus * allocStatus() 265{ 266 struct dlstatus * dls; 267#ifdef REUSE_STATUS 268 dls = stqueue; 269 while (dls && dls->module) 270 dls = dls->next; 271 if (!dls) 272#endif 273 dls = malloc(sizeof(*dls)); 274 dls->flags = 0; 275 return dls; 276} 277 278static int promoteLocalToGlobal(struct dlstatus * dls) 279{ 280 static int (*p)(NSModule module) = 0; 281 debug("promoting"); 282 if(!p) 283 _dyld_func_lookup("__dyld_NSMakePrivateModulePublic", 284 (unsigned long *)&p); 285 return (dls->module == MAGIC_DYLIB_MOD) || (p && p(dls->module)); 286} 287 288static void * reference (struct dlstatus * dls, int mode) 289{ 290 if (dls) 291 { 292 if (dls->module == MAGIC_DYLIB_MOD && isFlagSet(mode, RTLD_LOCAL)) 293 { 294 warning("trying to open a .dylib with RTLD_LOCAL"); 295 error("unable to open a .dylib with RTLD_LOCAL"); 296 return NULL; 297 } 298 if (isFlagSet(mode, RTLD_GLOBAL) && 299 !isFlagSet(dls->mode, RTLD_GLOBAL) && 300 !promoteLocalToGlobal(dls)) 301 { 302 error("unable to promote local module to global"); 303 return NULL; 304 } 305 dls->mode |= mode; 306 dls->refs++; 307 } 308 else 309 debug("reference called with NULL argument"); 310 311 return dls; 312} 313 314static void * dlsymIntern(struct dlstatus * dls, const char *symbol,int canSetError) 315{ 316 NSSymbol * nssym = 0; 317 /* If it is a module - use NSLookupSymbolInModule */ 318 if (dls->module != MAGIC_DYLIB_MOD) { 319 320 nssym = NSLookupSymbolInModule(dls->module, symbol); 321 if (!nssym && canSetError) 322 error("unable to find symbol \"%s\"", symbol); 323 } 324 else { 325 if (dls->lib != NULL) { 326 /* dylib, use NSIsSymbolNameDefinedInImage */ 327 if (NSIsSymbolNameDefinedInImage(dls->lib,symbol)) { 328 nssym = NSLookupSymbolInImage(dls->lib, 329 symbol, 330 NSLOOKUPSYMBOLINIMAGE_OPTION_BIND 331 | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR 332 ); 333 } 334 } 335 else { 336 /* Global context, use NSLookupAndBindSymbol */ 337 nssym = NSLookupAndBindSymbol(symbol); 338 } 339 if (!nssym) { 340 NSLinkEditErrors ler; 341 int lerno; 342 const char* errstr; 343 const char* file; 344 NSLinkEditError(&ler,&lerno,&file,&errstr); 345 if (errstr && strlen(errstr)) { 346 if (canSetError) 347 error(errstr); 348 debug("%s",errstr); 349 } 350 351 } 352 } 353 if (!nssym) 354 return NULL; 355 return NSAddressOfSymbol(nssym); 356} 357 358static struct dlstatus * loadModule(const char * path, 359 const struct stat * sbuf, int mode) 360{ 361 NSObjectFileImage ofi = 0; 362 NSObjectFileImageReturnCode ofirc; 363 struct dlstatus * dls; 364 NSLinkEditErrors ler; 365 int lerno; 366 const char* errstr; 367 const char* file; 368 void (*init)(void); 369 370 ofirc = NSCreateObjectFileImageFromFile(path, &ofi); 371 switch (ofirc) 372 { 373 case NSObjectFileImageSuccess: 374 break; 375 case NSObjectFileImageInappropriateFile: 376 if (isFlagSet(mode, RTLD_LOCAL)) 377 { 378 warning("trying to open a .dylib with RTLD_LOCAL"); 379 error("unable to open this file with RTLD_LOCAL"); 380 return NULL; 381 } 382 break; 383 case NSObjectFileImageFailure: 384 error("object file setup failure"); 385 return NULL; 386 case NSObjectFileImageArch: 387 error("no object for this architecture"); 388 return NULL; 389 case NSObjectFileImageFormat: 390 error("bad object file format"); 391 return NULL; 392 case NSObjectFileImageAccess: 393 error("can't read object file"); 394 return NULL; 395 default: 396 error("unknown error from NSCreateObjectFileImageFromFile()"); 397 return NULL; 398 } 399 dls = lookupStatus(sbuf); 400 if (!dls) { 401 dls = allocStatus(); 402 } 403 if (!dls) 404 { 405 error("unable to allocate memory"); 406 return NULL; 407 } 408 dls->lib=0; 409 if (ofirc == NSObjectFileImageInappropriateFile) 410 { 411 if ((dls->lib = NSAddImage(path, NSADDIMAGE_OPTION_RETURN_ON_ERROR))) 412 { 413 debug("Dynamic lib loaded at %ld",dls->lib); 414 ofi = MAGIC_DYLIB_OFI; 415 dls->module = MAGIC_DYLIB_MOD; 416 ofirc = NSObjectFileImageSuccess; 417 } 418 } 419 else 420 { 421 /* Should change this to take care of RLTD_LAZY etc */ 422 dls->module = NSLinkModule(ofi, path, 423 NSLINKMODULE_OPTION_RETURN_ON_ERROR 424 | NSLINKMODULE_OPTION_PRIVATE 425 | NSLINKMODULE_OPTION_BINDNOW); 426 NSDestroyObjectFileImage(ofi); 427 } 428 if (!dls->module) 429 { 430 NSLinkEditError(&ler,&lerno,&file,&errstr); 431 free(dls); 432 error(errstr); 433 return NULL; 434 } 435 insertStatus(dls, sbuf); 436 437 if ((init = dlsymIntern(dls, "__init",0))) 438 { 439 debug("calling _init()"); 440 init(); 441 } 442 443 return dls; 444} 445 446static void * dlopen(const char * path, int mode) 447{ 448 const struct stat * sbuf; 449 struct dlstatus * dls; 450 const char * fullPath; 451 if (!path) 452 { 453 return &mainStatus; 454 } 455 if (!(sbuf = findFile(path, &fullPath))) 456 { 457 error("file \"%s\" not found", path); 458 return NULL; 459 } 460 /* Now checks that it hasn't been closed already */ 461 if ((dls = lookupStatus(sbuf)) && (dls->refs > 0)) 462 { 463 /* debug("status found"); */ 464 return reference(dls, mode); 465 } 466 if (isFlagSet(mode, RTLD_NOLOAD)) 467 { 468 error("no existing handle and RTLD_NOLOAD specified"); 469 return NULL; 470 } 471 return reference(loadModule(fullPath, sbuf, mode),mode); 472} 473 474static void * dlsym(void * handle, const char *symbol) 475{ 476 struct dlstatus * dls = handle; 477 void * addr = 0; 478 479 if (!isValidStatus(dls)) 480 return NULL; 481 addr = dlsymIntern(dls, symbol,1); 482 return addr; 483} 484 485static int dlclose(void * handle) 486{ 487 struct dlstatus * dls = handle; 488 if (!isValidStatus(dls)) 489 return 1; 490 if (dls->module == MAGIC_DYLIB_MOD) 491 { 492 warning("trying to close a .dylib!"); 493 error("dynamic libraries cannot be closed"); 494 return 1; 495 } 496 if (!dls->module) 497 { 498 error("module already closed"); 499 return 1; 500 } 501 dls->refs--; 502 if (!dls->refs) 503 { 504 unsigned long options = 0; 505 void (*fini)(void); 506 if ((fini = dlsymIntern(dls, "__fini",0))) 507 { 508 debug("calling _fini()"); 509 fini(); 510 } 511 if (isFlagSet(dls->mode, RTLD_NODELETE)) 512 options |= NSUNLINKMODULE_OPTION_KEEP_MEMORY_MAPPED; 513 if (!NSUnLinkModule(dls->module, options)) 514 { 515 error("unable to unlink module"); 516 return 1; 517 } 518 dls->module = 0; 519 /* Note: the dlstatus struct dls is neither removed from the list 520 * nor is the memory it occupies freed. This shouldn't pose a 521 * problem in mostly all cases, though. 522 */ 523 } 524 return 0; 525} 526 527static const char * dlerror(void) 528{ 529 const char * e = err_filled ? err_str : 0; 530 err_filled = 0; 531 return e; 532} 533 534#ifdef MZ_PRECISE_GC 535END_XFORM_SKIP; 536#endif 537