Add pure Jerboa ELF section parser

ober

9b3de840e8dd0f0f7193a13d01c3166490428944

diff --git a/docs/plan.md b/docs/plan.md
index ca80442..1fe2ca6 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -39,6 +39,9 @@ reference material and parity targets.
   - `mips`, `mipsel`, `mips64`, `mips64el`: seed fixed-width raw-byte decoding
     for common integer/load/store/branch/jump instructions.
 - Tests cover the initial decoder slice.
+- `(jasm object elf)` can parse ELF32/ELF64 headers and section tables from a
+  bytevector or file, map supported machines to decoder architectures, discover
+  executable sections, and return section bytes.
 
 ## Core Architecture
 
@@ -189,16 +192,18 @@ Implement ELF first because it is regular and common.
 
 Required:
 
-- ELF32 and ELF64 headers.
-- Little-endian first, then big-endian.
-- Section header table.
+- ELF32 and ELF64 headers. Initial parser is implemented.
+- Little-endian first, then big-endian. Initial parser handles both endian tags
+  and integer decoding.
+- Section header table. Initial parser is implemented.
 - Program header table.
 - String tables.
+- Section-name string table. Initial parser is implemented.
 - Symbol tables: `.symtab`, `.dynsym`.
 - Relocations:
   - `SHT_REL`
   - `SHT_RELA`
-- Executable section discovery.
+- Executable section discovery. Initial parser is implemented.
 - Section filtering.
 - Address range filtering.
 
