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 $clear-trace-stream:loop:
157     # if (EAX >= ECX) break
158     39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
159     7d/jump-if-greater-or-equal  $clear-trace-stream:end/disp8
160     # *EAX = 0
161     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
162     # EAX += 4
163     81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               4/imm32           # add to EAX
164     eb/jump  $clear-trace-stream:loop/disp8
165 $clear-trace-stream:end:
166     # . restore registers
167     59/pop-to-ECX
168     58/pop-to-EAX
169     # . epilog
170     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
171     5d/pop-to-EBP
172     c3/return
173 
174 # - tests
175 
176 test-trace-single:
177     # clear-trace-stream(_test-trace-stream)
178     # . . push args
179     68/push  _test-trace-stream/imm32
180     # . . call
181     e8/call  clear-trace-stream/disp32
182     # . . discard args
183     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
184     # trace(_test-trace-stream, "Ab")
185     # . . push args
186     68/push  "Ab"/imm32
187     68/push  _test-trace-stream/imm32
188     # . . call
189     e8/call  trace/disp32
190     # . . discard args
191     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
192     # check-ints-equal(*_test-trace-stream->data, 41/A 62/b 0a/newline 00, msg)
193     # . . push args
194     68/push  "F - test-trace-single"/imm32
195     68/push  0x0a6241/imm32/Ab-newline
196     # . . push *_test-trace-stream->data
197     b8/copy-to-EAX  _test-trace-stream/imm32
198     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
199     # . . call
200     e8/call  check-ints-equal/disp32
201     # . . discard args
202     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
203     # end
204     c3/return
205 
206 test-trace-appends:
207     # clear-trace-stream(_test-trace-stream)
208     # . . push args
209     68/push  _test-trace-stream/imm32
210     # . . call
211     e8/call  clear-trace-stream/disp32
212     # . . discard args
213     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
214     # trace(_test-trace-stream, "C")
215     # . . push args
216     68/push  "C"/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     # trace(_test-trace-stream, "D")
223     # . . push args
224     68/push  "D"/imm32
225     68/push  _test-trace-stream/imm32
226     # . . call
227     e8/call  trace/disp32
228     # . . discard args
229     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
230     # check-ints-equal(*_test-trace-stream->data, 43/C 0a/newline 44/D 0a/newline, msg)
231     # . . push args
232     68/push  "F - test-trace-appends"/imm32
233     68/push  0x0a440a43/imm32/C-newline-D-newline
234     # . . push *_test-trace-stream->data
235     b8/copy-to-EAX  _test-trace-stream/imm32
236     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
237     # . . call
238     e8/call  check-ints-equal/disp32
239     # . . discard args
240     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
241     # end
242     c3/return
243 
244 test-trace-empty-line:
245     # clear-trace-stream(_test-trace-stream)
246     # . . push args
247     68/push  _test-trace-stream/imm32
248     # . . call
249     e8/call  clear-trace-stream/disp32
250     # . . discard args
251     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
252     # trace(_test-trace-stream, "")
253     # . . push args
254     68/push  ""/imm32
255     68/push  _test-trace-stream/imm32
256     # . . call
257     e8/call  trace/disp32
258     # . . discard args
259     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
260     # check-ints-equal(*_test-trace-stream->data, 0, msg)
261     # . . push args
262     68/push  "F - test-trace-empty-line"/imm32
263     68/push  0/imm32
264     # . . push *_test-trace-stream->data
265     b8/copy-to-EAX  _test-trace-stream/imm32
266     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
267     # . . call
268     e8/call  check-ints-equal/disp32
269     # . . discard args
270     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
271     # end
272     c3/return
273 
274 # - helpers
275 
276 # 3-argument variant of _append
277 _append-3:  # out : address, outend : address, s : (array byte) -> num_bytes_appended/EAX
278     # . prolog
279     55/push-EBP
280     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
281     # . save registers
282     51/push-ECX
283     # _append-4(out, outend, &s->data[0], &s->data[s->length]) -> num_bytes_appended/EAX
284     # . . push &s->data[s->length]
285     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
286     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
287     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
288     51/push-ECX
289     # . . push &s->data[0]
290     8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EAX+4 to ECX
291     51/push-ECX
292     # . . push outend
293     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
294     # . . push out
295     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
296     # . . call
297     e8/call  _append-4/disp32
298     # . . discard args
299     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
300 $_append-3:end:
301     # . restore registers
302     59/pop-to-ECX
303     # . epilog
304     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
305     5d/pop-to-EBP
306     c3/return
307 
308 # 4-argument variant of _append
309 _append-4:  # out : address, outend : address, in : address, inend : address -> num_bytes_appended/EAX
310     # . prolog
311     55/push-EBP
312     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
313     # . save registers
314     51/push-ECX
315     52/push-EDX
316     53/push-EBX
317     56/push-ESI
318     57/push-EDI
319     # EAX/num_bytes_appended = 0
320     b8/copy-to-EAX  0/imm32
321     # EDI = out
322     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
323     # EDX = outend
324     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
325     # ESI = in
326     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0x10/disp8      .                 # copy *(EBP+16) to ESI
327     # ECX = inend
328     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x14/disp8      .                 # copy *(EBP+20) to ECX
329 $_append-4:loop:
330     # if (in >= inend) break
331     39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
332     7d/jump-if-greater-or-equal  $_append-4:end/disp8
333     # if (out >= outend) break  # for now silently ignore filled up buffer
334     39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI with EDX
335     7d/jump-if-greater-or-equal  $_append-4:end/disp8
336     # *out = *in
337     8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
338     88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *EDI
339     # ++num_bytes_appended
340     40/increment-EAX
341     # ++in
342     46/increment-ESI
343     # ++out
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