From 79f6b161f892f621a909ce1df99b3c680f3682a2 Mon Sep 17 00:00:00 2001 From: Sunil Nimmagadda Date: Fri, 23 Oct 2026 22:43:44 +0530 Subject: Refactor: Move to an elf module. While here, define a elf.d.lua to help author type check configuration file. --- elf/constants.lua | 101 +++++++++++++++++ elf/fmt.lua | 17 +++ elf/header.lua | 58 ++++++++++ elf/init.lua | 20 ++++ elf/section.lua | 16 +++ elf/segment.lua | 65 +++++++++++ mkelf.lua | 321 +++++++++--------------------------------------------- sample.elf.lua | 47 ++++++++ types/elf.d.lua | 22 ++++ 9 files changed, 398 insertions(+), 269 deletions(-) create mode 100644 elf/constants.lua create mode 100644 elf/fmt.lua create mode 100644 elf/header.lua create mode 100644 elf/init.lua create mode 100644 elf/section.lua create mode 100644 elf/segment.lua create mode 100644 sample.elf.lua create mode 100644 types/elf.d.lua diff --git a/elf/constants.lua b/elf/constants.lua new file mode 100644 index 0000000..b06b275 --- /dev/null +++ b/elf/constants.lua @@ -0,0 +1,101 @@ +local M = {} + +M.VERSION = 1 + +---@enum Osabi +M.Osabi = { + sysv = 0x00, + hpux = 0x01, + netbsd = 0x02, + linux = 0x03, + hurd = 0x04, + solaris = 0x06, + aix = 0x07, + irix = 0x08, + freebsd = 0x09, + tru64 = 0x0a, + modesto = 0x0b, + openbsd = 0x0c, + openvms = 0x0d, + nsk = 0x0e, + aros = 0x0f, + fenixos = 0x10, + cloudabi = 0x11, + openvos = 0x12, +} + +---@enum Class +M.Class = { + bit32 = 0x1, + bit64 = 0x2, +} + +---@enum Endian +M.Endian = { + little = 0x1, + big = 0x2, +} + +---@enum Type +M.Type = { + none = 0x00, + rel = 0x01, + exec = 0x02, + dyn = 0x03, + core = 0x04, + loos = 0xfe00, + hios = 0xfeff, + loproc = 0xff00, + hiproc = 0xffff, +} + +---@enum Machine +M.Machine = { + none = 0x00, + m32 = 0x01, -- AT&T WE 32100 + sparc = 0x02, -- SPARC + i386 = 0x03, -- Intel 80386 + m68k = 0x04, -- Motorola 68000 (M68k) + m88k = 0x05, -- Motorola 88000 (M88k) + mcu = 0x06, -- Intel MCU + i860 = 0x07, -- Intel 80860 + mips = 0x08, -- MIPS + + s370 = 0x09, -- IBM System/370 + mipsrs3le = 0x0a, -- MIPS RS3000 Little Endian + parisc = 0x0f, -- Hewlett-Packard PA-RISC + vpp500 = 0x11, -- Fujitsu VPP500 + sparc32 = 0x12, -- Enhanced instruction set SPARC + i960 = 0x13, -- Intel 80960 + ppc = 0x14, -- PowerPC + ppc64 = 0x15, -- PowerPC (64-bit) + s390 = 0x16, -- IBM System/390 + spu = 0x17, -- IBM SPC/SPU + v800 = 0x24, -- NEC V800 + fr20 = 0x25, -- Fujitsu FR20 + -- todo +} + +---@enum SegmentType +M.SegmentType = { + null = 0x00000000, + load = 0x00000001, + dynamic = 0x00000002, + interp = 0x00000003, + note = 0x00000004, + shlib = 0x00000005, + phdr = 0x00000006, + tls = 0x00000007, + loos = 0x60000000, + hios = 0x6FFFFFFF, + loproc = 0x70000000, + hiproc = 0x7FFFFFFF, +} + +M.SegmentFlags = { + x = 0x1, + w = 0x2, + r = 0x4, +} + +return M diff --git a/elf/fmt.lua b/elf/fmt.lua new file mode 100644 index 0000000..44498d4 --- /dev/null +++ b/elf/fmt.lua @@ -0,0 +1,17 @@ +local constants = require("elf.constants") + +local M = {} + +---@param endian Endian +---@nodiscard +function M.endian(endian) + return (endian == constants.Endian.big) and ">" or "<" +end + +---@param class Class +---@nodiscard +function M.word(class) + return (class == constants.Class.bit64) and "I8" or "I4" +end + +return M diff --git a/elf/header.lua b/elf/header.lua new file mode 100644 index 0000000..2a9ec68 --- /dev/null +++ b/elf/header.lua @@ -0,0 +1,58 @@ +local fmt = require("elf.fmt") + +local M = {} + +---@alias Magic [integer, integer, integer, integer] + +---@class Header +---@field magic Magic +---@field class Class +---@field data Endian +---@field ident_version integer +---@field osabi Osabi +---@field abiversion integer +---@field type Type +---@field machine Machine +---@field version integer +---@field entry integer +---@field phoff integer +---@field shoff integer +---@field flags integer +---@field ehsize integer +---@field phentsize? integer +---@field phnum? integer +---@field shentsize integer +---@field shnum integer +---@field shstrndx integer + +---@param hdr Header +---@return string +---@nodiscard +function M.pack(hdr) + return string.pack("BBBBBBBBBxxxxxxx" .. + fmt.endian(hdr.data) .. + "I2I2I4" .. + string.rep(fmt.word(hdr.class), 3) .. + "I4I2I2I2I2I2I2", + hdr.magic[1], hdr.magic[2], hdr.magic[3], hdr.magic[4], + hdr.class, + hdr.data, + hdr.ident_version, + hdr.osabi, + hdr.abiversion, + hdr.type, + hdr.machine, + hdr.version, + hdr.entry, + hdr.phoff, + hdr.shoff, + hdr.flags, + hdr.ehsize, + hdr.phentsize, + hdr.phnum, + hdr.shentsize, + hdr.shnum, + hdr.shstrndx) +end + +return M diff --git a/elf/init.lua b/elf/init.lua new file mode 100644 index 0000000..6e6966a --- /dev/null +++ b/elf/init.lua @@ -0,0 +1,20 @@ +local constants = require("elf.constants") +local header = require("elf.header") +local segment = require("elf.segment") +local section = require("elf.section") + +local M = {} + +M.VERSION = constants.VERSION +M.Class = constants.Class +M.Endian = constants.Endian +M.Osabi = constants.Osabi +M.Type = constants.Type +M.Machine = constants.Machine +M.SegmentType = constants.SegmentType +M.SegmentFlags = constants.SegmentFlags +M.header = header +M.segment = segment +M.section = section + +return M diff --git a/elf/section.lua b/elf/section.lua new file mode 100644 index 0000000..64a91b2 --- /dev/null +++ b/elf/section.lua @@ -0,0 +1,16 @@ +local fmt = require("elf.fmt") + +local M = {} + +---@class Section + +---@param section Section +---@param endian Endian +---@return string +---@nodiscard +function M.pack(section, endian) + -- TODO + return string.pack("B", 0x1) +end + +return M diff --git a/elf/segment.lua b/elf/segment.lua new file mode 100644 index 0000000..da8e9c6 --- /dev/null +++ b/elf/segment.lua @@ -0,0 +1,65 @@ +local ec = require("elf.constants") +local fmt = require("elf.fmt") + +local M = {} + +---@class Segment +---@field type SegmentType +---@field flags integer +---@field offset integer +---@field vaddr integer +---@field paddr integer +---@field filesz integer +---@field memsz integer +---@field align integer + +---@param phdr Segment +---@param endian Endian +---@return string +---@nodiscard +local function pack32(phdr, endian) + return string.pack( + fmt.endian(endian) .. string.rep(fmt.word(ec.Class.bit32), 8), + phdr.type, + phdr.offset, + phdr.vaddr, + phdr.paddr, + phdr.filesz, + phdr.memsz, + phdr.flags, + phdr.align) +end + +---@param phdr Segment +---@param endian Endian +---@return string +---@nodiscard +local function pack64(phdr, endian) + return string.pack( + fmt.endian(endian) .. "I4I4" .. string.rep(fmt.word(ec.Class.bit64), 6), + phdr.type, + phdr.flags, + phdr.offset, + phdr.vaddr, + phdr.paddr, + phdr.filesz, + phdr.memsz, + phdr.align) +end + +---@param phdrs Segment[] +---@param endian Endian +---@param class Class +---@return string +---@nodiscard +function M.pack(phdrs, endian, class) + local p = {} + for i, phdr in ipairs(phdrs) do + p[i] = (class == ec.Class.bit32) and + pack32(phdr, endian) or + pack64(phdr, endian) + end + return table.concat(p) +end + +return M diff --git a/mkelf.lua b/mkelf.lua index 6a90504..3775956 100644 --- a/mkelf.lua +++ b/mkelf.lua @@ -1,282 +1,65 @@ -local ELFVERSION = 1 +local elf = require("elf") +local ec = require("elf.constants") ----@enum Osabi -local Osabi = { - sysv = 0x00, - hpux = 0x01, - netbsd = 0x02, - linux = 0x03, - hurd = 0x04, - solaris = 0x06, - aix = 0x07, - irix = 0x08, - freebsd = 0x09, - tru64 = 0x0a, - modesto = 0x0b, - openbsd = 0x0c, - openvms = 0x0d, - nsk = 0x0e, - aros = 0x0f, - fenixos = 0x10, - cloudabi = 0x11, - openvos = 0x12, -} - ----@enum Class -local Class = { - bit32 = 0x1, - bit64 = 0x2, -} - ----@enum Endian -local Endian = { - little = 0x1, - big = 0x2, -} - ----@enum Type -local Type = { - none = 0x00, - rel = 0x01, - exec = 0x02, - dyn = 0x03, - core = 0x04, - loos = 0xfe00, - hios = 0xfeff, - loproc = 0xff00, - hiproc = 0xffff, -} - ----@enum Machine -local Machine = { - none = 0x00, - m32 = 0x01, -- AT&T WE 32100 - sparc = 0x02, -- SPARC - i386 = 0x03, -- Intel 80386 - m68k = 0x04, -- Motorola 68000 (M68k) - m88k = 0x05, -- Motorola 88000 (M88k) - mcu = 0x06, -- Intel MCU - i860 = 0x07, -- Intel 80860 - mips = 0x08, -- MIPS - s370 = 0x09, -- IBM System/370 - mipsrs3le = 0x0a, -- MIPS RS3000 Little Endian - parisc = 0x0f, -- Hewlett-Packard PA-RISC - vpp500 = 0x11, -- Fujitsu VPP500 - sparc32 = 0x12, -- Enhanced instruction set SPARC - i960 = 0x13, -- Intel 80960 - ppc = 0x14, -- PowerPC - ppc64 = 0x15, -- PowerPC (64-bit) - s390 = 0x16, -- IBM System/390 - spu = 0x17, -- IBM SPC/SPU - v800 = 0x24, -- NEC V800 - fr20 = 0x25, -- Fujitsu FR20 - -- todo -} - ----@alias ElfMagic [integer, integer, integer, integer] ----@class ElfHeader ----@field magic ElfMagic ----@field class Class ----@field data Endian ----@field ident_version integer ----@field osabi Osabi ----@field abiversion integer ----@field type Type ----@field machine Machine ----@field version integer ----@field entry integer ----@field phoff integer ----@field shoff integer ----@field flags integer ----@field ehsize integer ----@field phentsize integer ----@field phnum integer ----@field shentsize integer ----@field shnum integer ----@field shstrndx integer - ----@param endian Endian ----@nodiscard -local function efmt(endian) - return (endian == Endian.big) and ">" or "<" -end - ----@param class Class ----@nodiscard -local function szfmt(class) - return (class == Class.bit64) and "I8" or "I4" +local function die(fmt, ...) + io.stderr:write("mkelf: ", string.format(fmt, ...), "\n") + os.exit(1, true) end ----@param hdr ElfHeader ----@nodiscard -local function ElfHeader_pack(hdr) - return string.pack("BBBBBBBBBxxxxxxx" .. - efmt(hdr.data) .. - "I2I2I4" .. - string.rep(szfmt(hdr.class), 3) .. - "I4I2I2I2I2I2I2", - hdr.magic[1], hdr.magic[2], hdr.magic[3], hdr.magic[4], - hdr.class, - hdr.data, - hdr.ident_version, - hdr.osabi, - hdr.abiversion, - hdr.type, - hdr.machine, - hdr.version, - hdr.entry, - hdr.phoff, - hdr.shoff, - hdr.flags, - hdr.ehsize, - hdr.phentsize, - hdr.phnum, - hdr.shentsize, - hdr.shnum, - hdr.shstrndx) +local function usage() + die("[-o output] input") end ----@class ProgramHeader ----@field type integer ----@field flags integer ----@field offset integer ----@field vaddr integer ----@field paddr integer ----@field filesz integer ----@field memsz integer ----@field align integer - ----@enum SegmentType -local SegmentType = { - null = 0x00000000, - load = 0x00000001, - dynamic = 0x00000002, - interp = 0x00000003, - note = 0x00000004, - shlib = 0x00000005, - phdr = 0x00000006, - tls = 0x00000007, - loos = 0x60000000, - hios = 0x6FFFFFFF, - loproc = 0x70000000, - hiproc = 0x7FFFFFFF, -} - ----@enum SegmentFlags -local SegmentFlags = { - x = 0x1, - w = 0x2, - r = 0x4, -} - ----@param phdr ProgramHeader ----@param endian Endian ----@return string ----@nodiscard -local function ElfPHeader_pack32(phdr, endian) - return string.pack(efmt(endian) .. string.rep(szfmt(Class.bit32), 8), - phdr.type, - phdr.offset, - phdr.vaddr, - phdr.paddr, - phdr.filesz, - phdr.memsz, - phdr.flags, - phdr.align) -end - ----@param phdr ProgramHeader ----@param endian Endian ----@return string ----@nodiscard -local function ElfPHeader_pack64(phdr, endian) - return string.pack( - efmt(endian) .. "I4I4" .. string.rep(szfmt(Class.bit64), 6), - phdr.type, - phdr.flags, - phdr.offset, - phdr.vaddr, - phdr.paddr, - phdr.filesz, - phdr.memsz, - phdr.align) -end - ----@param phdrs ProgramHeader[] ----@param endian Endian ----@param class Class ----@return string ----@nodiscard -local function ElfPHeader_pack(phdrs, endian, class) - local p = {} - for i, phdr in ipairs(phdrs) do - p[i] = (class == Class.bit32) and - ElfPHeader_pack32(phdr, endian) or - ElfPHeader_pack64(phdr, endian) - end - return table.concat(p) +---@param fn string|nil +---@return {header: Header, sections: Section[], segments: Segment[]} +local function loadconfig(fn) + local spec = {} + local env = { + -- Keep in sync with types/elf.d.lua + Class = elf.Class, + Endian = elf.Endian, + Osabi = elf.Osabi, + Type = elf.Type, + Machine = elf.Machine, + SegmentType = elf.SegmentType, + SegmentFlags = elf.SegmentFlags, + ELFVERSION = elf.VERSION, + + header = function(t) spec.header = t end, + sections = function(t) spec.sections = t end, + segments = function(t) spec.segments = t end, + } + -- TODO error check for multiple headers + local chunk, err = loadfile(fn, "t", env) + if not chunk then die("%s", err) end + local ok, perr = pcall(chunk) + if not ok then die("%s", perr) end + return spec end local function main() - ---@type ElfHeader - local header = { - magic = { 0x7f, 0x45, 0x4c, 0x46 }, - class = Class.bit64, - data = Endian.big, - ident_version = ELFVERSION, - osabi = Osabi.netbsd, - abiversion = 0, - type = Type.exec, - machine = Machine.i386, - version = ELFVERSION, - entry = 0, - phoff = 0x40, - shoff = 0xff, - flags = 0, - ehsize = 64, - phentsize = 0, -- filled in by pack later - phnum = 0, -- filled in by pack later - shentsize = 0x40, - shnum = 0, - shstrndx = 0, - } - ---@type ProgramHeader[] - local phdrs = { - { - type = SegmentType.dynamic, - flags = SegmentFlags.x, - offset = 0xf, - vaddr = 0xff, - paddr = 0xfff, - filesz = 0xffff, - memsz = 0xfffff, - align = 1, - }, - { - type = SegmentType.dynamic, - flags = SegmentFlags.r, - offset = 0xa, - vaddr = 0xaa, - paddr = 0xaaa, - filesz = 0xaaaa, - memsz = 0xaaaaa, - align = 1, - } - } - local output = "a.out" - for i in ipairs(arg) do - if arg[i] ~= "-o" then goto continue end - if not arg[i + 1] then - error("mkelf: missing filename after -o") + local outfn, infn = "a.out", nil + local i = 1 + while arg[i] do + if arg[i] == "-o" then + i = i + 1 + outfn = arg[i] or die("missing filename after -o") + elseif infn then + usage() + else + infn = arg[i] end - output = arg[i + 1] - ::continue:: + i = i + 1 end - local f = assert(io.open(output, "wb")) - header.phnum = #phdrs - header.phentsize = (header.class == Class.bit64) and 0x38 or 0x20 - f:write(ElfHeader_pack(header)) - f:write(ElfPHeader_pack(phdrs, header.data, header.class)) + local c = loadconfig(infn) + c.header.phnum = #c.segments + c.header.phentsize = (c.header.class == ec.Class.bit64) and 0x38 or 0x20 + -- TODO write to a temp file and atomic rename to outfn. + local out = assert(io.open(outfn, "wb")) + out:write(elf.header.pack(c.header)) + out:write(elf.segment.pack(c.segments, c.header.data, c.header.class)) + -- TODO finish section.pack + -- out:write(elf.section.pack(c.sections, c.header.data)) end main() diff --git a/sample.elf.lua b/sample.elf.lua new file mode 100644 index 0000000..f6b48d5 --- /dev/null +++ b/sample.elf.lua @@ -0,0 +1,47 @@ +header { + magic = { 0x7f, 0x45, 0x4c, 0x46 }, + class = Class.bit64, + data = Endian.big, + ident_version = ELFVERSION, + osabi = Osabi.netbsd, + abiversion = 0, + type = Type.exec, + machine = Machine.i386, + version = ELFVERSION, + entry = 0, + phoff = 0x40, + shoff = 0xff, + flags = 0, + ehsize = 64, + shentsize = 0x40, + shnum = 0, + shstrndx = 0, +} + +segments { + { + type = SegmentType.dynamic, + flags = SegmentFlags.x, + offset = 0xf, + vaddr = 0xff, + paddr = 0xfff, + filesz = 0xffff, + memsz = 0xfffff, + align = 1, + }, + { + type = SegmentType.dynamic, + flags = SegmentFlags.r, + offset = 0xa, + vaddr = 0xaa, + paddr = 0xaaa, + filesz = 0xaaaa, + memsz = 0xaaaaa, + align = 1, + }, +} + +sections { + {}, + {}, +} diff --git a/types/elf.d.lua b/types/elf.d.lua new file mode 100644 index 0000000..a2b774d --- /dev/null +++ b/types/elf.d.lua @@ -0,0 +1,22 @@ +---@meta + +local elf = require("elf") + +-- TODO keep in sync with mkelf:loadconfig() env. +Class = elf.Class +Endian = elf.Endian +Osabi = elf.Osabi +Type = elf.Type +Machine = elf.Machine +SegmentType = elf.SegmentType +SegmentFlags = elf.SegmentFlags +ELFVERSION = elf.VERSION + +---@param hdr Header +function header(hdr) end + +---@param xs Section[] +function sections(xs) end + +---@param xs Segment[] +function segments(xs) end -- cgit v1.3