Add Mach-O symbol table support

ober

3cf76c22754444a47fece151fa7df05bbb7cf9f4

diff --git a/docs/plan.md b/docs/plan.md
index a2025a3..5ee38f7 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -49,8 +49,9 @@ reference material and parity targets.
 - Raw CLI mode accepts byte input from `--hex` or `--file`. The reusable
   bytevector API supports `max-bytes` and `stop-address` limits.
 - `(jasm object macho)` can parse 64-bit Mach-O headers and `LC_SEGMENT_64`
-  section tables. The CLI detects ELF vs. Mach-O for `headers`, `sections`, and
-  executable-section disassembly.
+  section tables, plus `LC_SYMTAB` symbol tables. The CLI detects ELF vs.
+  Mach-O for `headers`, `sections`, `symbols`, and executable-section
+  disassembly.
 
 ## Core Architecture
 
@@ -238,8 +239,8 @@ Required:
 - Universal/fat binaries.
 - Load commands. Initial `LC_SEGMENT_64` support is implemented.
 - Segments and sections. Initial parser is implemented.
-- Symbol table.
-- String table.
+- Symbol table. Initial `LC_SYMTAB` parsing is implemented.
+- String table. Initial `LC_SYMTAB` string table parsing is implemented.
 - Dysymtab.
 - Relocations.
 - ARM64 and x86-64 architecture mapping.
diff --git a/jasm b/jasm
index 0bab9a2..6ce4cae 100755
Binary files a/jasm and b/jasm differ
diff --git a/lib/jasm/object/macho.ss b/lib/jasm/object/macho.ss
index 0308aba..813d41f 100644
--- a/lib/jasm/object/macho.ss
+++ b/lib/jasm/object/macho.ss
@@ -10,6 +10,7 @@
     macho-object-architecture
     macho-object-file-type
     macho-object-sections
+    macho-object-symbols
     macho-section?
     macho-section-index
     macho-section-segment
@@ -23,24 +24,32 @@
     macho-section-executable?
     macho-section-by-name
     macho-executable-sections
-    macho-section-bytes)
+    macho-section-bytes
+    macho-symbol?
+    macho-symbol-index
+    macho-symbol-name
+    macho-symbol-type
+    macho-symbol-section-index
+    macho-symbol-description
+    macho-symbol-value)
 
   (import (chezscheme))
 
