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     00 00 00 00
 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     00 00 00 00
 32     # current read index
 33     00 00 00 00
 34     # length (= 8)
 35     08 00 00 00
 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 # main:
 45     # run-tests()
 46     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 47     # syscall(exit, Num-test-failures)
 48     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 49     b8/copy-to-EAX  1/imm32/exit
 50     cd/syscall  0x80/imm8
 51 
 52 # Allocate a new segment for the trace stream, initialize its length, and save its address to Trace-stream.
 53 # The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
 54 initialize-trace-stream:
 55     # EAX = new-segment(0x1000)
 56     # . . push args
 57     68/push  0x1000/imm32/N
 58     # . . call
 59     e8/call  new-segment/disp32
 60     # . . discard args
 61     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 62     # copy EAX to *Trace-stream
 63     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Trace-stream/disp32               # copy EAX to *Trace-stream
 64     # Trace-stream->length = 0x1000/N - 12
 65     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         0xff4/imm32       # copy 0xff4 to *(EAX+8)
 66     c3/return
 67 
 68 # Append a string to the given trace stream.
 69 # Silently give up if it's already full. Or truncate the string if there isn't enough room.
 70 trace:  # t : (address trace-stream), line : string
 71     # . prolog
 72     55/push-EBP
 73     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 74     # . save registers
 75     50/push-EAX
 76     51/push-ECX
 77     52/push-EDX
 78     53/push-EBX
 79     56/push-ESI
 80     57/push-EDI
 81     # EDI = t
 82     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
 83     # ESI = line
 84     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
 85     # ECX = t->write
 86     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
 87     # EDX = t->length
 88     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
 89     # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
 90     # . . push line
 91     56/push-ESI
 92     # . . push &t->data[t->length]
 93     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
 94     53/push-EBX
 95     # . . push &t->data[t->write]
 96     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
 97     53/push-EBX
 98     # . . call
 99     e8/call  _append-3/disp32
