Add pure Jerboa ELF section parser
ober
9b3de840e8dd0f0f7193a13d01c3166490428944
--- 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. new file mode 100644 --- /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)))) --- 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)