# A sketch of Mu-style handles or kinda-safe pointers, that add a modicum of
# checking to dynamically allocated memory.
#
# This approach avoids using 'allocate' directly in favor of two primitives:
# - 'new', which allocates some space (the 'payload'), stores the address
# along with an opaque 'alloc id' in a 'handle', and prepends the same
# alloc id to the payload.
# - 'lookup', which checks that the alloc id at the start of a handle matches
# the alloc id at the start of the payload before returning the address.
#
# Layout of a handle:
# offset 0: alloc id
# offset 4: address
#
# To run (from the subx directory):
# $ ./subx translate *.subx apps/handle.subx -o apps/handle
# $ ./subx run apps/handle
# Expected result is a successful lookup followed by a hard abort:
# lookup succeeded
# lookup failed
# (This file is a prototype. The 'tests' in it aren't real; failures are
# expected.)
== code
# instruction effective address register displacement immediate
# . op subop mod rm32 base index scale r32
# . 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
# no Entry; the standard library runs all tests by default
new: # ad : (address allocation-descriptor), n : int, out : (address handle)
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
50/push-EAX
51/push-ECX
52/push-EDX
# ECX = n+4
8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX
81 0/subop/add 3/mod/direct 1/rm32/ECX . . . . . 4/imm32 # add to ECX
# EAX = allocate(ad, ECX)
# . . push args
51/push-ECX
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call allocate/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EDX = out
8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX
# out->address = EAX
89/copy 1/mod/*+disp8 2/rm32/EDX . . . 0/r32/EAX 4/disp8 . # copy EAX to *(EDX+4)
# if (EAX == 0) out->alloc_id = 0, return
3d/compare-EAX-and 0/imm32
75/jump-if-not-equal $new:continue/disp8
c7 0/subop/copy 0/mod/indirect 2/rm32/EDX . . . . . 0/imm32 # copy to *EDX
eb/jump $new:end/disp8
$new:continue:
# otherwise:
# ECX = *Next-alloc-id
8b/copy 0/mod/indirect 5/rm32/.disp32 . . 1/r32/ECX Next-alloc-id/disp32 # copy *Next-alloc-id to ECX
# *EAX = *Next-alloc-id/ECX
89/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy ECX to *EAX
# out->alloc_id = *Next-alloc-id
89/copy 0/mod/indirect 2/rm32/EDX . . . 1/r32/ECX . . # copy ECX to *EDX
# increment *Next-alloc-id
ff 0/subop/increment 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 # increment *Next-alloc-id
$new:end:
# . restore registers
5a/pop-to-EDX
59/pop-to-ECX
58/pop-to-EAX
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-new:
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# var heap/EDX : (address allocation-descriptor) = {0, 0}
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 2/rm32/EDX . . . 4/r32/ESP . . # copy ESP to EDX
# heap = new-segment(512)
# . . push args
52/push-EDX
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# *Next-alloc-id = 0x34
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 0x34/imm32 # copy to *Next-alloc-id
# var handle/ECX = {0, 0}
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# new(heap, 2, handle/ECX)
# . . push args
51/push-ECX
68/push 2/imm32/size
52/push-EDX
# . . call
e8/call new/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# check-ints-equal(handle->alloc_id, 0x34, msg)
# . . push args
68/push "F - test-new: alloc id of handle"/imm32
68/push 0x34/imm32
ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# check-ints-equal(*handle->address, 0x34, msg)
# . . push args
68/push "F - test-new: alloc id of payload"/imm32
68/push 0x34/imm32
8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # copy *(ECX+4) to EDX
ff 6/subop/push 0/mod/indirect 2/rm32/EDX . . . . . . # push *EDX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# check-ints-equal(*Next-alloc-id, 0x35)
# . . push args
68/push "F - test-new: next alloc id"/imm32
68/push 0x35/imm32
ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 # copy to *Next-alloc-id
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# clean up
# . *Next-alloc-id = 1
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 1/imm32 # copy to *Next-alloc-id
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-new-failure:
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . *Next-alloc-id = 0x34
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 0x34/imm32 # copy to *Next-alloc-id
# define an allocation-descriptor with no space left
# . var ad/EAX : (address allocation-descriptor) = {0x10, 0x10}
68/push 0x10/imm32/limit
68/push 0x10/imm32/curr
89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX
# . var handle/ECX = {random, random}
68/push 1234/imm32/address
68/push 5678/imm32/alloc-id
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# try to allocate
# . new(ad, 2, handle/ECX)
# . . push args
51/push-ECX
68/push 2/imm32/size
50/push-EAX
# . . call
e8/call new/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# handle should be cleared
# . check-ints-equal(handle->alloc_id, 0, msg)
# . . push args
68/push "F - test-new-failure: alloc id of handle"/imm32
68/push 0/imm32
ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# . check-ints-equal(handle->address, 0, msg)
# . . push args
68/push "F - test-new-failure: address of handle"/imm32
68/push 0/imm32
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# Next-alloc-id should be unmodified
# . check-ints-equal(*Next-alloc-id, 0x34)
# . . push args
68/push "F - test-new-failure: next alloc id"/imm32
68/push 0x34/imm32
ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 # copy to *Next-alloc-id
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# clean up
# . *Next-alloc-id = 1
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 1/imm32 # copy to *Next-alloc-id
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
lookup: # h : (handle T) -> EAX : (address T)
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - as a proof of concept for future inlining, uses no general-purpose registers besides the output (EAX)
# EAX = handle
8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX
# - inline {
# push handle->alloc_id
ff 6/subop/push 0/mod/indirect 0/rm32/EAX . . . . . . # push *EAX
# EAX = handle->address (payload)
8b/copy 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # copy *(EAX+4) to EAX
# push handle->address
50/push-EAX
# EAX = payload->alloc_id
8b/copy 0/mod/indirect 0/rm32/EAX . . . . . . # copy *EAX to EAX
# if (EAX != handle->alloc_id) abort
39/compare 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 0/r32/EAX 4/disp8 . # compare *(ESP+4) and EAX
75/jump-if-not-equal $lookup:abort/disp8
# EAX = pop handle->address
58/pop-to-EAX
# discard handle->alloc_id
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# add 4
05/add-to-EAX 4/imm32
# - }
# - alternative consuming a second register {
#? # ECX = handle->alloc_id
#? 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX
#? # EAX = handle->address (payload)
#? 8b/copy 1/mod/*+disp8 0/rm32/EAX . . . 0/r32/EAX 4/disp8 . # copy *(EAX+4) to EAX
#? # if (ECX != *EAX) abort
#? 39/compare 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # compare *EAX and ECX
#? 75/jump-if-not-equal $lookup:abort/disp8
#? # add 4 to EAX
#? 05/add-to-EAX 4/imm32
# - }
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
$lookup:abort:
# . _write(2/stderr, msg)
# . . push args
68/push "lookup failed\n"/imm32
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . syscall(exit, 1)
bb/copy-to-EBX 1/imm32/exit-status
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
test-lookup-success:
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
# var heap/EBX : (address allocation-descriptor) = {0, 0}
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 3/rm32/EBX . . . 4/r32/ESP . . # copy ESP to EBX
# heap = new-segment(512)
# . . push args
53/push-EBX
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# var handle/ECX = {0, 0}
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# var old_top/EDX = heap->curr
8b/copy 0/mod/indirect 3/rm32/EBX . . . 2/r32/EDX . . # copy *EBX to EDX
# new(heap, 2, handle)
# . . push args
51/push-ECX
68/push 2/imm32/size
53/push-EBX
# . . call
e8/call new/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# EAX = lookup(handle)
# . . push args
51/push-ECX
# . . call
e8/call lookup/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# EAX contains old top of heap, except skipping the alloc id in the payload
# . check-ints-equal(EAX, old_top+4, msg)
# . . push args
68/push "F - test-lookup-success"/imm32
81 0/subop/add 3/mod/direct 2/rm32/EDX . . . . . 4/imm32 # add to EDX
52/push-EDX
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# clean up
# . *Next-alloc-id = 1
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 1/imm32 # copy to *Next-alloc-id
# write(2/stderr, "lookup succeeded\n")
# . . push args
68/push "lookup succeeded\n"/imm32
68/push 2/imm32/stderr
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . restore registers
5a/pop-to-EDX
59/pop-to-ECX
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-lookup-failure:
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# var heap/ESI : (address allocation-descriptor) = {0, 0}
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 6/rm32/ESI . . . 4/r32/ESP . . # copy ESP to ESI
# heap = new-segment(512)
# . . push args
56/push-ESI
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# var h1/ECX = {0, 0}
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# var old_top/EBX = heap->curr
8b/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy *ESI to EBX
# first allocation, to h1
# . new(heap, 2, h1)
# . . push args
51/push-ECX
68/push 2/imm32/size
56/push-ESI
# . . call
e8/call new/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# reset heap->curr to mimic reclamation
89/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy EBX to *ESI
# second allocation that returns the same address as the first
# var h2/EDX = {0, 0}
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 2/rm32/EDX . . . 4/r32/ESP . . # copy ESP to EDX
# . new(heap, 2, h2)
# . . push args
52/push-EDX
68/push 2/imm32/size
56/push-ESI
# . . call
e8/call new/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# check-ints-equal(h1->address, h2->address, msg)
# . . push args
68/push "F - test-lookup-failure"/imm32
ff 6/subop/push 1/mod/*+disp8 2/rm32/ECX . . . . 4/disp8 . # push *(EDX+4)
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# lookup(h1) should crash
# . . push args
51/push-ECX
# . . call
e8/call lookup/disp32
# should never get past this point
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# clean up
# . *Next-alloc-id = 1
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 1/imm32 # copy to *Next-alloc-id
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
== data
# Monotonically increasing counter for calls to 'new'
Next-alloc-id:
1/imm32
# . . vim:nowrap:textwidth=0