f04fdc8e57
A little more resizing of buffers. apps/hex.subx is now building an identical binary. I'm now aborting on allocation failures. That requires disabling a couple of tests. (I'm not quite confident enough of this decision to delete them outright.) I want to treat all segfaults as bugs, and machine code is no place to add boilerplate checks for return values of standard library functions.
413 lines
22 KiB
Plaintext
413 lines
22 KiB
Plaintext
# 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
|
|
|
|
_pending-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
|