https://github.com/akkartik/mu/blob/master/subx/apps/handle.subx
  1 # A sketch of Mu-style handles or kinda-safe pointers, that add a modicum of
  2 # checking to dynamically allocated memory.
  3 #
  4 # This approach avoids using 'allocate' directly in favor of two primitives:
  5 #   - 'new', which allocates some space (the 'payload'), stores the address
  6 #     along with an opaque 'alloc id' in a 'handle', and prepends the same
  7 #     alloc id to the payload.
  8 #   - 'lookup', which checks that the alloc id at the start of a handle matches
  9 #     the alloc id at the start of the payload before returning the address.
 10 #
 11 # Layout of a handle:
 12 #   offset 0: alloc id
 13 #   offset 4: address
 14 #
 15 # To run (from the subx directory):
 16 #   $ ./subx translate *.subx apps/handle.subx -o apps/handle
 17 #   $ ./subx run apps/handle
 18 # Expected result is a hard abort:
 19 #   ........lookup failed
 20 # (This file is a prototype, so the tests in this file aren't real tests. Don't
 21 # expect to run anything in the same process after they've completed.)
 22 
 23 == code
 24 #   instruction                     effective address                                                   register    displacement    immediate
 25 # . op          subop               mod             rm32          base        index         scale       r32
 26 # . 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
 27 
 28 # main:
 29     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 30     # syscall(exit, Num-test-failures)
 31     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 32     b8/copy-to-EAX  1/imm32/exit
 33     cd/syscall  0x80/imm8
 34 
 35 new:  # ad : (address allocation-descriptor), n : int, out : (address handle)
 36     # . prolog
 37     55/push-EBP
 38     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 39     # . save registers
 40     50/push-EAX
 41     51/push-ECX
 42     52/push-EDX
 43     # ECX = n+4
 44     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy *(EBP+12) to ECX
 45     81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm32           # add to ECX
 46     # EAX = allocate(ad, ECX)
 47     # . . push args
 48     51/push-ECX
 49     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 50     # . . call
 51     e8/call  allocate/disp32
 52     # . . discard args
 53     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 54     # EDX = out
 55     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
 56     # out->address = EAX
 57     89/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDX+4)
 58     # if (EAX == 0) out->alloc_id = 0, return
 59     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 60     75/jump-if-not-equal  $new:continue/disp8
 61     c7          0/subop/copy        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               0/imm32           # copy to *EDX
 62     eb/jump  $new:end/disp8
 63 $new:continue:
 64     # otherwise:
 65     # ECX = *Next-alloc-id
 66     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           1/r32/ECX   Next-alloc-id/disp32              # copy *Next-alloc-id to ECX
 67     # *EAX = *Next-alloc-id/ECX
 68     89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
 69     # out->alloc_id = *Next-alloc-id
 70     89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDX
 71     # increment *Next-alloc-id
 72     ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # increment *Next-alloc-id
 73 $new:end:
 74     # . restore registers
 75     5a/pop-to-EDX
 76     59/pop-to-ECX
 77     58/pop-to-EAX
 78     # . epilog
 79     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 80     5d/pop-to-EBP
 81     c3/return
 82 
 83 test-new:
 84     # . prolog
 85     55/push-EBP
 86     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 87     # *Next-alloc-id = 0x34
 88     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  0x34/imm32        # copy to *Next-alloc-id
 89     # var handle/ECX = {0, 0}
 90     68/push  0/imm32/address
 91     68/push  0/imm32/alloc-id
 92     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 93     # new(Heap, 2, handle/ECX)
 94     # . . push args
 95     51/push-ECX
 96     68/push  2/imm32/size
 97     68/push  Heap/imm32
 98     # . . call
 99     e8/call  new/disp32
