summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSunil Nimmagadda <sunil@nimmagadda.net>2026-10-23 22:43:44 +0530
committerSunil Nimmagadda <sunil@nimmagadda.net>2026-10-23 22:44:48 +0530
commit79f6b161f892f621a909ce1df99b3c680f3682a2 (patch)
tree7647bed7ccaa977f3cd28dcc8283ceef63265e57
parentd46e3e7761a133d58b6c77058473d9edc8a54744 (diff)
Refactor: Move to an elf module.
While here, define a elf.d.lua to help author type check configuration file.
-rw-r--r--elf/constants.lua101
-rw-r--r--elf/fmt.lua17
-rw-r--r--elf/header.lua58
-rw-r--r--elf/init.lua20
-rw-r--r--elf/section.lua16
-rw-r--r--elf/segment.lua65
-rw-r--r--mkelf.lua319
-rw-r--r--sample.elf.lua47
-rw-r--r--types/elf.d.lua22
9 files changed, 397 insertions, 268 deletions
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 <const> = 1
+local elf = require("elf")
+local ec = require("elf.constants")
----@enum Osabi
-local Osabi <const> = {
- 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 <const> = {
- bit32 = 0x1,
- bit64 = 0x2,
-}
-
----@enum Endian
-local Endian <const> = {
- little = 0x1,
- big = 0x2,
-}
-
----@enum Type
-local Type <const> = {
- none = 0x00,
- rel = 0x01,
- exec = 0x02,
- dyn = 0x03,
- core = 0x04,
- loos = 0xfe00,
- hios = 0xfeff,
- loproc = 0xff00,
- hiproc = 0xffff,
-}
-
----@enum Machine
-local Machine <const> = {
- 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"
-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 die(fmt, ...)
+ io.stderr:write("mkelf: ", string.format(fmt, ...), "\n")
+ os.exit(1, true)
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 <const> = {
- 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 <const> = {
- 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)
+local function usage()
+ die("[-o output] input")
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 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,
----@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)
+ 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 <const> = {
- 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 <const> = {
- {
- 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 <close> = 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 <close> = 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