about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xsubx/apps/surveybin41174 -> 42791 bytes
-rw-r--r--subx/apps/survey.subx582
-rwxr-xr-xsubx/test_apps10
3 files changed, 552 insertions, 40 deletions
diff --git a/subx/apps/survey b/subx/apps/survey
index 61e2f974..b7bd7dd3 100755
--- a/subx/apps/survey
+++ b/subx/apps/survey
Binary files differdiff --git a/subx/apps/survey.subx b/subx/apps/survey.subx
index f25c62d2..79fddc53 100644
--- a/subx/apps/survey.subx
+++ b/subx/apps/survey.subx
@@ -31,6 +31,10 @@
 #   ee nn nn nn nn  # some computed address
 #   # ELF header above will specify that data segment begins at this offset
 #   00
+#
+# The ELF format has some persnickety constraints on the starting addresses of
+# segments, so input headers are treated as guidelines and adjusted in the
+# output.
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
@@ -1783,7 +1787,14 @@ emit-segments:  # in : (address stream), out : (address buffered-file), segments
     #         continue
     #       datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
     #       info = get-slice(labels, datum)
-    #       if has-metadata?(word-slice, "imm8")
+    #       if !string-equal?(info->segment-name, "code")
+    #         if has-metadata?(word-slice, "disp8")
+    #           abort
+    #         if has-metadata?(word-slice, "imm8")
+    #           abort
+    #         emit(out, info->address, 4)  # global variables always translate to absolute addresses
+    #       # code segment cases
+    #       else if has-metadata?(word-slice, "imm8")
     #         abort  # label should never go to imm8
     #       else if has-metadata?(word-slice, "imm32")
     #         emit(out, info->address, 4)
@@ -1880,7 +1891,7 @@ $emit-segments:line-loop:
 #?     # . . discard args
 #?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 #?     # }}}
-$emit-segments:check0:
+$emit-segments:check-for-end-of-input:
     # if (line->write == 0) break
     81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
     0f 84/jump-if-equal  $emit-segments:end/disp32
@@ -1936,7 +1947,7 @@ $emit-segments:word-loop:
 #?     # . . discard args
 #?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 #?     # }}}
-$emit-segments:check1:
+$emit-segments:check-for-end-of-line:
     # if (slice-empty?(word-slice)) break
     # . EAX = slice-empty?(word-slice)
     # . . push args
@@ -2066,7 +2077,84 @@ $emit-segments:check-metadata:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # . ESI = EAX
     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
-$emit-segments:check-for-imm8:
+$emit-segments:check-global-variable:
+#?     # dump info->segment-name {{{
+#?     # . write(2/stderr, "aa: label segment: ")
+#?     # . . push args
+#?     68/push  "aa: label segment: "/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, info->segment-name)
+#?     # . . push args
+#?     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+    # if string-equal?(info->segment-name, "code") goto code label checks
+    # . EAX = string-equal?(info->segment-name, "code")
+    # . . push args
+    68/push  "code"/imm32
+    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
+    # . . call
+    e8/call  string-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . if (EAX != 0) goto code label checks
+    3d/compare-EAX-and  0/imm32
+    0f 85/jump-if-not-equal  $emit-segments:check-code-label-for-imm8/disp32
+$emit-segments:check-global-variable-for-disp8:
+    # if has-metadata?(word-slice, "disp8") abort
+    # . EAX = has-metadata?(word-slice, "disp8")
+    # . . push args
+    68/push  "disp8"/imm32
+    52/push-EDX
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . if (EAX != 0) abort
+    3d/compare-EAX-and  0/imm32
+    0f 85/jump-if-not-equal  $emit-segments:global-variable-abort/disp32
+$emit-segments:check-global-variable-for-imm8:
+    # if has-metadata?(word-slice, "imm8") abort
+    # . EAX = has-metadata?(word-slice, "imm8")
+    # . . push args
+    68/push  "imm8"/imm32
+    52/push-EDX
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . if (EAX != 0) abort
+    3d/compare-EAX-and  0/imm32
+    0f 85/jump-if-not-equal  $emit-segments:global-variable-abort/disp32
+$emit-segments:emit-global-variable:
+    # emit-hex(out, info->address, 4)
+    # . . push args
+    68/push  4/imm32
+    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # continue
+    e9/jump  $emit-segments:word-loop/disp32
+$emit-segments:check-code-label-for-imm8:
     # if (has-metadata?(word-slice, "imm8")) abort
     # . EAX = has-metadata?(EDX, "imm8")
     # . . push args
