3 * Common Binary Loader
\r
10 #define BIN_LOWEST MM_USER_MIN // 1MiB
\r
11 #define BIN_GRANUALITY 0x10000 // 64KiB
\r
12 //! \todo Move 0xBC000000 to mm_virt.h
\r
13 #define BIN_HIGHEST (0xBC000000-BIN_GRANUALITY) // Just below the kernel
\r
14 #define KLIB_LOWEST MM_MODULE_MIN
\r
15 #define KLIB_GRANUALITY 0x10000 // 32KiB
\r
16 #define KLIB_HIGHEST (MM_MODULE_MAX-KLIB_GRANUALITY)
\r
19 typedef struct sKernelBin {
\r
20 struct sKernelBin *Next;
\r
26 extern int Proc_Clone(Uint *Err, Uint Flags);
\r
27 extern char *Threads_GetName(int ID);
\r
28 extern void Threads_Exit(int, int);
\r
29 extern Uint MM_ClearUser();
\r
30 extern void Proc_StartUser(Uint Entrypoint, Uint *Bases, int ArgC, char **ArgV, char **EnvP, int DataSize);
\r
31 extern tKernelSymbol gKernelSymbols[];
\r
32 extern void gKernelSymbolsEnd;
\r
33 extern tBinaryType gELF_Info;
\r
35 // === PROTOTYPES ===
\r
36 int Proc_Execve(char *File, char **ArgV, char **EnvP);
\r
37 Uint Binary_Load(char *file, Uint *entryPoint);
\r
38 tBinary *Binary_GetInfo(char *truePath);
\r
39 Uint Binary_MapIn(tBinary *binary);
\r
40 Uint Binary_IsMapped(tBinary *binary);
\r
41 tBinary *Binary_DoLoad(char *truePath);
\r
42 void Binary_Dereference(tBinary *Info);
\r
43 Uint Binary_Relocate(void *Base);
\r
44 Uint Binary_GetSymbolEx(char *Name, Uint *Value);
\r
45 Uint Binary_FindSymbol(void *Base, char *Name, Uint *Val);
\r
48 int glBinListLock = 0;
\r
49 tBinary *glLoadedBinaries = NULL;
\r
50 char **gsaRegInterps = NULL;
\r
51 int giRegInterps = 0;
\r
52 int glKBinListLock = 0;
\r
53 tKernelBin *glLoadedKernelLibs;
\r
54 tBinaryType *gRegBinTypes = &gELF_Info;
\r
56 // === FUNCTIONS ===
\r
58 * \brief Registers a binary type
\r
60 int Binary_RegisterType(tBinaryType *Type)
\r
62 Type->Next = gRegBinTypes;
\r
63 gRegBinTypes = Type;
\r
68 * \fn int Proc_Spawn(char *Path)
\r
70 int Proc_Spawn(char *Path)
\r
72 char stackPath[strlen(Path)+1];
\r
74 strcpy(stackPath, Path);
\r
76 LOG("stackPath = '%s'\n", stackPath);
\r
78 if(Proc_Clone(NULL, CLONE_VM) == 0)
\r
81 char *args[2] = {stackPath, NULL};
\r
82 LOG("stackPath = '%s'\n", stackPath);
\r
83 Proc_Execve(stackPath, args, &args[1]);
\r
90 * \fn int Proc_Execve(char *File, char **ArgV, char **EnvP)
\r
91 * \brief Replace the current user image with another
\r
92 * \param File File to load as the next image
\r
93 * \param ArgV Arguments to pass to user
\r
94 * \param EnvP User's environment
\r
95 * \note Called Proc_ for historical reasons
\r
97 int Proc_Execve(char *File, char **ArgV, char **EnvP)
\r
101 char *argenvBuf, *strBuf;
\r
102 char **argvSaved, **envpSaved;
\r
105 Uint bases[2] = {0};
\r
107 ENTER("sFile pArgV pEnvP", File, ArgV, EnvP);
\r
109 // --- Save File, ArgV and EnvP (also get argc)
\r
111 // Count Arguments, Environment Variables and total string sizes
\r
113 for( argc = 0; ArgV && ArgV[argc]; argc++ )
\r
114 argenvBytes += strlen(ArgV[argc])+1;
\r
115 for( envc = 0; EnvP && EnvP[envc]; envc++ )
\r
116 argenvBytes += strlen(EnvP[envc])+1;
\r
117 argenvBytes = (argenvBytes + sizeof(void*)-1) & ~(sizeof(void*)-1);
\r
118 argenvBytes += (argc+1)*sizeof(void*) + (envc+1)*sizeof(void*);
\r
121 argenvBuf = malloc(argenvBytes);
\r
122 if(argenvBuf == NULL) {
\r
123 Warning("Proc_Execve - What the hell? The kernel is out of heap space");
\r
126 strBuf = argenvBuf + (argc+1)*sizeof(void*) + (envc+1)*sizeof(void*);
\r
129 argvSaved = (char **) argenvBuf;
\r
130 for( i = 0; i < argc; i++ )
\r
132 argvSaved[i] = strBuf;
\r
133 strcpy(argvSaved[i], ArgV[i]);
\r
134 strBuf += strlen(ArgV[i])+1;
\r
136 argvSaved[i] = NULL;
\r
137 envpSaved = &argvSaved[i+1];
\r
138 for( i = 0; i < envc; i++ )
\r
140 envpSaved[i] = strBuf;
\r
141 strcpy(envpSaved[i], EnvP[i]);
\r
142 strBuf += strlen(EnvP[i])+1;
\r
145 savedFile = malloc(strlen(File)+1);
\r
146 strcpy(savedFile, File);
\r
148 // --- Set Process Name
\r
149 Threads_SetName(File);
\r
151 // --- Clear User Address space
\r
154 // --- Load new binary
\r
155 bases[0] = Binary_Load(savedFile, &entry);
\r
159 Warning("Proc_Execve - Unable to load '%s'", Threads_GetName(-1));
\r
160 Threads_Exit(0, 0);
\r
164 LOG("entry = 0x%x, bases[0] = 0x%x", entry, bases[0]);
\r
166 // --- And... Jump to it
\r
167 Proc_StartUser(entry, bases, argc, argvSaved, envpSaved, argenvBytes);
\r
168 for(;;); // Tell GCC that we never return
\r
172 * \fn Uint Binary_Load(char *file, Uint *entryPoint)
\r
174 Uint Binary_Load(char *file, Uint *entryPoint)
\r
180 ENTER("sfile", file);
\r
182 // Sanity Check Argument
\r
188 // Get True File Path
\r
189 sTruePath = VFS_GetTruePath(file);
\r
191 if(sTruePath == NULL) {
\r
192 Warning("[BIN ] '%s' does not exist.", file);
\r
197 LOG("sTruePath = '%s'", sTruePath);
\r
199 // Check if the binary has already been loaded
\r
200 if( !(pBinary = Binary_GetInfo(sTruePath)) )
\r
201 pBinary = Binary_DoLoad(sTruePath); // Else load it
\r
207 if(pBinary == NULL) {
\r
213 if( (base = Binary_IsMapped(pBinary)) ) {
\r
219 // Map into process space
\r
220 base = Binary_MapIn(pBinary); // If so then map it in
\r
222 // Check for errors
\r
229 if(pBinary->Interpreter) {
\r
231 if( Binary_Load(pBinary->Interpreter, &start) == 0 ) {
\r
235 *entryPoint = start;
\r
238 *entryPoint = pBinary->Entry - pBinary->Base + base;
\r
241 LOG("*entryPoint = 0x%x", *entryPoint);
\r
243 return base; // Pass the base as an argument to the user if there is an interpreter
\r
247 * \brief Finds a matching binary entry
\r
248 * \param TruePath File Identifier (True path name)
\r
250 tBinary *Binary_GetInfo(char *TruePath)
\r
253 pBinary = glLoadedBinaries;
\r
256 if(strcmp(pBinary->TruePath, TruePath) == 0)
\r
258 pBinary = pBinary->Next;
\r
264 \fn Uint Binary_MapIn(tBinary *binary)
\r
265 \brief Maps an already-loaded binary into an address space.
\r
266 \param binary Pointer to globally stored data.
\r
268 Uint Binary_MapIn(tBinary *binary)
\r
274 // Reference Executable (Makes sure that it isn't unloaded)
\r
275 binary->ReferenceCount ++;
\r
278 base = binary->Base;
\r
280 // Check if base is free
\r
283 for(i=0;i<binary->NumPages;i++)
\r
285 if( MM_GetPhysAddr( binary->Pages[i].Virtual & ~0xFFF ) ) {
\r
287 LOG("Address 0x%x is taken\n", binary->Pages[i].Virtual & ~0xFFF);
\r
293 // Check if the executable has no base or it is not free
\r
296 // If so, give it a base
\r
297 base = BIN_HIGHEST;
\r
298 while(base >= BIN_LOWEST)
\r
300 for(i=0;i<binary->NumPages;i++)
\r
302 addr = binary->Pages[i].Virtual & ~0xFFF;
\r
303 addr -= binary->Base;
\r
305 if( MM_GetPhysAddr( addr ) ) break;
\r
307 // If space was found, break
\r
308 if(i == binary->NumPages) break;
\r
309 // Else decrement pointer and try again
\r
310 base -= BIN_GRANUALITY;
\r
315 if(base < BIN_LOWEST) {
\r
316 Warning("[BIN ] Executable '%s' cannot be loaded, no space", binary->TruePath);
\r
320 // Map Executable In
\r
321 for(i=0;i<binary->NumPages;i++)
\r
323 addr = binary->Pages[i].Virtual & ~0xFFF;
\r
324 addr -= binary->Base;
\r
326 LOG("%i - 0x%x to 0x%x", i, addr, binary->Pages[i].Physical);
\r
327 MM_Map( addr, (Uint) (binary->Pages[i].Physical) );
\r
330 if( binary->Pages[i].Flags & BIN_PAGEFLAG_RO)
\r
331 MM_SetFlags( addr, MM_PFLAG_RO, -1 );
\r
333 MM_SetFlags( addr, MM_PFLAG_COW, -1 );
\r
336 if( binary->Pages[i].Flags & BIN_PAGEFLAG_EXEC )
\r
337 MM_SetFlags( addr, MM_PFLAG_EXEC, -1 );
\r
339 MM_SetFlags( addr, MM_PFLAG_EXEC, 0 );
\r
343 //Log("Mapped '%s' to 0x%x", binary->TruePath, base);
\r
345 //LOG("*0x%x = 0x%x\n", binary->Pages[0].Virtual, *(Uint*)binary->Pages[0].Virtual);
\r
352 * \fn Uint Binary_IsMapped(tBinary *binary)
\r
353 * \brief Check if a binary is already mapped into the address space
\r
354 * \param binary Binary information to check
\r
355 * \return Current Base or 0
\r
357 Uint Binary_IsMapped(tBinary *binary)
\r
361 // Check prefered base
\r
362 iBase = binary->Base;
\r
363 if(MM_GetPage( iBase ) == (binary->Pages[0].Physical & ~0xFFF))
\r
366 for(iBase = BIN_HIGHEST;
\r
367 iBase >= BIN_LOWEST;
\r
368 iBase -= BIN_GRANUALITY)
\r
370 if(MM_GetPage( iBase ) == (binary->Pages[0].Physical & ~0xFFF))
\r
379 * \fn tBinary *Binary_DoLoad(char *truePath)
\r
380 * \brief Loads a binary file into memory
\r
381 * \param truePath Absolute filename of binary
\r
383 tBinary *Binary_DoLoad(char *truePath)
\r
388 tBinaryType *bt = gRegBinTypes;
\r
390 ENTER("struePath", truePath);
\r
393 fp = VFS_Open(truePath, VFS_OPENFLAG_READ);
\r
395 LOG("Unable to load file, access denied");
\r
401 VFS_Read(fp, 4, &ident);
\r
402 VFS_Seek(fp, 0, SEEK_SET);
\r
404 for(; bt; bt = bt->Next)
\r
406 if( (ident & bt->Mask) != (Uint)bt->Ident )
\r
408 pBinary = bt->Load(fp);
\r
412 Warning("[BIN ] '%s' is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
\r
413 truePath, ident&0xFF, (ident>>8)&0xFF, (ident>>16)&0xFF, (ident>>24)&0xFF);
\r
419 if(pBinary == NULL) {
\r
424 // Initialise Structure
\r
425 pBinary->ReferenceCount = 0;
\r
426 pBinary->TruePath = malloc( strlen(truePath) + 1 );
\r
427 strcpy(pBinary->TruePath, truePath);
\r
429 // Debug Information
\r
430 LOG("Interpreter: '%s'", pBinary->Interpreter);
\r
431 LOG("Base: 0x%x, Entry: 0x%x", pBinary->Base, pBinary->Entry);
\r
432 LOG("NumPages: %i", pBinary->NumPages);
\r
435 for(i=0;i<pBinary->NumPages;i++)
\r
439 paddr = (Uint)MM_AllocPhys();
\r
441 Warning("Binary_DoLoad - Physical memory allocation failed");
\r
443 MM_DerefPhys( pBinary->Pages[i].Physical );
\r
447 MM_RefPhys( paddr ); // Make sure it is _NOT_ freed until we want it to be
\r
448 dest = MM_MapTemp( paddr );
\r
449 dest += pBinary->Pages[i].Virtual & 0xFFF;
\r
450 LOG("dest = 0x%x, paddr = 0x%x", dest, paddr);
\r
451 LOG("Pages[%i]={Physical:0x%llx,Virtual:%p,Size:0x%x}",
\r
452 i, pBinary->Pages[i].Physical, pBinary->Pages[i].Virtual, pBinary->Pages[i].Size);
\r
455 if(pBinary->Pages[i].Physical == -1) {
\r
456 LOG("%i - ZERO", i);
\r
457 memsetd( (void*)dest, 0, 1024 - (pBinary->Pages[i].Virtual & 0xFFF)/4 );
\r
461 VFS_Seek( fp, pBinary->Pages[i].Physical, 1 );
\r
462 if(pBinary->Pages[i].Size != 0x1000) {
\r
463 LOG("%i - 0x%llx - 0x%x bytes",
\r
464 i, pBinary->Pages[i].Physical, pBinary->Pages[i].Size);
\r
465 memset( (void*)dest, 0, 0x1000 -(dest&0xFFF) );
\r
466 VFS_Read( fp, pBinary->Pages[i].Size, (void*)dest );
\r
468 LOG("%i - 0x%x", i, pBinary->Pages[i].Physical);
\r
469 VFS_Read( fp, 0x1000, (void*)dest );
\r
472 pBinary->Pages[i].Physical = paddr;
\r
473 MM_FreeTemp( dest );
\r
475 LOG("Page Count: %i", pBinary->NumPages);
\r
481 LOCK(&glBinListLock);
\r
482 pBinary->Next = glLoadedBinaries;
\r
483 glLoadedBinaries = pBinary;
\r
484 RELEASE(&glBinListLock);
\r
487 LEAVE('p', pBinary);
\r
492 * \fn void Binary_Unload(void *Base)
\r
493 * \brief Unload / Unmap a binary
\r
494 * \param Base Loaded Base
\r
495 * \note Currently used only for kernel libaries
\r
497 void Binary_Unload(void *Base)
\r
500 tKernelBin *prev = NULL;
\r
503 if((Uint)Base < 0xC0000000)
\r
505 // TODO: User Binaries
\r
506 Warning("[BIN ] Unloading user binaries is currently unimplemented");
\r
510 // Kernel Libraries
\r
511 for(pKBin = glLoadedKernelLibs;
\r
513 prev = pKBin, pKBin = pKBin->Next)
\r
516 if(pKBin->Base != Base) continue;
\r
517 // Deallocate Memory
\r
518 for(i = 0; i < pKBin->Info->NumPages; i++) {
\r
519 MM_Deallocate( (Uint)Base + (i << 12) );
\r
521 // Dereference Binary
\r
522 Binary_Dereference( pKBin->Info );
\r
523 // Remove from list
\r
524 if(prev) prev->Next = pKBin->Next;
\r
525 else glLoadedKernelLibs = pKBin->Next;
\r
533 * \fn void Binary_Dereference(tBinary *Info)
\r
534 * \brief Dereferences and if nessasary, deletes a binary
\r
535 * \param Info Binary information structure
\r
537 void Binary_Dereference(tBinary *Info)
\r
539 // Decrement reference count
\r
540 Info->ReferenceCount --;
\r
542 // Check if it is still in use
\r
543 if(Info->ReferenceCount) return;
\r
545 /// \todo Implement binary freeing
\r
549 * \fn char *Binary_RegInterp(char *Path)
\r
550 * \brief Registers an Interpreter
\r
551 * \param Path Path to interpreter provided by executable
\r
553 char *Binary_RegInterp(char *Path)
\r
556 // NULL Check Argument
\r
557 if(Path == NULL) return NULL;
\r
558 // NULL Check the array
\r
559 if(gsaRegInterps == NULL)
\r
562 gsaRegInterps = malloc( sizeof(char*) );
\r
563 gsaRegInterps[0] = malloc( strlen(Path) );
\r
564 strcpy(gsaRegInterps[0], Path);
\r
565 return gsaRegInterps[0];
\r
569 for( i = 0; i < giRegInterps; i++ )
\r
571 if(strcmp(gsaRegInterps[i], Path) == 0)
\r
572 return gsaRegInterps[i];
\r
575 // Interpreter is not in list
\r
577 gsaRegInterps = malloc( sizeof(char*)*giRegInterps );
\r
578 gsaRegInterps[i] = malloc( strlen(Path) );
\r
579 strcpy(gsaRegInterps[i], Path);
\r
580 return gsaRegInterps[i];
\r
584 // Kernel Binary Handling
\r
587 * \fn void *Binary_LoadKernel(char *File)
\r
588 * \brief Load a binary into kernel space
\r
589 * \note This function shares much with #Binary_Load, but does it's own mapping
\r
590 * \param File File to load into the kernel
\r
592 void *Binary_LoadKernel(char *File)
\r
596 tKernelBin *pKBinary;
\r
601 ENTER("sfile", File);
\r
603 // Sanity Check Argument
\r
609 // Get True File Path
\r
610 sTruePath = VFS_GetTruePath(File);
\r
611 if(sTruePath == NULL) {
\r
616 // Check if the binary has already been loaded
\r
617 if( (pBinary = Binary_GetInfo(sTruePath)) )
\r
619 for(pKBinary = glLoadedKernelLibs;
\r
621 pKBinary = pKBinary->Next )
\r
623 if(pKBinary->Info == pBinary) {
\r
624 LEAVE('p', pKBinary->Base);
\r
625 return pKBinary->Base;
\r
630 pBinary = Binary_DoLoad(sTruePath); // Else load it
\r
633 if(pBinary == NULL) {
\r
639 // Now pBinary is valid (either freshly loaded or only user mapped)
\r
640 // So, map it into kernel space
\r
643 // Reference Executable (Makes sure that it isn't unloaded)
\r
644 pBinary->ReferenceCount ++;
\r
646 // Check compiled base
\r
647 base = pBinary->Base;
\r
649 if(base < KLIB_LOWEST || base > KLIB_HIGHEST || base + (pBinary->NumPages<<12) > KLIB_HIGHEST) {
\r
652 // - Check if it is a valid base address
\r
655 for(i=0;i<pBinary->NumPages;i++)
\r
657 if( MM_GetPhysAddr( pBinary->Pages[i].Virtual & ~0xFFF ) ) {
\r
659 LOG("Address 0x%x is taken\n", pBinary->Pages[i].Virtual & ~0xFFF);
\r
665 // Check if the executable has no base or it is not free
\r
668 // If so, give it a base
\r
669 base = KLIB_LOWEST;
\r
670 while(base < KLIB_HIGHEST)
\r
672 for(i = 0; i < pBinary->NumPages; i++)
\r
674 addr = pBinary->Pages[i].Virtual & ~0xFFF;
\r
675 addr -= pBinary->Base;
\r
677 if( MM_GetPhysAddr( addr ) ) break;
\r
679 // If space was found, break
\r
680 if(i == pBinary->NumPages) break;
\r
681 // Else decrement pointer and try again
\r
682 base += KLIB_GRANUALITY;
\r
687 if(base >= KLIB_HIGHEST) {
\r
688 Warning("[BIN ] Executable '%s' cannot be loaded into kernel, no space", pBinary->TruePath);
\r
689 Binary_Dereference( pBinary );
\r
694 LOG("base = 0x%x", base);
\r
697 LOG("pBinary = {NumPages:%i, Pages=%p}", pBinary->NumPages, pBinary->Pages);
\r
698 for(i = 0; i < pBinary->NumPages; i++)
\r
700 addr = pBinary->Pages[i].Virtual & ~0xFFF;
\r
701 addr -= pBinary->Base;
\r
703 LOG("%i - 0x%x to 0x%x", i, addr, pBinary->Pages[i].Physical);
\r
704 MM_Map( addr, (Uint) (pBinary->Pages[i].Physical) );
\r
705 MM_SetFlags( addr, MM_PFLAG_KERNEL, MM_PFLAG_KERNEL );
\r
707 if( pBinary->Pages[i].Flags & BIN_PAGEFLAG_RO) // Read-Only?
\r
708 MM_SetFlags( addr, MM_PFLAG_RO, MM_PFLAG_KERNEL );
\r
711 // Relocate Library
\r
712 if( !Binary_Relocate( (void*)base ) )
\r
714 Warning("[BIN ] Relocation of '%s' failed, unloading", sTruePath);
\r
715 Binary_Unload( (void*)base );
\r
716 Binary_Dereference( pBinary );
\r
721 // Add to list (relocator must look at itself manually, not via Binary_GetSymbol)
\r
722 pKBinary = malloc(sizeof(*pKBinary));
\r
723 pKBinary->Base = (void*)base;
\r
724 pKBinary->Info = pBinary;
\r
725 LOCK( &glKBinListLock );
\r
726 pKBinary->Next = glLoadedKernelLibs;
\r
727 glLoadedKernelLibs = pKBinary;
\r
728 RELEASE( &glKBinListLock );
\r
731 return (void*)base;
\r
735 * \fn Uint Binary_Relocate(void *Base)
\r
736 * \brief Relocates a loaded binary (used by kernel libraries)
\r
737 * \param Base Loaded base address of binary
\r
738 * \return Boolean Success
\r
740 Uint Binary_Relocate(void *Base)
\r
742 Uint32 ident = *(Uint32*) Base;
\r
743 tBinaryType *bt = gRegBinTypes;
\r
745 for(; bt; bt = bt->Next)
\r
747 if( (ident & bt->Mask) == (Uint)bt->Ident )
\r
748 return bt->Relocate( (void*)Base);
\r
751 Warning("[BIN ] 0x%x is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
\r
752 Base, ident&0xFF, (ident>>8)&0xFF, (ident>>16)&0xFF, (ident>>24)&0xFF);
\r
757 * \fn int Binary_GetSymbol(char *Name, Uint *Val)
\r
758 * \brief Get a symbol value
\r
759 * \return Value of symbol or -1 on error
\r
761 * Gets the value of a symbol from either the currently loaded
\r
762 * libraries or the kernel's exports.
\r
764 int Binary_GetSymbol(char *Name, Uint *Val)
\r
766 if( Binary_GetSymbolEx(Name, Val) ) return 1;
\r
771 * \fn Uint Binary_GetSymbolEx(char *Name, Uint *Value)
\r
772 * \brief Get a symbol value
\r
774 * Gets the value of a symbol from either the currently loaded
\r
775 * libraries or the kernel's exports.
\r
777 Uint Binary_GetSymbolEx(char *Name, Uint *Value)
\r
781 int numKSyms = ((Uint)&gKernelSymbolsEnd-(Uint)&gKernelSymbols)/sizeof(tKernelSymbol);
\r
784 for( i = 0; i < numKSyms; i++ )
\r
786 if(strcmp(Name, gKernelSymbols[i].Name) == 0) {
\r
787 *Value = gKernelSymbols[i].Value;
\r
792 // Scan Loaded Libraries
\r
793 for(pKBin = glLoadedKernelLibs;
\r
795 pKBin = pKBin->Next )
\r
797 if( Binary_FindSymbol(pKBin->Base, Name, Value) ) {
\r
802 Warning("[BIN ] Unable to find symbol '%s'", Name);
\r
807 * \fn Uint Binary_FindSymbol(void *Base, char *Name, Uint *Val)
\r
808 * \brief Get a symbol from the specified library
\r
809 * \param Base Base address
\r
810 * \param Name Name of symbol to find
\r
811 * \param Val Pointer to place final value
\r
813 Uint Binary_FindSymbol(void *Base, char *Name, Uint *Val)
\r
815 Uint32 ident = *(Uint32*) Base;
\r
816 tBinaryType *bt = gRegBinTypes;
\r
818 for(; bt; bt = bt->Next)
\r
820 if( (ident & bt->Mask) == (Uint)bt->Ident )
\r
821 return bt->GetSymbol(Base, Name, Val);
\r
824 Warning("[BIN ] 0x%x is an unknown file type. (0x%x 0x%x 0x%x 0x%x)",
\r
825 Base, ident&0xFF, ident>>8, ident>>16, ident>>24);
\r
830 EXPORT(Binary_FindSymbol);
\r
831 EXPORT(Binary_Unload);
\r