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