kernel/elf.h
About this file
C structures that mirror the start of an ELF file, the format of every xv6 user
program (user/_cat, user/_sh, …). kexec in kernel/exec.c reads these
structures straight from the file with readi and uses them to decide what to load
where.
An executable ELF file starts with a fixed 64-byte file header (elfhdr). The header
says where in the file to find the program header table, an array of proghdr
entries, one per segment. Each segment is a range of bytes in the file
together with the virtual address it should occupy in memory and its permissions. The
rest of the file (section headers, symbol tables, debug information) is for tools such as
the debugger and is ignored by the kernel.
The structures follow the 64-bit ELF layout exactly, field for field, so that the bytes on
disk can be read directly into them. You can see the same fields, decoded, by running
${TOOLPREFIX}readelf -h -l user/_cat (TOOLPREFIX is your RISC-V toolchain’s prefix; see Tour 1: From make qemu to a disk image and a kernel).
Only a few fields are used by xv6: magic, entry, phoff and phnum in the file
header, and type, flags, off, vaddr, filesz and memsz in a program header. The
rest are present to keep the layout right.
Read next: kernel/exec.c.
The magic number
Every ELF file begins with the four bytes 0x7f, 'E', 'L', 'F'. Read as one
32-bit number on a little-endian machine such as RISC-V, the first byte is the least
significant, which gives 0x464C457F (0x46 = 'F', 0x4C = 'L', 0x45 =
'E'). kexec compares the file’s first word with this value, its only check that
the file is an executable. The U suffix makes the constant unsigned, matching the
uint it is compared with.
The bytes 7f 45 4c 46 (“\x7fELF”) read as a little-endian 32-bit number.
The file header
The first 64 bytes of the file. The names drop the e_ prefix the ELF specification
uses (e_entry, e_phoff…). magic and elf[12] together are the 16-byte
identification block, e_ident: after the magic come the class (2 = 64-bit), the
byte order (1 = little-endian), the ELF version (1), the OS/ABI and ABI version (both
0 here), then padding.
The fields kexec uses:
magic: checked againstELF_MAGIC.entry: the virtual address of the first instruction to run, the program’s entry point. Foruser/_catit is0xf6, the address ofstart.kexeccopies it into the saved program counter in the trapframe.phoff: the byte offset in the file of the program header table.phnum: how many program headers there are.
The others are not used: type (2 = an executable), machine (243 = RISC-V),
version, shoff/shentsize/shnum/shstrndx (the section header table, for
linkers and debuggers), flags (processor-specific; for RISC-V it records things such
as the floating-point ABI), ehsize (this header’s size, 64) and phentsize (one
program header’s size, 56). kexec assumes the last value instead of reading it: it
steps through the table by sizeof(struct proghdr).
The field order and types make the C layout match the file exactly: entry falls at
offset 24, already a multiple of 8, so the compiler inserts no padding, and the total is
64 bytes.
The first 4 bytes of the file; kexec rejects the file if they differ from
ELF_MAGIC.
The program’s entry point: where the new program starts executing.
File offset of the program header table.
Number of entries in the program header table.
A program header
One entry of the program header table, 56 bytes, describing one segment
(program header (segment)). The ELF specification’s names carry a p_ prefix (p_type, p_vaddr…).
The source comment’s “Program section header” is loose wording: this is a program
(segment) header; section headers are a different table that xv6 ignores.
type: what kind of segment. OnlyELF_PROG_LOADsegments are loaded.flags: permissions, a combination of theELF_PROG_FLAG_bits below; turned into page-table bits byflags2perm.off: where the segment’s bytes start in the file.vaddr: the virtual address where they go in memory. Must be page-aligned for xv6.paddr: a physical address, meaningful only on systems without virtual memory; unused.filesz: how many bytes to copy from the file.memsz: how much memory the segment occupies. If larger thanfilesz, the rest is zero-filled; that is how a program’s .bss gets its zeros without being stored in the file.align: the alignment the linker used (0x1000for xv6’s loadable segments, all except_forktest’s, which is linked withoutuser.ldand has a single read-write-execute segment); unused.
Segment type; only ELF_PROG_LOAD (1) is loaded.
Permission bits (execute 1, write 2, read 4).
File offset of the segment’s bytes.
Virtual address of the segment’s first byte in the new process.
Bytes to copy from the file.
Bytes of memory to reserve; the part beyond filesz is zero.
Segment types
1 is PT_LOAD in the ELF specification: a segment to be loaded into memory. Other
types exist (xv6’s programs also contain RISCV_ATTRIBUTES and GNU_STACK
headers), and kexec skips them.
Segment permission bits
These are PF_X, PF_W and PF_R in the ELF specification: execute, write, read.
A code segment typically has R and E (5); a data segment R and W (6).
flags2perm tests the first two using the literal values 0x1 and 0x2 rather than
these names. The read bit is never tested: every loaded user page is readable.