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