-  (define (make-macho-object endian cpu-type file-type sections path bytes)
-    (vector 'macho-object endian cpu-type file-type sections path bytes))
+  (define (make-macho-object endian cpu-type file-type sections symbols path bytes)
+    (vector 'macho-object endian cpu-type file-type sections symbols path bytes))
 
   (define (macho-object? x)
     (and (vector? x)
-         (= (vector-length x) 7)
+         (= (vector-length x) 8)
          (eq? (vector-ref x 0) 'macho-object)))
 
   (define (macho-object-endian obj) (vector-ref obj 1))
   (define (macho-object-cpu-type obj) (vector-ref obj 2))
   (define (macho-object-file-type obj) (vector-ref obj 3))
   (define (macho-object-sections obj) (vector-ref obj 4))
-  (define (macho-object-path obj) (vector-ref obj 5))
-  (define (macho-object-bytes obj) (vector-ref obj 6))
+  (define (macho-object-symbols obj) (vector-ref obj 5))
+  (define (macho-object-path obj) (vector-ref obj 6))
+  (define (macho-object-bytes obj) (vector-ref obj 7))
 
   (define (make-macho-section index segment name flags address offset size align)
     (vector 'macho-section index segment name flags address offset size align))
@@ -59,6 +68,21 @@
   (define (macho-section-size section) (vector-ref section 7))
   (define (macho-section-align section) (vector-ref section 8))
 
+  (define (make-macho-symbol index name type section-index description value)
+    (vector 'macho-symbol index name type section-index description value))
+
+  (define (macho-symbol? x)
+    (and (vector? x)
+         (= (vector-length x) 7)
+         (eq? (vector-ref x 0) 'macho-symbol)))
+
+  (define (macho-symbol-index symbol) (vector-ref symbol 1))
+  (define (macho-symbol-name symbol) (vector-ref symbol 2))
+  (define (macho-symbol-type symbol) (vector-ref symbol 3))
+  (define (macho-symbol-section-index symbol) (vector-ref symbol 4))
+  (define (macho-symbol-description symbol) (vector-ref symbol 5))
+  (define (macho-symbol-value symbol) (vector-ref symbol 6))
+
   (define (macho-section-full-name section)
     (string-append (macho-section-segment section) "," (macho-section-name section)))
 
@@ -74,6 +98,14 @@
     (require-range 'read-macho-bytevector bv offset 1)
     (bytevector-u8-ref bv offset))
 
+  (define (u16 bv offset endian)
+    (require-range 'read-macho-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-macho-bytevector bv offset 4)
     (let ([b0 (u8 bv offset)]
@@ -148,24 +180,61 @@
                           sections)))))))
 
   (define (parse-load-commands endian bv ncmds)
-    (let loop ([i 0] [offset 32] [section-index 0] [sections '()])
+    (let loop ([i 0] [offset 32] [section-index 0] [sections '()] [symtab #f])
       (if (= i ncmds)
-          (reverse sections)
+          (vector (reverse sections) symtab)
           (let* ([cmd (u32 bv offset endian)]
                  [cmdsize (u32 bv (+ offset 4) endian)])
             (require-range 'read-macho-bytevector bv offset cmdsize)
-            (if (= cmd #x19)
-                (let* ([result (parse-segment-64 endian bv offset section-index)]
-                       [next-index (car result)]
-                       [new-sections (cdr result)])
-                  (loop (+ i 1)
-                        (+ offset cmdsize)
-                        next-index
-                        (append (reverse new-sections) sections)))
-                (loop (+ i 1)
-                      (+ offset cmdsize)
-                      section-index
-                      sections))))))
+            (cond
+              [(= cmd #x19)
+               (let* ([result (parse-segment-64 endian bv offset section-index)]
+                      [next-index (car result)]
+                      [new-sections (cdr result)])
+                 (loop (+ i 1)
+                       (+ offset cmdsize)
+                       next-index
+                       (append (reverse new-sections) sections)
+                       symtab))]
+              [(= cmd #x2)
+               (loop (+ i 1)
+                     (+ offset cmdsize)
+                     section-index
+                     sections
+                     (list (u32 bv (+ offset 8) endian)
+                           (u32 bv (+ offset 12) endian)
+                           (u32 bv (+ offset 16) endian)
+                           (u32 bv (+ offset 20) endian)))]
+              [else
+               (loop (+ i 1)
+                     (+ offset cmdsize)
+                     section-index
+                     sections
+                     symtab)])))))
+
+  (define (parse-symbols endian bv symtab)
+    (if (not symtab)
+        '()
+        (let* ([symoff (list-ref symtab 0)]
+               [nsyms (list-ref symtab 1)]
+               [stroff (list-ref symtab 2)]
+               [strsize (list-ref symtab 3)]
+               [str-limit (+ stroff strsize)])
+          (let loop ([index 0] [symbols '()])
+            (if (= index nsyms)
+                (reverse symbols)
+                (let* ([offset (+ symoff (* index 16))]
+                       [strx (u32 bv offset endian)]
+                       [type (u8 bv (+ offset 4))]
+                       [section-index (u8 bv (+ offset 5))]
+                       [description (u16 bv (+ offset 6) endian)]
+                       [value (u64 bv (+ offset 8) endian)]
+                       [name (if (zero? strx)
+                                 ""
+                                 (read-fixed-string bv (+ stroff strx) (- str-limit (+ stroff strx))))])
+                  (loop (+ index 1)
+                        (cons (make-macho-symbol index name type section-index description value)
+                              symbols))))))))
 
   (define (read-macho-bytevector bv . maybe-path)
     (let ([path (if (null? maybe-path) #f (car maybe-path))])
@@ -179,12 +248,15 @@
              [cpu-type (u32 bv 4 endian)]
              [file-type (u32 bv 12 endian)]
              [ncmds (u32 bv 16 endian)]
-             [sizeofcmds (u32 bv 20 endian)])
+             [sizeofcmds (u32 bv 20 endian)]
+             [command-info #f])
         (require-range 'read-macho-bytevector bv 32 sizeofcmds)
+        (set! command-info (parse-load-commands endian bv ncmds))
         (make-macho-object endian
                            cpu-type
                            file-type
-                           (parse-load-commands endian bv ncmds)
+                           (vector-ref command-info 0)
+                           (parse-symbols endian bv (vector-ref command-info 1))
                            path
                            bv))))
 
diff --git a/main.ss b/main.ss
index 69213de..c13ae75 100644
--- a/main.ss
+++ b/main.ss
@@ -202,6 +202,34 @@
   (for-each (lambda (symbol) (print-symbol-row obj symbol))
             (elf-object-symbols obj)))
 
+(define (macho-symbol-section-name obj index)
+  (if (and (> index 0) (<= index (length (macho-object-sections obj))))
+      (macho-section-full-name (list-ref (macho-object-sections obj) (- index 1)))
+      (number->string index)))
+
+(define (print-macho-symbol-row obj symbol)
+  (display "[")
+  (display (macho-symbol-index symbol))
+  (display "] ")
+  (display (hex-number (macho-symbol-value symbol)))
+  (display "\ttype=")
+  (display (hex-number (macho-symbol-type symbol)))
+  (display "\tdesc=")
+  (display (macho-symbol-description symbol))
+  (display "\t")
+  (display (macho-symbol-section-name obj (macho-symbol-section-index symbol)))
+  (display "\t")
+  (display (macho-symbol-name symbol))
+  (newline))
+
+(define (print-object-symbols obj)
+  (cond
+    [(elf-object? obj) (print-elf-symbols obj)]
+    [(macho-object? obj)
+     (for-each (lambda (symbol) (print-macho-symbol-row obj symbol))
+               (macho-object-symbols obj))]
+    [else (error 'jasm "unsupported object record" obj)]))
+
 (define (symbol-name-by-index obj index)
   (if (and (>= index 0) (< index (length (elf-object-symbols obj))))
       (elf-symbol-name (list-ref (elf-object-symbols obj) index))
@@ -367,9 +395,7 @@
 
 (define (parse-symbols args)
   (let ([obj (read-object-file (one-file args "symbols"))])
-    (unless (elf-object? obj)
-      (error 'jasm "symbols are currently implemented for ELF objects"))
-    (print-elf-symbols obj)))
+    (print-object-symbols obj)))
 
 (define (parse-relocs args)
   (let ([obj (read-object-file (one-file args "relocs"))])
diff --git a/tests/test-jasm.ss b/tests/test-jasm.ss
index 06ccafe..478d9d9 100644
--- a/tests/test-jasm.ss
+++ b/tests/test-jasm.ss
@@ -113,6 +113,9 @@
   (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))
@@ -131,8 +134,8 @@
     (put32le! 4 #x0100000c)
     (put32le! 8 0)
     (put32le! 12 1)
-    (put32le! 16 1)
-    (put32le! 20 152)
+    (put32le! 16 2)
+    (put32le! 20 176)
     (put32le! 24 0)
     (put32le! 28 0)
     (put32le! #x20 #x19)
@@ -155,7 +158,19 @@
     (put32le! #xa0 0)
     (put32le! #xa4 0)
     (put32le! #xa8 #x80000400)
+    (put32le! #xb8 #x2)
+    (put32le! #xbc 24)
+    (put32le! #xc0 #x120)
+    (put32le! #xc4 1)
+    (put32le! #xc8 #x140)
+    (put32le! #xcc 7)
     (put-ascii! #x100 (string #\xc0 #\x03 #\x5f #\xd6))
+    (put32le! #x120 1)
+    (put8! #x124 #xf)
+    (put8! #x125 1)
+    (put16le! #x126 0)
+    (put64le! #x128 #x1000)
+    (put-ascii! #x140 (string #\nul #\_ #\f #\u #\n #\c #\nul))
     bv))
 
 (printf "--- jasm tests ---~%")
@@ -259,6 +274,17 @@
     (bytevector->u8-list (macho-section-bytes obj text)))
   '(192 3 95 214))
 
+(test "mach-o symbols"
+  (let* ([obj (read-macho-bytevector (sample-macho64le))]
+         [symbol (car (macho-object-symbols obj))])
+    (list (length (macho-object-symbols obj))
+          (macho-symbol? symbol)
+          (macho-symbol-name symbol)
+          (macho-symbol-type symbol)
+          (macho-symbol-section-index symbol)
+          (macho-symbol-value symbol)))
+  '(1 #t "_func" 15 1 4096))
+
 (test "instruction records are vector-backed"
   (let ([insn (car (disassemble-bytevector 'x86-64 (hex-string->bytevector "55")))])
     (list (instruction? insn)