@@ -2079,7 +2167,7 @@ $emit-segments:check-for-imm8:
     # . if (EAX != 0) abort
     3d/compare-EAX-and  0/imm32
     0f 85/jump-if-not-equal  $emit-segments:imm8-abort/disp32
-$emit-segments:check-for-imm32:
+$emit-segments:check-code-label-for-imm32:
     # if (!has-metadata?(word-slice, "imm32")) goto next check
     # . EAX = has-metadata?(EDX, "imm32")
     # . . push args
@@ -2091,7 +2179,7 @@ $emit-segments:check-for-imm32:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # . if (EAX == 0) goto next check
     3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-segments:check-for-disp8/disp8
+    74/jump-if-equal  $emit-segments:check-code-label-for-disp8/disp8
 #?     # dump info->address {{{
 #?     # . write(2/stderr, "info->address: ")
 #?     # . . push args
@@ -2125,6 +2213,7 @@ $emit-segments:check-for-imm32:
 #?     # . . discard args
 #?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 #?     # }}}
+$emit-segments:emit-code-label-imm32:
     # emit-hex(out, info->address, 4)
     # . . push args
     68/push  4/imm32
@@ -2136,7 +2225,7 @@ $emit-segments:check-for-imm32:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # continue
     e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-for-disp8:
+$emit-segments:check-code-label-for-disp8:
     # if (!has-metadata?(word-slice, "disp8")) goto next check
     # . EAX = has-metadata?(EDX, "disp8")
     # . . push args
@@ -2148,7 +2237,8 @@ $emit-segments:check-for-disp8:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # . if (EAX == 0) goto next check
     3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-segments:check-for-disp32/disp8
+    74/jump-if-equal  $emit-segments:check-code-label-for-disp32/disp8
+$emit-segments:emit-code-label-disp8:
     # emit-hex(out, info->offset - offset-of-next-instruction, 1)
     # . . push args
     68/push  1/imm32
@@ -2162,7 +2252,7 @@ $emit-segments:check-for-disp8:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # continue
     e9/jump  $emit-segments:word-loop/disp32
-$emit-segments:check-for-disp32:
+$emit-segments:check-code-label-for-disp32:
     # if (!has-metadata?(word-slice, "disp32")) abort
     # . EAX = has-metadata?(EDX, "disp32")
     # . . push args
@@ -2174,7 +2264,8 @@ $emit-segments:check-for-disp32:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # . if (EAX == 0) abort
     3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $emit-segments:abort/disp8
+    0f 84/jump-if-equal  $emit-segments:abort/disp32
+$emit-segments:emit-code-label-disp32:
     # emit-hex(out, info->offset - offset-of-next-instruction, 4)
     # . . push args
     68/push  4/imm32
@@ -2214,10 +2305,25 @@ $emit-segments:end:
     5d/pop-to-EBP
     c3/return
 
+$emit-segments:global-variable-abort:
+    # . _write(2/stderr, error)
+    # . . push args
+    68/push  "emit-segments: must refer to global variables with /disp32 or /imm32"/imm32
+    68/push  2/imm32/stderr
+    # . . call
+    e8/call  _write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . syscall(exit, 1)
+    bb/copy-to-EBX  1/imm32
+    b8/copy-to-EAX  1/imm32/exit
+    cd/syscall  0x80/imm8
+    # never gets here
+
 $emit-segments:imm8-abort:
     # . _write(2/stderr, error)
     # . . push args
-    68/push  "emit-segments: unexpected /imm8"/imm32
+    68/push  "emit-segments: cannot refer to code labels with /imm8"/imm32
     68/push  2/imm32/stderr
     # . . call
     e8/call  _write/disp32
@@ -2260,21 +2366,22 @@ $emit-segments:abort:
     cd/syscall  0x80/imm8
     # never gets here
 
-test-emit-segments:
+test-emit-segments-global-variable:
+    # global variables always convert to absolute addresses, regardless of metadata
+    #
     # input:
     #   in:
     #     == code 0x1000
     #     ab cd ef gh
-    #     ij x/imm32
+    #     ij x/disp32
     #     == data 0x2000
     #     00
     #     x:
     #       34
     #   segments:
-    #     - 'code': {0x1074, 0, 5}
-    #     - 'data': {0x2079, 5, 1}
+    #     - 'code': {0x1074, 0, 9}
+    #     - 'data': {0x2079, 5, 2}
     #   labels:
-    #     - 'l1': {'code', 3, 0x1077}
     #     - 'x': {'data', 1, 0x207a}
     #
     # output:
