about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-07-19 11:29:52 -0700
committerKartik Agaram <vc@akkartik.com>2019-07-19 11:29:52 -0700
commit31cb01daf4c481736e151dde88d1ec55005f6347 (patch)
tree4a13e717ebe64abcddd2be55e2e10eb2e962c9af /subx
parentcfb4b738c60ea7126f831248e665e15fea780216 (diff)
downloadmu-31cb01daf4c481736e151dde88d1ec55005f6347.tar.gz
5419
Bugfix fourteen: we need different address computation logic for code vs
data labels.

It's really about different categories of instructions having different
address computation logic. This subtle distinction will make good error
messages hard. But that's a problem for later.

Now there's just one example program not translating.
Diffstat (limited to 'subx')
-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 -