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                                                   operand     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   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
 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/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   4/rm32/sib    5/base/EBP  4/index/none              7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
 83   # ESI = line
 84   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none              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   4/rm32/sib    5/base/EBP  4/index/none              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/copy                         0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
152   # t->read = 0
153   c7/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/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       # EAX = s
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       # ECX = s.length
289   8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
290       # ECX = &s.data[s.length]
291   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
292   51/push-ECX
293     # push &s.data[0]
294   8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EAX+4 to ECX
295   51/push-ECX
296     # push outend
297   ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
298     # push out
299   ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x8/disp8       .                 # push *(EBP+8)
300     # call
301   e8/call  _append-4/disp32
302     # discard args
303   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
304   # restore registers
305   59/pop-to-ECX
306   # epilog
307   89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
308   5d/pop-to-EBP
309   c3/return
310 
311 # 4-argument variant of _append
312 _append-4:  # out : address, outend : address, in : address, inend : address -> num_bytes_appended/EAX
313   # prolog
314   55/push-EBP
315   89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
316   # save registers
317   51/push-ECX
318   52/push-EDX
319   53/push-EBX
320   56/push-ESI
321   57/push-EDI
322   # EAX/num_bytes_appended = 0
323   b8/copy-to-EAX  0/imm32
324   # EDI = out
325   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           7/r32/EDI   0x8/disp8       .                 # copy *(EBP+8) to EDI
326   # EDX = outend
327   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
328   # ESI = in
329   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           6/r32/ESI   0x10/disp8      .                 # copy *(EBP+16) to ESI
330   # ECX = inend
331   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           1/r32/ECX   0x14/disp8      .                 # copy *(EBP+20) to ECX
332 $_append-4:loop:
333   # if ESI/src >= ECX/srcend break
334   39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
335   7d/jump-if-greater-or-equal  $_append-4:end/disp8
336   # if EDI/out >= EDX/outend break  (for now silently ignore filled up buffer)
337   39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # compare EDI with EDX
338   7d/jump-if-greater-or-equal  $_append-4:end/disp8
339   # copy one byte from ESI/src to EDI/out
340   8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/BL    .               .                 # copy byte at *ESI to BL
341   88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *EDI
342   # updates
343   40/increment-EAX
344   46/increment-ESI
345   47/increment-EDI
346   eb/jump  $_append-4:loop/disp8
347 $_append-4:end:
348   # restore registers
349   5f/pop-to-EDI
350   5e/pop-to-ESI
351   5b/pop-to-EBX
352   5a/pop-to-EDX
353   59/pop-to-ECX
354   # epilog
355   89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
356   5d/pop-to-EBP
357   c3/return
358 
359 # vim:nowrap:textwidth=0