100     # . . discard args
101     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
102     # check-ints-equal(handle->alloc_id, 0x34, msg)
103     # . . push args
104     68/push  "F - test-new: alloc id of handle"/imm32
105     68/push  0x34/imm32
106     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
107     # . . call
108     e8/call  check-ints-equal/disp32
109     # . . discard args
110     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
111     # check-ints-equal(*handle->address, 0x34, msg)
112     # . . push args
113     68/push  "F - test-new: alloc id of payload"/imm32
114     68/push  0x34/imm32
115     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
116     ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
117     # . . call
118     e8/call  check-ints-equal/disp32
119     # . . discard args
120     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
121     # check-ints-equal(*Next-alloc-id, 0x35)
122     # . . push args
123     68/push  "F - test-new: next alloc id"/imm32
124     68/push  0x35/imm32
125     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # copy to *Next-alloc-id
126     # . . call
127     e8/call  check-ints-equal/disp32
128     # . . discard args
129     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
130     # clean up
131     # . *Next-alloc-id = 1
132     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
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 test-new-failure:
139     # . prolog
140     55/push-EBP
141     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
142     # . *Next-alloc-id = 0x34
143     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
144     # define an allocation-descriptor with no space left
145     # . var ad/EAX : (address allocation-descriptor) = {0x10, 0x10}
146     68/push  0x10/imm32/limit
147     68/push  0x10/imm32/curr
148     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
149     # . var handle/ECX = {random, random}
150     68/push  1234/imm32/address
151     68/push  5678/imm32/alloc-id
152     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
153     # try to allocate
154     # . new(ad, 2, handle/ECX)
155     # . . push args
156     51/push-ECX
157     68/push  2/imm32/size
158     50/push-EAX
159     # . . call
160     e8/call  new/disp32
161     # . . discard args
162     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
163     # handle should be cleared
164     # . check-ints-equal(handle->alloc_id, 0, msg)
165     # . . push args
166     68/push  "F - test-new-failure: alloc id of handle"/imm32
167     68/push  0/imm32
168     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
169     # . . call
170     e8/call  check-ints-equal/disp32
171     # . . discard args
172     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
173     # . check-ints-equal(handle->address, 0, msg)
174     # . . push args
175     68/push  "F - test-new-failure: address of handle"/imm32
176     68/push  0/imm32
177     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
178     # . . call
179     e8/call  check-ints-equal/disp32
180     # . . discard args
181     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
182     # Next-alloc-id should be unmodified
183     # . check-ints-equal(*Next-alloc-id, 0x34)
184     # . . push args
185     68/push  "F - test-new-failure: next alloc id"/imm32
186     68/push  0x34/imm32
187     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # copy to *Next-alloc-id
188     # . . call
189     e8/call  check-ints-equal/disp32
190     # . . discard args
191     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
192     # clean up
193     # . *Next-alloc-id = 1
194     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
195     # . epilog
196     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
197     5d/pop-to-EBP
198     c3/return
199 
200 lookup:  # h : (handle T) -> EAX : (address T)
201     # . prolog
202     55/push-EBP
203     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
204     # . save registers
205     51/push-ECX
206     52/push-EDX
207     # ECX = handle
208     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
209     # EDX = handle->alloc_id
210     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
211     # EAX = handle->address
212     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
213     # if (*EAX == EDX) return
214     39/compare                      0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare *EAX and EDX
215     74/jump-if-equal  $lookup:success/disp8
216     # otherwise print a message and exit
217     # hard-coded abort just to minimize args and simplify calls
218     # TODO: emit stack trace, etc.
219     # . _write(2/stderr, msg)
220     # . . push args
221     68/push  "lookup failed"/imm32
222     68/push  2/imm32/stderr
223     # . . call
224     e8/call  _write/disp32
225     # . . discard args
226     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
227     # . syscall(exit, 1)
228     bb/copy-to-EBX  1/imm32/exit-status
229     b8/copy-to-EAX  1/imm32/exit
230     cd/syscall  0x80/imm8
231 $lookup:success:
232     # increment past payload alloc id
233     05/add-to-EAX  4/imm32
234     # . restore registers
235     5a/pop-to-EDX
236     59/pop-to-ECX
237     # . epilog
238     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
239     5d/pop-to-EBP
240     c3/return
241 
242 test-lookup-success:
243     # . prolog
244     55/push-EBP
245     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
246     # . save registers
247     # var handle/ECX = {0, 0}
248     68/push  0/imm32/address
249     68/push  0/imm32/alloc-id
250     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
251     # var old_top/EDX = Heap->curr
252     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           2/r32/EDX   Heap/disp32     .                 # copy *Heap to EDX
253     # new(Heap, 2, handle)
254     # . . push args
255     51/push-ECX
256     68/push  2/imm32/size
257     68/push  Heap/imm32
258     # . . call
259     e8/call  new/disp32
260     # . . discard args
261     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
262     # EAX = lookup(handle)
263     # . . push args
264     51/push-ECX
265     # . . call
266     e8/call  lookup/disp32
267     # . . discard args
268     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
269     # EAX contains old top of Heap, except skipping the alloc id in the payload
270     # . check-ints-equal(EAX, old_top+4, msg)
271     # . . push args
272     68/push  "F - test-lookup-success"/imm32
273     81          0/subop/add         3/mod/direct    2/rm32/EDX    .           .             .           .           .               4/imm32           # add to EDX
274     52/push-EDX
275     50/push-EAX
276     # . . call
277     e8/call  check-ints-equal/disp32
278     # . . discard args
279     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
280     # clean up
281     # . *Next-alloc-id = 1
282     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
283     # . restore registers
284     5a/pop-to-EDX
285     59/pop-to-ECX
286     # . epilog
287     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
288     5d/pop-to-EBP
289     c3/return
290 
291 test-lookup-failure:
292     # . prolog
293     55/push-EBP
294     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
295     # . save registers
296     50/push-EAX
297     51/push-ECX
298     52/push-EDX
299     # var h1/ECX = {0, 0}
300     68/push  0/imm32/address
301     68/push  0/imm32/alloc-id
302     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
303     # var old_top/EBX = Heap->curr
304     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Heap/disp32     .                 # copy *Heap to EBX
305     # first allocation, to h1
306     # . new(Heap, 2, h1)
307     # . . push args
308     51/push-ECX
309     68/push  2/imm32/size
310     68/push  Heap/imm32
311     # . . call
312     e8/call  new/disp32
313     # . . discard args
314     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
315     # reset Heap->curr to mimic reclamation
316     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Heap/disp32     .                 # copy EBX to *Heap
317     # second allocation that returns the same address as the first
318     # var h2/EDX = {0, 0}
319     68/push  0/imm32/address
320     68/push  0/imm32/alloc-id
321     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
322     # . new(Heap, 2, h2)
323     # . . push args
324     52/push-EDX
325     68/push  2/imm32/size
326     68/push  Heap/imm32
327     # . . call
328     e8/call  new/disp32
329     # . . discard args
330     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
331     # check-ints-equal(h1->address, h2->address, msg)
332     # . . push args
333     68/push  "F - test-lookup-failure"/imm32
334     ff          6/subop/push        1/mod/*+disp8   2/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
335     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
336     # . . call
337     e8/call  check-ints-equal/disp32
338     # . . discard args
339     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
340     # lookup(h1) should crash
341     # . . push args
342     51/push-ECX
343     # . . call
344     e8/call  lookup/disp32
345     # should never get past this point
346     # . . discard args
347     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
348     # clean up
349     # . *Next-alloc-id = 1
350     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
351     # . restore registers
352     5a/pop-to-EDX
353     59/pop-to-ECX
354     58/pop-to-EAX
355     # . epilog
356     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
357     5d/pop-to-EBP
358     c3/return
359 
360 == data
361 
362 # Monotonically increasing counter for calls to 'new'
363 Next-alloc-id:
364     01 00 00 00  # 1
365 
366 # . . vim:nowrap:textwidth=0