diff --git a/lib/jasm/object/elf.ss b/lib/jasm/object/elf.ss
new file mode 100644
index 0000000..e7fb184
--- /dev/null
+++ b/lib/jasm/object/elf.ss
@@ -0,0 +1,305 @@
+#!chezscheme
+
+(library (jasm object elf)
+  (export
+    read-elf-bytevector
+    read-elf-file
+    elf-object?
+    elf-object-class
+    elf-object-endian
+    elf-object-machine
+    elf-object-machine-name
+    elf-object-architecture
+    elf-object-entry
+    elf-object-sections
+    elf-section?
+    elf-section-index
+    elf-section-name
+    elf-section-type
+    elf-section-flags
+    elf-section-address
+    elf-section-offset
+    elf-section-size
+    elf-section-align
+    elf-section-entry-size
+    elf-section-link
+    elf-section-info
+    elf-section-executable?
+    elf-section-by-name
+    elf-executable-sections
+    elf-section-bytes)
+
+  (import (chezscheme))
+
+  (define (make-elf-object class endian machine entry sections path bytes)
+    (vector 'elf-object class endian machine entry sections path bytes))
+
+  (define (elf-object? x)
+    (and (vector? x)
+         (= (vector-length x) 8)
+         (eq? (vector-ref x 0) 'elf-object)))
+
+  (define (elf-object-class obj) (vector-ref obj 1))
+  (define (elf-object-endian obj) (vector-ref obj 2))
+  (define (elf-object-machine obj) (vector-ref obj 3))
+  (define (elf-object-entry obj) (vector-ref obj 4))
+  (define (elf-object-sections obj) (vector-ref obj 5))
+  (define (elf-object-path obj) (vector-ref obj 6))
+  (define (elf-object-bytes obj) (vector-ref obj 7))
+
+  (define (make-elf-section index name type flags address offset size align entry-size link info)
+    (vector 'elf-section index name type flags address offset size align entry-size link info))
+
+  (define (elf-section? x)
+    (and (vector? x)
+         (= (vector-length x) 12)
+         (eq? (vector-ref x 0) 'elf-section)))
+
+  (define (elf-section-index section) (vector-ref section 1))
+  (define (elf-section-name section) (vector-ref section 2))
+  (define (elf-section-type section) (vector-ref section 3))
+  (define (elf-section-flags section) (vector-ref section 4))
+  (define (elf-section-address section) (vector-ref section 5))
+  (define (elf-section-offset section) (vector-ref section 6))
+  (define (elf-section-size section) (vector-ref section 7))
+  (define (elf-section-align section) (vector-ref section 8))
+  (define (elf-section-entry-size section) (vector-ref section 9))
+  (define (elf-section-link section) (vector-ref section 10))
+  (define (elf-section-info section) (vector-ref section 11))
+
+  (define (bv-len bv) (bytevector-length bv))
+
+  (define (require-range who bv offset size)
+    (when (or (< offset 0)
+              (< size 0)
+              (> (+ offset size) (bv-len bv)))
+      (error who "ELF data is truncated" offset size (bv-len bv))))
+
+  (define (u8 bv offset)
+    (require-range 'read-elf-bytevector bv offset 1)
+    (bytevector-u8-ref bv offset))
+
+  (define (u16 bv offset endian)
+    (require-range 'read-elf-bytevector bv offset 2)
+    (let ([b0 (u8 bv offset)]
+          [b1 (u8 bv (+ offset 1))])
+      (if (eq? endian 'little)
+          (+ b0 (* b1 #x100))
+          (+ (* b0 #x100) b1))))
+
+  (define (u32 bv offset endian)
+    (require-range 'read-elf-bytevector bv offset 4)
+    (let ([b0 (u8 bv offset)]
+          [b1 (u8 bv (+ offset 1))]
+          [b2 (u8 bv (+ offset 2))]
+          [b3 (u8 bv (+ offset 3))])
+      (if (eq? endian 'little)
+          (+ b0
+             (* b1 #x100)
+             (* b2 #x10000)
+             (* b3 #x1000000))
+          (+ (* b0 #x1000000)
+             (* b1 #x10000)
+             (* b2 #x100)
+             b3))))
+
+  (define (u64 bv offset endian)
+    (require-range 'read-elf-bytevector bv offset 8)
+    (if (eq? endian 'little)
+        (+ (u32 bv offset endian)
+           (* (u32 bv (+ offset 4) endian) #x100000000))
+        (+ (* (u32 bv offset endian) #x100000000)
+           (u32 bv (+ offset 4) endian))))
+
+  (define (slice-bytevector bv offset size)
+    (require-range 'elf-section-bytes bv offset size)
+    (let ([out (make-bytevector size 0)])
+      (bytevector-copy! bv offset out 0 size)
+      out))
+
+  (define (read-file-bytevector path)
+    (let ([port (open-file-input-port path)])
+      (let ([bytes (get-bytevector-all port)])
+        (close-port port)
+        bytes)))
+
+  (define (elf-class-name n)
+    (case n
+      [(1) 'elf32]
+      [(2) 'elf64]
+      [else (error 'read-elf-bytevector "unsupported ELF class" n)]))
+
+  (define (elf-endian-name n)
+    (case n
+      [(1) 'little]
+      [(2) 'big]
+      [else (error 'read-elf-bytevector "unsupported ELF endianness" n)]))
+
+  (define (elf-object-machine-name obj)
+    (case (elf-object-machine obj)
+      [(3) 'i386]
+      [(8) 'mips]
+      [(20) 'powerpc]
+      [(21) 'powerpc64]
+      [(40) 'arm]
+      [(62) 'x86-64]
+      [(183) 'aarch64]
+      [(243) 'risc-v]
+      [else 'unknown]))
+
+  (define (elf-object-architecture obj)
+    (let ([machine (elf-object-machine obj)])
+      (case machine
+        [(62) 'x86-64]
+        [(183) 'aarch64]
+        [(8)
+         (case (elf-object-class obj)
+           [(elf64) (if (eq? (elf-object-endian obj) 'little) 'mips64el 'mips64)]
+           [else (if (eq? (elf-object-endian obj) 'little) 'mipsel 'mips)])]
+        [else (error 'elf-object-architecture "unsupported ELF machine" machine)])))
+
+  (define (read-c-string bv offset limit)
+    (require-range 'read-elf-bytevector bv offset 0)
+    (let loop ([i offset] [chars '()])
+      (cond
+        [(or (>= i limit) (>= i (bv-len bv)))
+         (list->string (reverse chars))]
+        [(zero? (u8 bv i))
+         (list->string (reverse chars))]
+        [else
+         (loop (+ i 1) (cons (integer->char (u8 bv i)) chars))])))
+
+  (define (raw-section-header class endian bv offset)
+    (if (eq? class 'elf64)
+        (list (u32 bv offset endian)
+              (u32 bv (+ offset 4) endian)
+              (u64 bv (+ offset 8) endian)
+              (u64 bv (+ offset 16) endian)
+              (u64 bv (+ offset 24) endian)
+              (u64 bv (+ offset 32) endian)
+              (u32 bv (+ offset 40) endian)
+              (u32 bv (+ offset 44) endian)
+              (u64 bv (+ offset 48) endian)
+              (u64 bv (+ offset 56) endian))
+        (list (u32 bv offset endian)
+              (u32 bv (+ offset 4) endian)
+              (u32 bv (+ offset 8) endian)
+              (u32 bv (+ offset 12) endian)
+              (u32 bv (+ offset 16) endian)
+              (u32 bv (+ offset 20) endian)
+              (u32 bv (+ offset 24) endian)
+              (u32 bv (+ offset 28) endian)
+              (u32 bv (+ offset 32) endian)
+              (u32 bv (+ offset 36) endian))))
+
+  (define (raw-section-name-offset raw) (list-ref raw 0))
+  (define (raw-section-type raw) (list-ref raw 1))
+  (define (raw-section-flags raw) (list-ref raw 2))
+  (define (raw-section-address raw) (list-ref raw 3))
+  (define (raw-section-offset raw) (list-ref raw 4))
+  (define (raw-section-size raw) (list-ref raw 5))
+  (define (raw-section-link raw) (list-ref raw 6))
+  (define (raw-section-info raw) (list-ref raw 7))
+  (define (raw-section-align raw) (list-ref raw 8))
+  (define (raw-section-entry-size raw) (list-ref raw 9))
+
+  (define (read-raw-section-headers class endian bv shoff shentsize shnum)
+    (let ([minimum (if (eq? class 'elf64) 64 40)])
+      (when (< shentsize minimum)
+        (error 'read-elf-bytevector "ELF section entry size is too small" shentsize)))
+    (let loop ([index 0] [headers '()])
+      (if (= index shnum)
+          (reverse headers)
+          (let ([offset (+ shoff (* index shentsize))])
+            (require-range 'read-elf-bytevector bv offset shentsize)
+            (loop (+ index 1)
+                  (cons (raw-section-header class endian bv offset) headers))))))
+
+  (define (list-ref/default xs index default)
+    (if (and (>= index 0) (< index (length xs)))
+        (list-ref xs index)
+        default))
+
+  (define (name-section-bounds raw-section)
+    (if raw-section
+        (let ([offset (raw-section-offset raw-section)]
+              [size (raw-section-size raw-section)])
+          (cons offset (+ offset size)))
+        (cons 0 0)))
+
+  (define (name-for-section bv shstr-bounds raw)
+    (let ([name-offset (raw-section-name-offset raw)])
+      (if (= name-offset 0)
+          ""
+          (read-c-string bv
+                         (+ (car shstr-bounds) name-offset)
+                         (cdr shstr-bounds)))))
+
+  (define (materialize-sections bv raw-sections shstrndx)
+    (let* ([shstr (list-ref/default raw-sections shstrndx #f)]
+           [bounds (name-section-bounds shstr)])
+      (let loop ([index 0] [raws raw-sections] [sections '()])
+        (if (null? raws)
+            (reverse sections)
+            (let* ([raw (car raws)]
+                   [section (make-elf-section
+                              index
+                              (name-for-section bv bounds raw)
+                              (raw-section-type raw)
+                              (raw-section-flags raw)
+                              (raw-section-address raw)
+                              (raw-section-offset raw)
+                              (raw-section-size raw)
+                              (raw-section-align raw)
+                              (raw-section-entry-size raw)
+                              (raw-section-link raw)
+                              (raw-section-info raw))])
+              (loop (+ index 1) (cdr raws) (cons section sections)))))))
+
+  (define (read-elf-bytevector bv . maybe-path)
+    (let ([path (if (null? maybe-path) #f (car maybe-path))])
+      (require-range 'read-elf-bytevector bv 0 64)
+      (unless (and (= (u8 bv 0) #x7f)
+                   (= (u8 bv 1) (char->integer #\E))
+                   (= (u8 bv 2) (char->integer #\L))
+                   (= (u8 bv 3) (char->integer #\F)))
+        (error 'read-elf-bytevector "not an ELF file"))
+      (let* ([class (elf-class-name (u8 bv 4))]
+             [endian (elf-endian-name (u8 bv 5))]
+             [machine (u16 bv 18 endian)]
+             [entry (if (eq? class 'elf64) (u64 bv 24 endian) (u32 bv 24 endian))]
+             [shoff (if (eq? class 'elf64) (u64 bv 40 endian) (u32 bv 32 endian))]
+             [shentsize (if (eq? class 'elf64) (u16 bv 58 endian) (u16 bv 46 endian))]
+             [shnum (if (eq? class 'elf64) (u16 bv 60 endian) (u16 bv 48 endian))]
+             [shstrndx (if (eq? class 'elf64) (u16 bv 62 endian) (u16 bv 50 endian))]
+             [raw-sections (if (zero? shnum)
+                               '()
+                               (read-raw-section-headers class endian bv shoff shentsize shnum))]
+             [sections (materialize-sections bv raw-sections shstrndx)])
+        (make-elf-object class endian machine entry sections path bv))))
+
+  (define (read-elf-file path)
+    (read-elf-bytevector (read-file-bytevector path) path))
+
+  (define (elf-section-executable? section)
+    (not (zero? (bitwise-and (elf-section-flags section) #x4))))
+
+  (define (elf-section-by-name obj name)
+    (let loop ([sections (elf-object-sections obj)])
+      (cond
+        [(null? sections) #f]
+        [(string=? (elf-section-name (car sections)) name) (car sections)]
+        [else (loop (cdr sections))])))
+
+  (define (elf-executable-sections obj)
+    (let loop ([sections (elf-object-sections obj)] [out '()])
+      (cond
+        [(null? sections) (reverse out)]
+        [(elf-section-executable? (car sections))
+         (loop (cdr sections) (cons (car sections) out))]
+        [else (loop (cdr sections) out)])))
+
+  (define (elf-section-bytes obj section)
+    (slice-bytevector (elf-object-bytes obj)
+                      (elf-section-offset section)
+                      (elf-section-size section))))
diff --git a/tests/test-jasm.ss b/tests/test-jasm.ss
index 82f4ba8..9bf84da 100644
--- a/tests/test-jasm.ss
+++ b/tests/test-jasm.ss
@@ -1,6 +1,6 @@
 #!chezscheme
 
-(import (chezscheme) (jasm disasm) (jasm format))
+(import (chezscheme) (jasm disasm) (jasm format) (jasm object elf))
 
 (define pass 0)
 (define fail 0)
@@ -26,6 +26,63 @@
          [insns (disassemble-bytevector arch (hex-string->bytevector hex) options)])
     (instruction-text (car insns))))
 
+(define (sample-elf64le)
+  (let ([bv (make-bytevector #x200 0)])
+    (define (put8! offset value)
+      (bytevector-u8-set! bv offset (modulo value #x100)))
+    (define (put16le! offset value)
+      (put8! offset value)
+      (put8! (+ offset 1) (quotient value #x100)))
+    (define (put32le! offset value)
+      (put8! offset value)
+      (put8! (+ offset 1) (quotient value #x100))
+      (put8! (+ offset 2) (quotient value #x10000))
+      (put8! (+ offset 3) (quotient value #x1000000)))
+    (define (put64le! offset value)
+      (put32le! offset (modulo value #x100000000))
+      (put32le! (+ offset 4) (quotient value #x100000000)))
+    (define (put-ascii! offset s)
+      (let ([n (string-length s)])
+        (let loop ([i 0])
+          (when (< i n)
+            (put8! (+ offset i) (char->integer (string-ref s i)))
+            (loop (+ i 1))))))
+    (define (put-shdr64! offset name type flags address file-offset size link info align entry-size)
+      (put32le! offset name)
+      (put32le! (+ offset 4) type)
+      (put64le! (+ offset 8) flags)
+      (put64le! (+ offset 16) address)
+      (put64le! (+ offset 24) file-offset)
+      (put64le! (+ offset 32) size)
+      (put32le! (+ offset 40) link)
+      (put32le! (+ offset 44) info)
+      (put64le! (+ offset 48) align)
+      (put64le! (+ offset 56) entry-size))
+    (put8! 0 #x7f)
+    (put-ascii! 1 "ELF")
+    (put8! 4 2)
+    (put8! 5 1)
+    (put8! 6 1)
+    (put16le! 16 1)
+    (put16le! 18 62)
+    (put32le! 20 1)
+    (put64le! 24 0)
+    (put64le! 32 0)
+    (put64le! 40 #xc0)
+    (put32le! 48 0)
+    (put16le! 52 64)
+    (put16le! 54 0)
+    (put16le! 56 0)
+    (put16le! 58 64)
+    (put16le! 60 3)
+    (put16le! 62 2)
+    (put-ascii! #x80 (string #\x55 #\xc3 #\x90 #\x90 #\xc3))
+    (put-ascii! #x90 (string #\nul #\. #\t #\e #\x #\t #\nul #\. #\s #\h #\s #\t #\r #\t #\a #\b #\nul))
+    (put-shdr64! #xc0 0 0 0 0 0 0 0 0 0 0)
+    (put-shdr64! #x100 1 1 #x6 #x1000 #x80 5 0 0 16 0)
+    (put-shdr64! #x140 7 3 0 0 #x90 17 0 0 1 0)
+    bv))
+
 (printf "--- jasm tests ---~%")
 
 (test "hex parser"
@@ -45,6 +102,32 @@
         (architecture-endianness 'mips64el))
   '(big little))
 
+(test "elf header"
+  (let ([obj (read-elf-bytevector (sample-elf64le) "sample.o")])
+    (list (elf-object? obj)
+          (elf-object-class obj)
+          (elf-object-endian obj)
+          (elf-object-machine-name obj)
+          (elf-object-architecture obj)))
+  '(#t elf64 little x86-64 x86-64))
+
+(test "elf section discovery"
+  (let* ([obj (read-elf-bytevector (sample-elf64le))]
+         [text (elf-section-by-name obj ".text")])
+    (list (length (elf-object-sections obj))
+          (elf-section? text)
+          (elf-section-index text)
+          (elf-section-address text)
+          (elf-section-executable? text)
+          (map elf-section-name (elf-executable-sections obj))))
+  '(3 #t 1 4096 #t (".text")))
+
+(test "elf section bytes"
+  (let* ([obj (read-elf-bytevector (sample-elf64le))]
+         [text (elf-section-by-name obj ".text")])
+    (bytevector->u8-list (elf-section-bytes obj text)))
+  '(85 195 144 144 195))
+
 (test "instruction records are vector-backed"
   (let ([insn (car (disassemble-bytevector 'x86-64 (hex-string->bytevector "55")))])
     (list (instruction? insn)