https://github.com/akkartik/mu/blob/master/subx/056trace.subx

      1 # primitives for emitting traces to a 'trace' stream, and for tests to make assertions on its contents
      2 #
      3 # A trace stream looks like a regular stream:
      4 #   write : int  # index at which writes go
      5 #   read : int  # index that we've read until
      6 #   data : (array byte)  # prefixed by length as usual
      7 # In a real trace the data will be in a special segment set aside for the purpose.
      8 #
      9 # primitives for operating on traces:
     10 #   - initialize-trace-stream (update global variable)
     11 #   - trace: stream, string
     12 #   - die: stream (exit(1) if using real trace)
     13 #   - check-trace-contains: stream, string/line, string/message (scans only from stream's read pointer, prints message to stderr on failure, updates stream's read pointer)
     14 #   - scan-to-next-line: stream (advance read pointer past next newline)
     15 #
     16 # Traces are very fundamental, so many of the helpers we create here won't be
     17 # used elsewhere; we'll switch to more bounds-checked variants. But here we get
     18 # bounds-checking for free; we allocate a completely disjoint segment for trace
     19 # data, and overflowing it will generate a page fault.
     20
     21 == data
     22
     23 # We'll save the address of the trace segment here.
     24 Trace-stream:
     25     0/imm32
     26
     27 # Fake trace-stream for tests.
     28 # Also illustrates the layout of the real trace-stream (segment).
     29 _test-trace-stream:
     30     # current write index
     31     0/imm32
     32     # current read index
     33     0/imm32
     34     # length
     35     8/imm32
     36     # data
     37     00 00 00 00 00 00 00 00  # 8 bytes
     38
     39 == code
     40 #   instruction                     effective address                                                   register    displacement    immediate
     41 # . op          subop               mod             rm32          base        index         scale       r32
     42 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
     43
     44 # Allocate a new segment for the trace stream, initialize its length, and save its address to Trace-stream.
     45 # The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
     46 initialize-trace-stream:
     47     # EAX = new-segment(0x1000)
     48     # . . push args
     49     68/push  0x1000/imm32/N
     50     # . . call
     51     e8/call  new-segment/disp32
     52     # . . discard args
     53     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
     54     # copy EAX to *Trace-stream
     55     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
     56     # Trace-stream->length = 0x1000/N - 12
     57     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         0xff4/imm32       # copy 0xff4 to *(EAX+8)
     58     c3/return
     59
     60 # Append a string to the given trace stream.
     61 # Silently give up if it's already full. Or truncate the string if there isn't enough room.
     62 trace:  # t : (address trace-stream), line : string
     63     # . prolog
     64     55/push-EBP
     65     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     66     # . save registers
     67     50/push-EAX
     68     51/push-ECX
     69     52/push-EDX
     70     53/push-EBX
     71     56/push-ESI
     72     57/push-EDI
     73     # EDI = t
     74     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
     75     # ESI = line
     76     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
     77     # ECX = t->write
     78     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
     79     # EDX = t->length
     80     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
     81     # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
     82     # . . push line
     83     56/push-ESI
     84     # . . push &t->data[t->length]
     85     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+EDX+12 to EBX
     86     53/push-EBX
     87     # . . push &t->data[t->write]
     88     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
     89     53/push-EBX
     90     # . . call
     91     e8/call  _append-3/disp32
     92     # . . discard args
     93     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     94     # if (EAX == 0) return
     95     3d/compare-EAX-and  0/imm32
     96     74/jump-if-equal  $trace:end/disp8
     97     # t->write += EAX
     98     01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
     99     # refresh ECX = t->write
    100     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
    101     # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
    102     # . . push line
    103     68/push  Newline/imm32
    104     # . . push &t->data[t->length]
    105     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+EDX+12 to EBX
    106     53/push-EBX
    107     # . . push &t->data[t->write]
    108     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
    109     53/push-EBX
    110     # . . call
    111     e8/call  _append-3/disp32
    112     # . . discard args
    113     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    114     # t->write += EAX
    115     01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
    116 $trace:end:
    117     # . restore registers
    118     5f/pop-to-EDI
    119     5e/pop-to-ESI
    120     5b/pop-to-EBX
    121     5a/pop-to-EDX
    122     59/pop-to-ECX
    123     58/pop-to-EAX
    124     # . epilog
    125     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    126     5d/pop-to-EBP
    127     c3/return
    128
    129 clear-trace-stream:  # t : (address trace-stream)
    130     # . prolog
    131     55/push-EBP
    132     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    133     # . save registers
    134     50/push-EAX
    135     51/push-ECX
    136     # EAX = t
    137     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
    138     # ECX = t->length
    139     8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EAX+8) to ECX
    140     # ECX = &t->data[t->length]
    141     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   0xc/disp8       .                 # copy EAX+ECX+12 to ECX
    142     # t->write = 0
    143     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
    144     # t->read = 0
    145     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         0/imm32           # copy to *(EAX+4)
    146     # EAX = t->data
    147     81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xc/imm32         # add to EAX
    148 $clear-trace-stream:loop:
    149     # if (EAX >= ECX) break
    150     39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
    151     7d/jump-if-greater-or-equal  $clear-trace-stream:end/disp8
    152     # *EAX = 0
    153     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
    154     # EAX += 4
    155     81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               4/imm32           # add to EAX
    156     eb/jump  $clear-trace-stream:loop/disp8
    157 $clear-trace-stream:end:
    158     # . restore registers
    159     59/pop-to-ECX
    160     58/pop-to-EAX
    161     # . epilog
    162     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    163     5d/pop-to-EBP
    164     c3/return
    165
    166 # - tests
    167
    168 test-trace-single:
    169     # clear-trace-stream(_test-trace-stream)
    170     # . . push args
    171     68/push  _test-trace-stream/imm32
    172     # . . call
    173     e8/call  clear-trace-stream/disp32
    174     # . . discard args
    175     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    176     # trace(_test-trace-stream, "Ab")
    177     # . . push args
    178     68/push  "Ab"/imm32
    179     68/push  _test-trace-stream/imm32
    180     # . . call
    181     e8/call  trace/disp32
    182     # . . discard args
    183     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    184     # check-ints-equal(*_test-trace-stream->data, 41/A 62/b 0a/newline 00, msg)
    185     # . . push args
    186     68/push  "F - test-trace-single"/imm32
    187     68/push  0x0a6241/imm32/Ab-newline
    188     # . . push *_test-trace-stream->data
    189     b8/copy-to-EAX  _test-trace-stream/imm32
    190     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    191     # . . call
    192     e8/call  check-ints-equal/disp32
    193     # . . discard args
    194     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    195     # end
    196     c3/return
    197
    198 test-trace-appends:
    199     # clear-trace-stream(_test-trace-stream)
    200     # . . push args
    201     68/push  _test-trace-stream/imm32
    202     # . . call
    203     e8/call  clear-trace-stream/disp32
    204     # . . discard args
    205     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    206     # trace(_test-trace-stream, "C")
    207     # . . push args
    208     68/push  "C"/imm32
    209     68/push  _test-trace-stream/imm32
    210     # . . call
    211     e8/call  trace/disp32
    212     # . . discard args
    213     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    214     # trace(_test-trace-stream, "D")
    215     # . . push args
    216     68/push  "D"/imm32
    217     68/push  _test-trace-stream/imm32
    218     # . . call
    219     e8/call  trace/disp32
    220     # . . discard args
    221     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    222     # check-ints-equal(*_test-trace-stream->data, 43/C 0a/newline 44/D 0a/newline, msg)
    223     # . . push args
    224     68/push  "F - test-trace-appends"/imm32
    225     68/push  0x0a440a43/imm32/C-newline-D-newline
    226     # . . push *_test-trace-stream->data
    227     b8/copy-to-EAX  _test-trace-stream/imm32
    228     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    229     # . . call
    230     e8/call  check-ints-equal/disp32
    231     # . . discard args
    232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    233     # end
    234     c3/return
    235
    236 test-trace-empty-line:
    237     # clear-trace-stream(_test-trace-stream)
    238     # . . push args
    239     68/push  _test-trace-stream/imm32
    240     # . . call
    241     e8/call  clear-trace-stream/disp32
    242     # . . discard args
    243     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    244     # trace(_test-trace-stream, "")
    245     # . . push args
    246     68/push  ""/imm32
    247     68/push  _test-trace-stream/imm32
    248     # . . call
    249     e8/call  trace/disp32
    250     # . . discard args
    251     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    252     # check-ints-equal(*_test-trace-stream->data, 0, msg)
    253     # . . push args
    254     68/push  "F - test-trace-empty-line"/imm32
    255     68/push  0/imm32
    256     # . . push *_test-trace-stream->data
    257     b8/copy-to-EAX  _test-trace-stream/imm32
    258     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    259     # . . call
    260     e8/call  check-ints-equal/disp32
    261     # . . discard args
    262     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    263     # end
    264     c3/return
    265
    266 # - helpers
    267
    268 # 3-argument variant of _append
    269 _append-3:  # out : address, outend : address, s : (array byte) -> num_bytes_appended/EAX
    270     # . prolog
    271     55/push-EBP
    272     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    273     # . save registers
    274     51/push-ECX
    275     # EAX = _append-4(out, outend, &s->data[0], &s->data[s->length])
    276     # . . push &s->data[s->length]
    277     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
    278     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    279     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    280     51/push-ECX
    281     # . . push &s->data[0]
    282     8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EAX+4 to ECX
    283     51/push-ECX
    284     # . . push outend
    285     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    286     # . . push out
    287     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    288     # . . call
    289     e8/call  _append-4/disp32
    290     # . . discard args
    291     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    292 $_append-3:end:
    293     # . restore registers
    294     59/pop-to-ECX
    295     # . epilog
    296     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    297     5d/pop-to-EBP
    298     c3/return
    299
    300 # 4-argument variant of _append
    301 _append-4:  # out : address, outend : address, in : address, inend : address -> num_bytes_appended/EAX
    302     # . prolog
    303     55/push-EBP
    304     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    305     # . save registers
    306     51/push-ECX
    307     52/push-EDX
    308     53/push-EBX
    309     56/push-ESI
    310     57/push-EDI
    311     # EAX/num_bytes_appended = 0
    312     b8/copy-to-EAX  0/imm32
    313     # EDI = out
    314     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
    315     # EDX = outend
    316     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
    317     # ESI = in
    318     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0x10/disp8      .                 # copy *(EBP+16) to ESI
    319     # ECX = inend
    320     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x14/disp8      .                 # copy *(EBP+20) to ECX
    321 $_append-4:loop:
    322     # if (in >= inend) break
    323     39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
    324     7d/jump-if-greater-or-equal  $_append-4:end/disp8
    325     # if (out >= outend) abort  # just to catch test failures fast
    326     39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI with EDX
    327     7d/jump-if-greater-or-equal  $_append-4:abort/disp8
    328     # *out = *in
    329     8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
    330     88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *EDI
    331     # ++num_bytes_appended
    332     40/increment-EAX
    333     # ++in
    334     46/increment-ESI
    335     # ++out
    336     47/increment-EDI
    337     eb/jump  $_append-4:loop/disp8
    338 $_append-4:end:
    339     # . restore registers
    340     5f/pop-to-EDI
    341     5e/pop-to-ESI
    342     5b/pop-to-EBX
    343     5a/pop-to-EDX
    344     59/pop-to-ECX
    345     # . epilog
    346     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    347     5d/pop-to-EBP
    348     c3/return
    349
    350 $_append-4:abort:
    351     # . _write(2/stderr, error)
    352     # . . push args
    353     68/push  "stream overflow"/imm32
    354     68/push  2/imm32/stderr
    355     # . . call
    356     e8/call  _write/disp32
    357     # . . discard args
    358     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    359     # . syscall(exit, 1)
    360     bb/copy-to-EBX  1/imm32
    361     b8/copy-to-EAX  1/imm32/exit
    362     cd/syscall  0x80/imm8
    363     # never gets here
    364
    365 # . . vim:nowrap:textwidth=0