9.3.6 Object File Format

To compile a file to disk, we need a format in which to write the compiled code to disk, and later load it into Guile. A good object file format has a number of characteristics:

These characteristics are not specific to Scheme. Indeed, mainstream languages like C and C++ have solved this issue many times in the past. Guile builds on their work by adopting ELF, the object file format of GNU and other Unix-like systems, as its object file format. Although Guile uses ELF on all platforms, we do not use platform support for ELF. Guile implements its own linker and loader. The advantage of using ELF is not sharing code, but sharing ideas. ELF is simply a well-designed object file format.

An ELF file has two meta-tables describing its contents. The first meta-table is for the loader, and is called the program table or sometimes the segment table. The program table divides the file into big chunks that should be treated differently by the loader. Mostly the difference between these segments is their permissions.

Typically all segments of an ELF file are marked as read-only, except that part that represents modifiable static data or static data that needs load-time initialization. Loading an ELF file is as simple as mmapping the thing into memory with read-only permissions, then using the segment table to mark a small sub-region of the file as writable. This writable section is typically added to the root set of the garbage collector as well.

One ELF segment is marked as “dynamic”, meaning that it has data of interest to the loader. Guile uses this segment to record the Guile version corresponding to this file. There is also an entry in the dynamic segment that points to the address of an initialization thunk that is run to perform any needed link-time initialization. (This is like dynamic relocations for normal ELF shared objects, except that we compile the relocations as a procedure instead of having the loader interpret a table of relocations.) Finally, the dynamic segment marks the location of the “entry thunk” of the object file. This thunk is returned to the caller of load-thunk-from-memory or load-thunk-from-file. When called, it will execute the “body” of the compiled expression.

The other meta-table in an ELF file is the section table. Whereas the program table divides an ELF file into big chunks for the loader, the section table specifies small sections for use by introspective tools like debuggers or the like. One segment (program table entry) typically contains many sections. There may be sections outside of any segment, as well.

Typical sections in a Guile .go file include:

.rtl-text

Bytecode.

.data

Data that needs initialization, or which may be modified at runtime.

.rodata

Statically allocated data that needs no run-time initialization, and which therefore can be shared between processes.

.dynamic

The dynamic section, discussed above.

.symtab
.strtab

A table mapping addresses in the .rtl-text to procedure names. .strtab is used by .symtab.

.guile.procprops
.guile.arities
.guile.arities.strtab
.guile.docstrs
.guile.docstrs.strtab

Side tables of procedure properties, arities, and docstrings.

.guile.docstrs.strtab

Side table of frame maps, describing the set of live slots for ever return point in the program text, and whether those slots are pointers are not. Used by the garbage collector.

.debug_info
.debug_abbrev
.debug_str
.debug_loc
.debug_line

Debugging information, in DWARF format. See the DWARF specification, for more information.

.shstrtab

Section name string table.

For more information, see the elf(5) man page. See the DWARF specification for more on the DWARF debugging format. Or if you are an adventurous explorer, try running readelf or objdump on compiled .go files. It’s good times!