@@ -2339,9 +2446,9 @@ test-emit-segments:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . write(_test-input-stream, "ij x/imm32\n")
+    # . write(_test-input-stream, "ij x/disp32\n")
     # . . push args
-    68/push  "ij x/imm32\n"/imm32
+    68/push  "ij x/disp32\n"/imm32
     68/push  _test-input-stream/imm32
     # . . call
     e8/call  write/disp32
@@ -2379,8 +2486,8 @@ test-emit-segments:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . stream-add4(segments, "code", 0x1074, 0, 5)
-    68/push  5/imm32/segment-size
+    # . stream-add4(segments, "code", 0x1074, 0, 9)
+    68/push  9/imm32/segment-size
     68/push  0/imm32/file-offset
     68/push  0x1074/imm32/start-address
     68/push  "code"/imm32/segment-name
@@ -2389,7 +2496,7 @@ test-emit-segments:
     e8/call  stream-add4/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(segments, "data", 0x2079, 5, 1)
+    # . stream-add4(segments, "data", 0x2079, 5, 2)
     68/push  1/imm32/segment-size
     68/push  5/imm32/file-offset
     68/push  0x2079/imm32/start-address
@@ -2399,16 +2506,6 @@ test-emit-segments:
     e8/call  stream-add4/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
-    # . stream-add4(labels, "l1", "code", 3, 0x1077)
-    68/push  0x1077/imm32/label-address
-    68/push  3/imm32/segment-offset
-    68/push  "code"/imm32/segment-name
-    68/push  "l1"/imm32/label-name
-    52/push-EDX
-    # . . call
-    e8/call  stream-add4/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
     # . stream-add4(labels, "x", "data", 1, 0x207a)
     68/push  0x207a/imm32/label-address
     68/push  1/imm32/segment-offset
@@ -2431,6 +2528,13 @@ test-emit-segments:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
     # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 #?     # dump output {{{
 #?     # . write(2/stderr, "result: ^")
 #?     # . . push args
@@ -2466,7 +2570,7 @@ test-emit-segments:
 #?     # }}}
     # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
     # . . push args
-    68/push  "F - test-emit-segments/0"/imm32
+    68/push  "F - test-emit-segments-global-variable/0"/imm32
     68/push  "ab cd ef gh "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -2475,7 +2579,7 @@ test-emit-segments:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # . check-next-stream-line-equal(_test-output-stream, "ij 7a 20 00 00 ", msg)
     # . . push args
-    68/push  "F - test-emit-segments/1"/imm32
+    68/push  "F - test-emit-segments-global-variable/1"/imm32
     68/push  "ij 7a 20 00 00 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -2484,7 +2588,7 @@ test-emit-segments:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
     # . . push args
-    68/push  "F - test-emit-segments/2"/imm32
+    68/push  "F - test-emit-segments-global-variable/2"/imm32
     68/push  "00 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -2493,7 +2597,7 @@ test-emit-segments:
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
     # . . push args
-    68/push  "F - test-emit-segments/3"/imm32
+    68/push  "F - test-emit-segments-global-variable/3"/imm32
     68/push  "34 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -2505,6 +2609,414 @@ test-emit-segments:
     5d/pop-to-EBP
     c3/return
 