100     # . . discard args
101     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
102     # if EAX == 0 return
103     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EDX
104     74/jump-if-equal  $trace:end/disp8
105     # t->write += EAX
106     01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
107     # refresh ECX = t->write
108     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
109     # EAX = _append-3(&t->data[t->write], &t->data[t->length], line)
110     # . . push line
111     68/push  Newline/imm32
112     # . . push &t->data[t->length]
113     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
114     53/push-EBX
115     # . . push &t->data[t->write]
116     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
117     53/push-EBX
118     # . . call
119     e8/call  _append-3/disp32
120     # . . discard args
121     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
122     # t->write += EAX
123     01/add                          0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to *EDI
124 $trace:end:
125     # . restore registers
126     5f/pop-to-EDI
127     5e/pop-to-ESI
128     5b/pop-to-EBX
129     5a/pop-to-EDX
130     59/pop-to-ECX
131     58/pop-to-EAX
132     # . epilog
133     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
134     5d/pop-to-EBP
135     c3/return
136 
137 clear-trace-stream:  # t : (address trace-stream)
138     # . prolog
139     55/push-EBP
140     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
141     # . save registers
142     50/push-EAX
143     51/push-ECX
144     # EAX = t
145     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
146     # ECX = t->length
147     8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EAX+8) to ECX
148     # ECX = &t->data[t->length]
149     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
150     # t->write = 0
151     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
152     # t->read = 0
153     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         0/imm32           # copy to *(EAX+4)
154     # EAX = t->data
155     81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xc/imm32         # add to EAX
156     # while (true)
157 $clear-trace-stream:loop:
158     # if EAX >= ECX break
159     39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
160     7d/jump-if-greater-or-equal  $clear-trace-stream:end/disp8
161     # *EAX = 0
162     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
163     # EAX += 4
164     81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               4/imm32           # add to EAX
165     eb/jump  $clear-trace-stream:loop/disp8
166 $clear-trace-stream:end:
167     # . restore registers
168     59/pop-to-ECX
169     58/pop-to-EAX
170     # . epilog
171     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
172     5d/pop-to-EBP
173     c3/return
174 
175 # - tests
176 
177 test-trace-single:
178     # clear-trace-stream(_test-trace-stream)
179     # . . push args
180     68/push  _test-trace-stream/imm32
181     # . . call
182     e8/call  clear-trace-stream/disp32
183     # . . discard args
184     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
185     # trace(_test-trace-stream, "Ab")
186     # . . push args
187     68/push  "Ab"/imm32
188     68/push  _test-trace-stream/imm32
189     # . . call
190     e8/call  trace/disp32
191     # . . discard args
192     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
193     # check-ints-equal(*_test-trace-stream->data, 41/A 62/b 0a/newline 00, msg)
194     # . . push args
195     68/push  "F - test-trace-single"/imm32
196     68/push  0x0a6241/imm32/Ab-newline
197     # . . push *_test-trace-stream->data
198     b8/copy-to-EAX  _test-trace-stream/imm32
199     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
200     # . . call
201     e8/call  check-ints-equal/disp32
202     # . . discard args
203     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
204     # end
205     c3/return
206 
207 test-trace-appends:
208     # clear-trace-stream(_test-trace-stream)
209     # . . push args
210     68/push  _test-trace-stream/imm32
211     # . . call
212     e8/call  clear-trace-stream/disp32
213     # . . discard args
214     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
215     # trace(_test-trace-stream, "C")
216     # . . push args
217     68/push  "C"/imm32
218     68/push  _test-trace-stream/imm32
219     # . . call
220     e8/call  trace/disp32
221     # . . discard args
222     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
223     # trace(_test-trace-stream, "D")
224     # . . push args
225     68/push  "D"/imm32
226     68/push  _test-trace-stream/imm32
227     # . . call
228     e8/call  trace/disp32
229     # . . discard args
230     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
231     # check-ints-equal(*_test-trace-stream->data, 43/C 0a/newline 44/D 0a/newline, msg)
232     # . . push args
233     68/push  "F - test-trace-appends"/imm32
234     68/push  0x0a440a43/imm32/C-newline-D-newline
235     # . . push *_test-trace-stream->data
236     b8/copy-to-EAX  _test-trace-stream/imm32
237     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
238     # . . call
239     e8/call  check-ints-equal/disp32
240     # . . discard args
241     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
242     # end
243     c3/return
244 
245 test-trace-empty-line:
246     # clear-trace-stream(_test-trace-stream)
247     # . . push args
248     68/push  _test-trace-stream/imm32
249     # . . call
250     e8/call  clear-trace-stream/disp32
251     # . . discard args
252     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
253     # trace(_test-trace-stream, "")
254     # . . push args
255     68/push  ""/imm32
256     68/push  _test-trace-stream/imm32
257     # . . call
258     e8/call  trace/disp32
259     # . . discard args
260     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
261     # check-ints-equal(*_test-trace-stream->data, 0, msg)
262     # . . push args
263     68/push  "F - test-trace-empty-line"/imm32
264     68/push  0/imm32
265     # . . push *_test-trace-stream->data
266     b8/copy-to-EAX  _test-trace-stream/imm32
267     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
268     # . . call
269     e8/call  check-ints-equal/disp32
270     # . . discard args
271     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
272     # end
273     c3/return
274 
275 # - helpers
276 
277 # 3-argument variant of _append
278 _append-3:  # out : address, outend : address, s : (array byte) -> num_bytes_appended/EAX
279     # . prolog
280     55/push-EBP
281     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
282     # . save registers
283     51/push-ECX
284     # _append-4(out, outend, &s->data[0], &s->data[s->length]) -> num_bytes_appended/EAX
285     # . . push &s->data[s->length]
286     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
287     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
288     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
289     51/push-ECX
290     # . . push &s->data[0]
291     8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EAX+4 to ECX
292     51/push-ECX
293     # . . push outend
294     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
295     # . . push out
296     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
297     # . . call
298     e8/call  _append-4/disp32
299     # . . discard args
300     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
301 $_append-3:end:
302     # . restore registers
303     59/pop-to-ECX
304     # . epilog
305     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
306     5d/pop-to-EBP
307     c3/return
308 
309 # 4-argument variant of _append
310 _append-4:  # out : address, outend : address, in : address, inend : address -> num_bytes_appended/EAX
311     # . prolog
312     55/push-EBP
313     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
314     # . save registers
315     51/push-ECX
316     52/push-EDX
317     53/push-EBX
318     56/push-ESI
319     57/push-EDI
320     # EAX/num_bytes_appended = 0
321     b8/copy-to-EAX  0/imm32
322     # EDI = out
323     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
324     # EDX = outend
325     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
326     # ESI = in
327     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0x10/disp8      .                 # copy *(EBP+16) to ESI
328     # ECX = inend
329     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x14/disp8      .                 # copy *(EBP+20) to ECX
330 $_append-4:loop:
331     # if ESI/in >= ECX/inend break
332     39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
333     7d/jump-if-greater-or-equal  $_append-4:end/disp8
334     # if EDI/out >= EDX/outend break  (for now silently ignore filled up buffer)
335     39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI with EDX
336     7d/jump-if-greater-or-equal  $_append-4:end/disp8
337     # copy one byte from ESI/in to EDI/out
338     8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
339     88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *EDI
340     # updates
341     40/increment-EAX
342     46/increment-ESI
343     47/increment-EDI
344     eb/jump  $_append-4:loop/disp8
345 $_append-4:end:
346     # . restore registers
347     5f/pop-to-EDI
348     5e/pop-to-ESI
349     5b/pop-to-EBX
350     5a/pop-to-EDX
351     59/pop-to-ECX
352     # . epilog
353     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
354     5d/pop-to-EBP
355     c3/return
356 
357 # . . vim:nowrap:textwidth=0