The OSKit provides a small library that can recognize and load program executables in a variety of formats. It is analogous to the GNU Binary File Descriptor (BFD) library, except that it only supports loading linked program executables rather than general reading and writing of all types of object files. For this reason, it is much smaller and simpler than BFD.
Furthermore, as with the other OSKit components, the executable interpreter library is designed to be as generic and environment-independent as possible, so that it can readily be used in any situation in which it is useful. For example, the library does not directly do any memory allocation; it operates purely using memory provided to it explicitly. Furthermore, it does not make any assumptions about how a program’s code and data are to be written into the proper target address space; instead it uses generic callback functions for this purpose. All of the library functions are pure, not containing or relying on any global shared state.
All of the executable loading functions take pointers to two callback functions as parameters; the library calls these functions, which the client OS must provide, to load data from the executable and map or copy it into the address space of the program being loaded. Since all loading is done through these callback functions, the OSKit’s executable interpreter code can be used to load executables either into the same address space as the program currently running (e.g., loading a kernel from a boot loader) or into a different address space. The prototypes and semantics of these callback functions are defined below, in Section 35.2.
This section describes the header files provided with the OSKit’s executable interpreter library. For normal use of the library, only exec.h is needed; however, the other headers are provided as a convenience for clients that need to do their own executable interpretation.
This header file contains all the function prototypes for the executable loading functions described below, and all other symbol definitions required to use the executable program interpreter functions. Ths exec.h header file also defines the following error codes, returned from the executable loading functions:
This header file defines a set of structures and symbols describing a.out-format object and executable files. Since the a.out format is extremely nonstandard and varies widely even across different operating systems for the same processor architecture, this header file only provides a minimal, “least-common-denominator” set of definitions that applies to all the a.out variants we know of. Therefore, actually interpreting a.out files requires considerably more information than is provided in this header file; for more information, see the source code for the OSKit’s a.out interpreter, in exec/x86/aout.c.
An a.out file contains a simple fixed-size header, represented by the following structure:
unsigned | long a_magic; | /* Magic number | */ | |
unsigned | long a_text; | /* Size of text segment | */ | |
unsigned | long a_data; | /* Size of initialized data | */ | |
unsigned | long a_bss; | /* Size of uninitialized data | */ | |
unsigned | long a_syms; | /* Size of symbol table | */ | |
unsigned | long a_entry; | /* Entry point | */ | |
unsigned | long a_trsize; | /* Size of text relocation | */ | |
unsigned | long a_drsize; | /* Size of data relocation | */ | |
The a_magic field typically contain one of the following traditional magic numbers:
In addition, this header defines the nlist structure which describes the format of a.out symbol table entries:
long | n_strx; | /* Offset of symbol name in the string table | */ | |
unsigned | char n_type; | /* Symbol/relocation type | */ | |
char | n_other; | /* Miscellaneous info | */ | |
short | n_desc; | /* Miscellaneous info | */ | |
unsigned | long n_value; | /* Symbol value | */ | |
This header file contains a number of structure and symbol definitions describing the data structures used in ELF files. Since these names and their meanings are fairly well standardized, they are not described here; instead, see the ELF specification for details.
This section describes the types and other symbols which the client OS must interact with in order to use the executable interpreter library. These symbols are defined in oskit/exec/exec.h.
#include <oskit/exec/exec.h>
typedef int exec_read_func_t(void *handle, oskit_addr_t file_ofs, void *buf, oskit_size_t size, [out] oskit_size_t *actual);
This type describes the function prototype of the read callback function which the client OS must supply to the executable interpreter; it is used by the executable interpreter library to read “metadata” from the executable file such as the executable file’s header (as opposed to the actual executable data itself). It is basically analogous in purpose and semantics to the standard POSIX read function.
Returns 0 on success, or an error code on failure. The error code may be either one of the EX_ error codes defined in exec.h, or it may be a caller-defined error code, which the executable interpreter code will simply pass directly back through to the original caller.
#include <oskit/exec/exec.h>
typedef int exec_read_exec_func_t(void *handle, oskit_addr_t file_ofs, oskit_size_t file_size, oskit_addr_t mem_addr, oskit_size_t mem_size, exec_sectype_t section_type);
This type describes the function prototype of the read_exec callback function which the client OS must supply to the executable interpreter; it is used by the executable interpreter library to read actual executable code and data from the executable file to be copied or mapped into the loaded program’s image. It is also used to indicate to the client where debugging information can be found in the executable, and what format it is in. The executable interpreter generally calls this function once for each “section” it finds in the executable file, indicating where in the executable file to load or map from and where in the resulting program image to copy or map to. The actual executable data itself never actually “passes through” the generic executable interpreter itself; instead, the interpreter merely “directs” the loading process, giving the client OS ultimate flexibility in the way the loading is performed. In fact, the client’s callback function does not even necessarily need to “load” the executable: for example, if the client merely wants to determine the memory layout described by the executable file, it can provide a callback that does not actually load anything but instead just records the information passed by the executable interpreter.
Note that not all sections in an executable file are necessarily relevant to the loaded program image itself: for example, the executable interpreter also calls this callback when it encounters debug sections that the client may be interested in. Therefore, to avoid choking on such sections, the client’s implementation of this callback function should always check the section_type parameter and ignore sections for which EXEC_SECTYPE_ALLOC is not set and it doesn’t otherwise know how to deal with.
Returns 0 on success, or an error code on failure. The error code may be either one of the EX_ error codes defined in exec.h, or it may be a caller-defined error code, which the executable interpreter code will simply pass directly back through to the original caller.
#include <oskit/exec/exec.h>
typedef int exec_sectype_t;
The following flag definitions describe the section_type value that the executable program interpreter library passes back to the client in the read_exec callback:
#include <oskit/exec/exec.h>
exec_format_t | format; | /* Executable file format | */ | |
oskit_addr_t | entry; | /* Entrypoint address | */ | |
oskit_addr_t | init_dp; | /* Initial data pointer | */ | |
Each of the executable interpreter functions described below fills in a caller-provided structure of this type after successfully loading an executable. This structure contains miscellaneous information about the executable: in particular, information needed to actually start the program running.
This section describes the actual functions exported to the client OS by the executable interpreter library, liboskit_exec.a.
#include <oskit/exec/exec.h>
int exec_load(exec_read_func_t *read, exec_read_exec_func_t *read_exec, void *handle, [out] exec_info_t *info);
This is the primary entrypoint to the executable interpreter: it automatically detects the type of an executable file and loads it using the specified callback functions. This function simply calls, in succession, each of the format-specific executable loader functions that apply to the architecture for which the OSKit was compiled, until one succeeds or returns an error other than EX_NOT_EXECUTABLE.
Returns 0 on success, or an error code on failure. The error code may be either one of the EX_ error codes defined in exec.h, or it may be a caller-defined error code returned by one of the callback functions and passed through to the client.
#include <oskit/exec/exec.h>
int exec_load_elf(exec_read_func_t *read, exec_read_exec_func_t *read_exec, void *handle, [out] exec_info_t *info);
This function recognizes, interprets, and loads executables in the ELF (Executable and Linkable File) format.
Returns 0 on success, or an error code on failure. The error code may be either one of the EX_ error codes defined in exec.h, or it may be a caller-defined error code returned by one of the callback functions and passed through to the client.
#include <oskit/exec/exec.h>
int exec_load_aout(exec_read_func_t *read, exec_read_exec_func_t *read_exec, void *handle, [out] exec_info_t *info);
This function recognizes, interprets, and loads executables in the traditional Unix a.out file format. Unfortunately, there are many variants of the a.out format, even on a single processor architecture, each with similar but incompatible interpretations. Furthermore, there is no completely reliable and unambiguous way to differentiate between many of these formats: they often use the same magic numbers even though they have very different meanings. However, by using some hairy but fairly reliable heuristics, tthe OSKit’s a.out loader for the x86 architecture simultaneously supports Linux, NetBSD, FreeBSD, and Mach executables, in the OMAGIC, NMAGIC, QMAGIC, and several ZMAGIC variants. Thus, at least for executables linked on one of these systems, the OSKit’s loader should “just work.” Nevertheless, the a.out format is very outdated at best, and we strongly recommend anyone using the OSKit to use a more modern and powerful executable format such as ELF.
Returns 0 on success, or an error code on failure. The error code may be either one of the EX_ error codes defined in exec.h, or it may be a caller-defined error code returned by one of the callback functions and passed through to the client.