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\n"/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