Add ELF symbol table support

ober

0cbce05f57d8f7a6a489dc4472afde88e200d275

diff --git a/Makefile b/Makefile
index 7186400..2a28126 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,7 @@ test-binary: binary
 	$(SCHEME) --libdirs $(LIBDIRS) --script tests/write-sample-elf.ss ./.jasm-sample.o
 	./$(BINARY) headers ./.jasm-sample.o
 	./$(BINARY) sections ./.jasm-sample.o
+	./$(BINARY) symbols ./.jasm-sample.o
 	./$(BINARY) object --section .text --syntax intel ./.jasm-sample.o
 	rm -f ./.jasm-sample.o
 
diff --git a/docs/plan.md b/docs/plan.md
index 5ffcf60..5aed984 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -41,10 +41,11 @@ reference material and parity targets.
 - 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.
+  executable sections, return section bytes, and parse `.symtab`/`.dynsym`
+  symbol entries.
 - The `jasm` binary has initial ELF-oriented `object`, `headers`, and
-  `sections` commands. `object` disassembles executable sections or a selected
-  section by name.
+  `sections`, and `symbols` commands. `object` disassembles executable sections
+  or a selected section by name.
 
 ## Core Architecture
 
@@ -149,7 +150,7 @@ Initial commands:
 - `object` (implemented for initial ELF executable-section disassembly)
 - `headers` (implemented for initial ELF metadata)
 - `sections` (implemented for initial ELF section tables)
-- `symbols`
+- `symbols` (implemented for initial ELF symbol tables)
 - `relocs`
 
 Common options:
@@ -202,7 +203,7 @@ Required:
 - Program header table.
 - String tables.
 - Section-name string table. Initial parser is implemented.
-- Symbol tables: `.symtab`, `.dynsym`.
+- Symbol tables: `.symtab`, `.dynsym`. Initial parser is implemented.
 - Relocations:
   - `SHT_REL`
   - `SHT_RELA`
diff --git a/jasm b/jasm
index 4263eaa..d332be5 100755
Binary files a/jasm and b/jasm differ
diff --git a/lib/jasm/object/elf.ss b/lib/jasm/object/elf.ss
index e7fb184..c7382d1 100644
--- a/lib/jasm/object/elf.ss
+++ b/lib/jasm/object/elf.ss
@@ -12,6 +12,7 @@
     elf-object-architecture
     elf-object-entry
     elf-object-sections
+    elf-object-symbols
     elf-section?
     elf-section-index
     elf-section-name
@@ -27,16 +28,26 @@
     elf-section-executable?
     elf-section-by-name
     elf-executable-sections
-    elf-section-bytes)
+    elf-section-bytes
+    elf-symbol?
+    elf-symbol-index
+    elf-symbol-name
+    elf-symbol-value
+    elf-symbol-size
+    elf-symbol-binding
+    elf-symbol-type
+    elf-symbol-visibility
+    elf-symbol-section-index
+    elf-symbol-table-section)
 
   (import (chezscheme))
 