+test-emit-segments-code-label:
+    # labels usually convert to displacements
+    #
+    # input:
+    #   in:
+    #     == code 0x1000
+    #     ab cd
+    #     l1:
+    #       ef gh
+    #       ij l1/disp32
+    #   segments:
+    #     - 'code': {0x1054, 0, 9}
+    #   labels:
+    #     - 'l1': {'code', 2, 0x1056}
+    #
+    # output:
+    #   ab cd
+    #   ef gh
+    #   ij f9 ff ff ff  # -7
+    #
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . var segments/ECX = stream(10 * 16)
+    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
+    68/push  0xa0/imm32/length
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # . var labels/EDX = stream(512 * 16)
+    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
+    68/push  0x2000/imm32/length
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+    # initialize input
+    # . write(_test-input-stream, "== code 0x1000\n")
+    # . . push args
+    68/push  "== code 0x1000\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "ab cd\n")
+    # . . push args
+    68/push  "ab cd\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "l1:\n")
+    # . . push args
+    68/push  "l1:\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "  ef gh\n")
+    # . . push args
+    68/push  "  ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "  ij l1/disp32\n")
+    # . . push args
+    68/push  "  ij l1/disp32\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . stream-add4(segments, "code", 0x1054, 0, 9)
+    68/push  9/imm32/segment-size
+    68/push  0/imm32/file-offset
+    68/push  0x1054/imm32/start-address
+    68/push  "code"/imm32/segment-name
+    51/push-ECX
+    # . . call
+    e8/call  stream-add4/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
+    # . stream-add4(labels, "l1", "code", 2, 0x1056)
+    68/push  0x1056/imm32/label-address
+    68/push  2/imm32/segment-offset
+    68/push  "code"/imm32/segment-name
+    68/push  "l1"/imm32/label-name
+    52/push-EDX
+    # . . call
+    e8/call  stream-add4/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
+    # component under test
+    # . emit-segments(_test-input-stream, _test-output-buffered-file, segments, labels)
+    # . . push args
+    52/push-EDX
+    51/push-ECX
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-segments/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label/0"/imm32
+    68/push  "ab cd "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label/1"/imm32
+    68/push  "ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label/2"/imm32
+    68/push  "ij f9 ff ff ff "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-emit-segments-code-label-absolute:
+    # labels can also convert to absolute addresses
+    #
+    # input:
+    #   in:
+    #     == code 0x1000
+    #     ab cd
+    #     l1:
+    #       ef gh
+    #       ij l1/imm32
+    #   segments:
+    #     - 'code': {0x1054, 0, 9}
+    #   labels:
+    #     - 'l1': {'code', 2, 0x1056}
+    #
+    # output:
+    #   ab cd
+    #   ef gh
+    #   ij 56 10 00 00
+    #
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . var segments/ECX = stream(10 * 16)
+    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
+    68/push  0xa0/imm32/length
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # . var labels/EDX = stream(512 * 16)
+    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
+    68/push  0x2000/imm32/length
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+    # initialize input
+    # . write(_test-input-stream, "== code 0x1000\n")
+    # . . push args
+    68/push  "== code 0x1000\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "ab cd\n")
+    # . . push args
+    68/push  "ab cd\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "l1:\n")
+    # . . push args
+    68/push  "l1:\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "  ef gh\n")
+    # . . push args
+    68/push  "  ef gh\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(_test-input-stream, "  ij l1/imm32\n")
+    # . . push args
+    68/push  "  ij l1/imm32\n"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . stream-add4(segments, "code", 0x1054, 0, 9)
+    68/push  9/imm32/segment-size
+    68/push  0/imm32/file-offset
+    68/push  0x1054/imm32/start-address
+    68/push  "code"/imm32/segment-name
+    51/push-ECX
+    # . . call
+    e8/call  stream-add4/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
+    # . stream-add4(labels, "l1", "code", 2, 0x1056)
+    68/push  0x1056/imm32/label-address
+    68/push  2/imm32/segment-offset
+    68/push  "code"/imm32/segment-name
+    68/push  "l1"/imm32/label-name
+    52/push-EDX
+    # . . call
+    e8/call  stream-add4/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
+    # component under test
+    # . emit-segments(_test-input-stream, _test-output-buffered-file, segments, labels)
+    # . . push args
+    52/push-EDX
+    51/push-ECX
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  emit-segments/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+    # checks
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # }}}
+    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label-absolute/0"/imm32
+    68/push  "ab cd "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label-absolute/1"/imm32
+    68/push  "ef gh "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
+    # . . push args
+    68/push  "F - test-emit-segments-code-label-absolute/2"/imm32
+    68/push  "ij 56 10 00 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
 emit-headers:  # out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
     # pseudocode:
     #   emit-elf-header(out, segments, labels)
diff --git a/subx/test_apps b/subx/test_apps
index cc0f301f..e9d75364 100755
--- a/subx/test_apps
+++ b/subx/test_apps
@@ -258,11 +258,11 @@ test `uname` = 'Linux'  &&  {
   cat examples/ex5.subx |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex5 -
 }
 
-#? echo ex6
-#? cat examples/ex6.subx |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex6 -
-#? test `uname` = 'Linux'  &&  {
-#?   cat examples/ex6.subx |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex6 -
-#? }
+echo ex6
+cat examples/ex6.subx |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex6 -
+test `uname` = 'Linux'  &&  {
+  cat examples/ex6.subx |apps/dquotes |apps/assort |apps/pack |apps/survey |apps/hex |diff examples/ex6 -
+}
 
 echo ex7
 cat examples/ex7.subx |./subx_bin run apps/dquotes |./subx_bin run apps/assort |./subx_bin run apps/pack |./subx_bin run apps/survey |./subx_bin run apps/hex |diff examples/ex7 -