README.txt

CheckExecutableArchitecture
 
This sample deals with the problem of determining in advance whether a particular Mach-O executable contains a version suitable for executing on a given processor architecture.  
 
For example, an application running on an Intel-based Macintosh may wish to examine a set of potential plugins, because it suspects that some of them may be PowerPC-only and thus not suitable for loading.  Note that this sort of preflighting is not absolutely necessary; one might simply attempt to load a plugin and let the operation fail if the executable is not suitable.  However, for user interface reasons, preflighting may often be desirable.
 
Future versions of Mac OS X may contain APIs that will make this sort of determination much simpler, and thus supersede the techniques presented in this sample.  However, the code here may still be instructive and relevant for those who wish to understand the structure of Mach-O executables.
 
There are actually two different sorts of files that will be accepted and used as executables with Mach-O content:  (a) those that are individual Mach-O files, as described in <http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/index.html>, and (b) those that contain a collection of Mach-O files for different processor architectures, often known as universal binaries, as described in <http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/FileStructure/chapter_2.1_section_7.html>.  Both types contain processor architecture specifications--only one, in the first case, or many, in the second case.
 
For those who are working at the command line, /usr/bin/file can be used to immediately identify these different sorts of files, and determine briefly which architectures they support.  For more detailed information, or to manipulate the contents of a universal binary, use /usr/bin/lipo.
 
The problem examined in this sample is that of programmatically determining whether a given file of either type matches a specified architecture.  Architectures are specified by numeric constants that specify not only the processor type, but optionally also a more specific processor subtype.  For a detailed list of the possible specifications, see "man 3 arch", or <http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/arch.3.html>.
 
The most important consequence for this sample is that one should not iterate over the list of architectures in a file, seeking an exact match.  Instead, the correct procedure is to create an architecture specification, and then apply a standard API, NXFindBestFatArch(), to try to find a match for that specification from the given list of architectures.  NXFindBestFatArch() will return a pointer to the best match in the list, or NULL if no match is found.  Here we are interested only in whether a match exists, so we detect only whether the result is non-NULL.
 
The creation of an architecture specification is done using NXGetLocalArchInfo() to obtain a specification for the current architecture (whatever it may be), or using NXGetArchInfoFromName() to obtain a specification for some other architecture, using a name such as "ppc", "i386", and so on.  In the case mentioned above, in which a process is looking for plugins suitable for loading, one would properly use NXGetLocalArchInfo() to avoid hard-coding any specific alternatives.  More information about all of these functions and names can be found on the "arch" manpage referred to above.
 
The sample is presented here in the form of a command-line tool using CoreFoundation.  It should be compiled with "cc -o check_executable_architecture -framework CoreFoundation check_executable_architecture.c".  The result will be a tool that takes any number of file or directory names as arguments.  Files are directly checked to determine whether they are of any Mach-O types, and if so they are checked to see whether they match the current architecture, whether they match "ppc", and whether they match "i386".  Directories are assumed to be bundles, and for them CFBundle is used to locate their executables, which are then checked as files.
 
There are a number of salient points here.  Files are checked by reading in their first 512 bytes.  It is important to check the type of the file with stat() before open()ing it for reading, and to open only regular files, since some types of special files can cause the process to hang immediately on open() or read().  
 
The type of the file is determined by examining the first four bytes for one of six different magic numbers.  The magic number determines whether the file is Mach-O, 64-bit Mach-O, or a universal binary, and whether its endianness matches the current architecture or differs from it.  If the endianness does not match, then the bytes read must be swapped in four-byte blocks (the relevant portions of the headers consist entirely of four-byte quantities on four-byte boundaries).
 
If the file is a universal binary, then the bytes read will contain a list of fat_arch structures.  If not, then a single fat_arch structure is created based on the architecture specification in the Mach-O header.  NXFindBestFatArch() is used, as described above, to determine whether the file matches the current architecture, whether it matches "ppc", and whether it matches "i386". 
 
It is possible that some executables of interest might not be Mach-O at all, but of some other type, notably PEF/CFM.  This sample does not directly deal with such files, merely noting them as not being Mach-O.  For Mac OS X, there is only one architecture for which PEF/CFM executables are relevant.  Also, PEF/CFM executables cannot reliably be identified from the first few bytes of their content, so in general the appropriate assumption (for applications that deal with CFM) is that non-Mach-O executables might be PEF/CFM.