-  (define (make-elf-object class endian machine entry sections path bytes)
-    (vector 'elf-object class endian machine entry sections path bytes))
+  (define (make-elf-object class endian machine entry sections symbols path bytes)
+    (vector 'elf-object class endian machine entry sections symbols path bytes))
 
   (define (elf-object? x)
     (and (vector? x)
-         (= (vector-length x) 8)
+         (= (vector-length x) 9)
          (eq? (vector-ref x 0) 'elf-object)))
 
   (define (elf-object-class obj) (vector-ref obj 1))
@@ -44,8 +55,9 @@
   (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 (elf-object-symbols obj) (vector-ref obj 6))
+  (define (elf-object-path obj) (vector-ref obj 7))
+  (define (elf-object-bytes obj) (vector-ref obj 8))
 
   (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))
@@ -67,6 +79,24 @@
   (define (elf-section-link section) (vector-ref section 10))
   (define (elf-section-info section) (vector-ref section 11))
 
+  (define (make-elf-symbol index name value size binding type visibility section-index table-section)
+    (vector 'elf-symbol index name value size binding type visibility section-index table-section))
+
+  (define (elf-symbol? x)
+    (and (vector? x)
+         (= (vector-length x) 10)
+         (eq? (vector-ref x 0) 'elf-symbol)))
+
+  (define (elf-symbol-index symbol) (vector-ref symbol 1))
+  (define (elf-symbol-name symbol) (vector-ref symbol 2))
+  (define (elf-symbol-value symbol) (vector-ref symbol 3))
+  (define (elf-symbol-size symbol) (vector-ref symbol 4))
+  (define (elf-symbol-binding symbol) (vector-ref symbol 5))
+  (define (elf-symbol-type symbol) (vector-ref symbol 6))
+  (define (elf-symbol-visibility symbol) (vector-ref symbol 7))
+  (define (elf-symbol-section-index symbol) (vector-ref symbol 8))
+  (define (elf-symbol-table-section symbol) (vector-ref symbol 9))
+
   (define (bv-len bv) (bytevector-length bv))
 
   (define (require-range who bv offset size)
@@ -256,6 +286,115 @@
                               (raw-section-info raw))])
               (loop (+ index 1) (cdr raws) (cons section sections)))))))
 
+  (define (symbol-table-section? section)
+    (or (= (elf-section-type section) 2)
+        (= (elf-section-type section) 11)))
+
+  (define (symbol-binding-name n)
+    (case n
+      [(0) 'local]
+      [(1) 'global]
+      [(2) 'weak]
+      [(10) 'loos]
+      [(12) 'hios]
+      [(13) 'loproc]
+      [(15) 'hiproc]
+      [else n]))
+
+  (define (symbol-type-name n)
+    (case n
+      [(0) 'notype]
+      [(1) 'object]
+      [(2) 'func]
+      [(3) 'section]
+      [(4) 'file]
+      [(5) 'common]
+      [(6) 'tls]
+      [(10) 'loos]
+      [(12) 'hios]
+      [(13) 'loproc]
+      [(15) 'hiproc]
+      [else n]))
+
+  (define (symbol-visibility-name n)
+    (case n
+      [(0) 'default]
+      [(1) 'internal]
+      [(2) 'hidden]
+      [(3) 'protected]
+      [else n]))
+
+  (define (parse-symbol-entry class endian bv offset)
+    (if (eq? class 'elf64)
+        (let* ([name-offset (u32 bv offset endian)]
+               [info (u8 bv (+ offset 4))]
+               [other (u8 bv (+ offset 5))]
+               [section-index (u16 bv (+ offset 6) endian)]
+               [value (u64 bv (+ offset 8) endian)]
+               [size (u64 bv (+ offset 16) endian)])
+          (list name-offset info other section-index value size))
+        (let* ([name-offset (u32 bv offset endian)]
+               [value (u32 bv (+ offset 4) endian)]
+               [size (u32 bv (+ offset 8) endian)]
+               [info (u8 bv (+ offset 12))]
+               [other (u8 bv (+ offset 13))]
+               [section-index (u16 bv (+ offset 14) endian)])
+          (list name-offset info other section-index value size))))
+
+  (define (parse-symbols-for-section class endian bv sections table-section)
+    (let* ([entry-size (if (zero? (elf-section-entry-size table-section))
+                           (if (eq? class 'elf64) 24 16)
+                           (elf-section-entry-size table-section))]
+           [count (quotient (elf-section-size table-section) entry-size)]
+           [string-section (list-ref/default sections (elf-section-link table-section) #f)]
+           [string-bounds (if string-section
+                              (cons (elf-section-offset string-section)
+                                    (+ (elf-section-offset string-section)
+                                       (elf-section-size string-section)))
+                              (cons 0 0))])
+      (let loop ([index 0] [symbols '()])
+        (if (= index count)
+            (reverse symbols)
+            (let* ([entry-offset (+ (elf-section-offset table-section)
+                                    (* index entry-size))]
+                   [entry (parse-symbol-entry class endian bv entry-offset)]
+                   [name-offset (list-ref entry 0)]
+                   [info (list-ref entry 1)]
+                   [other (list-ref entry 2)]
+                   [section-index (list-ref entry 3)]
+                   [value (list-ref entry 4)]
+                   [size (list-ref entry 5)]
+                   [binding (symbol-binding-name (quotient info 16))]
+                   [type (symbol-type-name (modulo info 16))]
+                   [visibility (symbol-visibility-name (bitwise-and other #x3))]
+                   [name (if (or (zero? name-offset) (not string-section))
+                             ""
+                             (read-c-string bv
+                                            (+ (car string-bounds) name-offset)
+                                            (cdr string-bounds)))])
+              (loop (+ index 1)
+                    (cons (make-elf-symbol index
+                                           name
+                                           value
+                                           size
+                                           binding
+                                           type
+                                           visibility
+                                           section-index
+                                           (elf-section-name table-section))
+                          symbols)))))))
+
+  (define (parse-symbols class endian bv sections)
+    (let ([all-sections sections])
+      (let loop ([remaining sections] [symbols '()])
+        (cond
+          [(null? remaining) (reverse symbols)]
+          [(symbol-table-section? (car remaining))
+           (loop (cdr remaining)
+                 (append (reverse (parse-symbols-for-section class endian bv all-sections (car remaining)))
+                         symbols))]
+          [else (loop (cdr remaining) symbols)]))))
+
   (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)
@@ -275,8 +414,9 @@
              [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))))
+             [sections (materialize-sections bv raw-sections shstrndx)]
+             [symbols (parse-symbols class endian bv sections)])
+        (make-elf-object class endian machine entry sections symbols path bv))))
 
   (define (read-elf-file path)
     (read-elf-bytevector (read-file-bytevector path) path))
diff --git a/main.ss b/main.ss
index 304b989..320220c 100644
--- a/main.ss
+++ b/main.ss
@@ -8,6 +8,7 @@
   (display "  jasm object [--arch <arch>] [--syntax att|intel] [--section name] <file>\n")
   (display "  jasm headers <file>\n")
   (display "  jasm sections <file>\n")
+  (display "  jasm symbols <file>\n")
   (display "\nExamples:\n")
   (display "  jasm raw --arch x86-64 --syntax intel --hex '55 48 89 e5 c3'\n")
   (display "  jasm raw --arch aarch64 --hex '20 00 80 d2 c0 03 5f d6'\n")
@@ -96,6 +97,34 @@
 (define (print-elf-sections obj)
   (for-each print-section-row (elf-object-sections obj)))
 
+(define (section-name-by-index obj index)
+  (if (and (>= index 0) (< index (length (elf-object-sections obj))))
+      (elf-section-name (list-ref (elf-object-sections obj) index))
+      (number->string index)))
+
+(define (print-symbol-row obj symbol)
+  (display "[")
+  (display (elf-symbol-index symbol))
+  (display "] ")
+  (display (hex-number (elf-symbol-value symbol)))
+  (display "\tsize=")
+  (display (elf-symbol-size symbol))
+  (display "\t")
+  (display (elf-symbol-binding symbol))
+  (display "\t")
+  (display (elf-symbol-type symbol))
+  (display "\t")
+  (display (elf-symbol-visibility symbol))
+  (display "\t")
+  (display (section-name-by-index obj (elf-symbol-section-index symbol)))
+  (display "\t")
+  (display (elf-symbol-name symbol))
+  (newline))
+
+(define (print-elf-symbols obj)
+  (for-each (lambda (symbol) (print-symbol-row obj symbol))
+            (elf-object-symbols obj)))
+
 (define (one-file args command-name)
   (cond
     [(null? args) (error 'jasm (string-append command-name " requires a file"))]
@@ -180,6 +209,9 @@
 (define (parse-sections args)
   (print-elf-sections (read-elf-file (one-file args "sections"))))
 
+(define (parse-symbols args)
+  (print-elf-symbols (read-elf-file (one-file args "symbols"))))
+
 (define (main args)
   (guard (exn [#t
                (display "jasm: " (current-error-port))
@@ -201,6 +233,10 @@
        (parse-headers (cdr args))]
       [(string=? (car args) "sections")
        (parse-sections (cdr args))]
+      [(or (string=? (car args) "symbols")
+           (string=? (car args) "--syms")
+           (string=? (car args) "-t"))
+       (parse-symbols (cdr args))]
       [else
        (usage)
        (exit 1)])))
diff --git a/tests/test-jasm.ss b/tests/test-jasm.ss
index 899a47b..e00e7cf 100644
--- a/tests/test-jasm.ss
+++ b/tests/test-jasm.ss
@@ -27,7 +27,7 @@
     (instruction-text (car insns))))
 
 (define (sample-elf64le)
-  (let ([bv (make-bytevector #x200 0)])
+  (let ([bv (make-bytevector #x300 0)])
     (define (put8! offset value)
       (bytevector-u8-set! bv offset (modulo value #x100)))
     (define (put16le! offset value)
@@ -58,6 +58,13 @@
       (put32le! (+ offset 44) info)
       (put64le! (+ offset 48) align)
       (put64le! (+ offset 56) entry-size))
+    (define (put-sym64! offset name info other section-index value size)
+      (put32le! offset name)
+      (put8! (+ offset 4) info)
+      (put8! (+ offset 5) other)
+      (put16le! (+ offset 6) section-index)
+      (put64le! (+ offset 8) value)
+      (put64le! (+ offset 16) size))
     (put8! 0 #x7f)
     (put-ascii! 1 "ELF")
     (put8! 4 2)
@@ -74,13 +81,17 @@
     (put16le! 54 0)
     (put16le! 56 0)
     (put16le! 58 64)
-    (put16le! 60 3)
+    (put16le! 60 5)
     (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-ascii! #x90 (string #\nul #\. #\t #\e #\x #\t #\nul #\. #\s #\h #\s #\t #\r #\t #\a #\b #\nul #\. #\s #\t #\r #\t #\a #\b #\nul #\. #\s #\y #\m #\t #\a #\b #\nul))
+    (put-ascii! #xb0 (string #\nul #\f #\u #\n #\c #\nul))
+    (put-sym64! #x238 1 #x12 0 1 #x1000 5)
     (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)
+    (put-shdr64! #x140 7 3 0 0 #x90 33 0 0 1 0)
+    (put-shdr64! #x180 17 3 0 0 #xb0 6 0 0 1 0)
+    (put-shdr64! #x1c0 25 2 0 0 #x220 48 3 1 8 24)
     bv))
 
 (define (write-test-bytevector-file path bv)
@@ -127,7 +138,7 @@
           (elf-section-address text)
           (elf-section-executable? text)
           (map elf-section-name (elf-executable-sections obj))))
-  '(3 #t 1 4096 #t (".text")))
+  '(5 #t 1 4096 #t (".text")))
 
 (test "elf section bytes"
   (let* ([obj (read-elf-bytevector (sample-elf64le))]
@@ -135,6 +146,20 @@
     (bytevector->u8-list (elf-section-bytes obj text)))
   '(85 195 144 144 195))
 
+(test "elf symbols"
+  (let* ([obj (read-elf-bytevector (sample-elf64le))]
+         [symbol (cadr (elf-object-symbols obj))])
+    (list (length (elf-object-symbols obj))
+          (elf-symbol? symbol)
+          (elf-symbol-name symbol)
+          (elf-symbol-binding symbol)
+          (elf-symbol-type symbol)
+          (elf-symbol-value symbol)
+          (elf-symbol-size symbol)
+          (elf-symbol-section-index symbol)
+          (elf-symbol-table-section symbol)))
+  '(2 #t "func" global func 4096 5 1 ".symtab"))
+
 (test "elf file reader"
   (let ([path ".jasm-test-sample.o"])
     (write-test-bytevector-file path (sample-elf64le))
diff --git a/tests/write-sample-elf.ss b/tests/write-sample-elf.ss
index 92efab9..c032223 100644
--- a/tests/write-sample-elf.ss
+++ b/tests/write-sample-elf.ss
@@ -3,7 +3,7 @@
 (import (chezscheme))
 
 (define (sample-elf64le)
-  (let ([bv (make-bytevector #x200 0)])
+  (let ([bv (make-bytevector #x300 0)])
     (define (put8! offset value)
       (bytevector-u8-set! bv offset (modulo value #x100)))
     (define (put16le! offset value)
@@ -34,6 +34,13 @@
       (put32le! (+ offset 44) info)
       (put64le! (+ offset 48) align)
       (put64le! (+ offset 56) entry-size))
+    (define (put-sym64! offset name info other section-index value size)
+      (put32le! offset name)
+      (put8! (+ offset 4) info)
+      (put8! (+ offset 5) other)
+      (put16le! (+ offset 6) section-index)
+      (put64le! (+ offset 8) value)
+      (put64le! (+ offset 16) size))
     (put8! 0 #x7f)
     (put-ascii! 1 "ELF")
     (put8! 4 2)
@@ -50,13 +57,17 @@
     (put16le! 54 0)
     (put16le! 56 0)
     (put16le! 58 64)
-    (put16le! 60 3)
+    (put16le! 60 5)
     (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-ascii! #x90 (string #\nul #\. #\t #\e #\x #\t #\nul #\. #\s #\h #\s #\t #\r #\t #\a #\b #\nul #\. #\s #\t #\r #\t #\a #\b #\nul #\. #\s #\y #\m #\t #\a #\b #\nul))
+    (put-ascii! #xb0 (string #\nul #\f #\u #\n #\c #\nul))
+    (put-sym64! #x238 1 #x12 0 1 #x1000 5)
     (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)
+    (put-shdr64! #x140 7 3 0 0 #x90 33 0 0 1 0)
+    (put-shdr64! #x180 17 3 0 0 #xb0 6 0 0 1 0)
+    (put-shdr64! #x1c0 25 2 0 0 #x220 48 3 1 8 24)
     bv))
 
 (define (write-bytevector-file path bv)