libcheri_invoke: CCall Implementation in CheriBSD

libcheri_invoke is defined in assembly code; two versions for different ABIs respectively.

Hybrid libcheri_invoke

The Hybrid MIPS ABI (github):

# file: lib/libcheri/mips/libcheri_invoke_hybrid.S

#
# Assembly wrapper for CCall on an object-capability.  Its function is to save
# and restore any general-purpose and capability registers needed on either
# side of CCall, but not handled by the compiler.  This is done by creating an
# on-stack frame which will be pointed to by $idc before CCall, and then
# unwrapping it again.  We rely on the compiler and CCall to sort out clearing
# of registers that require it, since they have the information to do so.
#
# Calling conventions into libcheri_invoke:
#
# Registers	Description
# $c0		MIPS address space
# $c1, $c2	Invoked capabilities
# $c3..$c10	Argument capabilities
# $c11..$c16	Caller-save capabilities
# $c17..$c26	Callee-save capabilities
#
# Calling conventions implemented by CCall:
#
# $c1		Invoked code capability
# $c2		Invoked data capability
# $c3..$c10	Argument capabilities; $c3 as return capability
# $c11..$c16	n/a
# $c17..$c25	n/a
# $c26		IDC
#
# XXXRW: Worries/TODO:
#
# - Floating-point registers.
# - The compiler needs to handle unused argument/return registers.
#

	.text
	.global libcheri_invoke
	.global cheri_invoke
	.ent	libcheri_invoke
libcheri_invoke:
cheri_invoke:

	#
	# Wrap up all caller-save state suitable to be preseved by CCall and
	# restored by CReturn.  This happens in two phases:
	#
	# 1. First, use the conventional stack to save as many caller-save
	# general-purpose and capability registers as possible.
	#
	# 2. Produce a value for $csp that bundles these up suitable to
	# bootstrap restoration.  This will save the original $idc, $csp,
	# $sp, and $ddc.
	#
	# Then after CReturn, reverse these steps by first unwrapping $idc,
	# and then performing a more normal function return.
	#
	# The caller $csp will describe a stack fragment, which gives us a bit
	# of space to store useful things, such as $sp, that are otherwise
	# quite hard to restore (for obvious reasons).
	#
	# NB: This is still required for the hybrid ABI, unlike the
	# pure-capability ABI, because we need to save and restore $sp.
	#
	# Save callee-save general-purpose registers.
	#
	# Caller-save registers are: $s0..$s7, $gp, $sp, $s8 ($fp).
	#
	# Do also save $ra so that we can return properly.
	#
	# NB: Use 96 rather than 88 for the stack to ensure 32-byte alignment
	# for capabilities stored and loaded from it later.
	#
	# XXXRW: Possibly with the __ccall calling convention, the compiler
	# should be doing this?
	#
libcheri_invoke_save_regs:
	daddiu	$sp, -96
	sd	$s0, 0($sp)
    ...
	sd	$s7, 56($sp)
	sd	$gp, 64($sp)
	sd	$fp, 72($sp)
	sd	$ra, 80($sp)
	sd	$t9, 88($sp)

	#
	# Save capability registers we later need to restore (that won't be
	# handled by CCall for us).
	#
libcheri_invoke_save_caps:
	cgetdefault	$c11
	daddiu	$sp, -10*CHERICAP_SIZE
	csc	$c17, $sp, 0($c11)
	csc	$c18, $sp, 1*CHERICAP_SIZE($c11)
    ...
	csc	$c26, $sp, 9*CHERICAP_SIZE($c11)

	#
	# Stash $sp in the offset of $c11 so that it will be saved and
	# restored by CCall.
	#
	csetoffset	$c11, $c11, $sp

	#
	# The compiler is responsible for scrubbing unused argument registers
	# (since only it has the information required to do so).  CCall is
	# responsible for scrubbing all other registers.
	#

	#
	# Construct link-address PCC.
	#
	# XXXRW: Do we want a CCall variant like CJALR that automatically
	# builds the desired capability?
	#
	dla		$t0, libcheri_invoke_ccall_linkaddr
	cgetpcc		$c17
	csetoffset	$c17, $c17, $t0

	#
	# Invoke object capability.  CCall/CReturn will save and restore $csp.
	#
libcheri_invoke_ccall:
	CCALL($c1, $c2)
libcheri_invoke_ccall_linkaddr:

	#
	# Unstash $sp from offset of $c11; restore $c11 offset to zero and
	# install as DDC.
	#
	cgetoffset	$sp, $c11
	csetoffset	$c11, $c11, $zero
	csetdefault	$c11

	#
	# Restore capability registers from stack.
	#
libcheri_invoke_restore_caps:
	clc	$c17, $sp, 0($c11)
	clc	$c18, $sp, 1*CHERICAP_SIZE($c11)
    ...
	clc	$c26, $sp, 9*CHERICAP_SIZE($c11)

    ...

    #
	# CCall has conservatively cleared all non-return-value registers, and
	# so we don't need to.

	#
	# Restore general-purpose registers from the stack.
	#
libcheri_invoke_restore_regs:
	ld	$s0, 0($sp)
	ld	$s1, 8($sp)
    ...
	ld	$s7, 56($sp)
	ld	$gp, 64($sp)
	ld	$fp, 72($sp)
	ld	$ra, 80($sp)
	daddiu	$sp, 96

	#
	# Return to C-language caller.
	#
libcheri_invoke_return:
	jr	$ra
	nop				# Branch-delay slot

Pure-cap libcheri_invoke

The Pure-capability CheriABI: (github)

# file: lib/libcheri/mips/libcheri_invoke_cabi.S

# Calling conventions into libcheri_invoke:
#
# Registers	Description
# $c0		MIPS address space
# $c1, $c2	Invoked capabilities
# $c3..$c10	Argument capabilities
# $c11..$c16	Caller-save capabilities
# $c17..$c26	Callee-save capabilities
#
# Calling conventions implemented by CCall:
#
# $c1		Invoked code capability
# $c2		Invoked data capability
# $c3..$c10	Argument capabilities; $c3 as return capability
# $c11..$c16	n/a
# $c17..$c25	n/a
# $c26		IDC
#
# XXXRW: Worries/TODO:
#
# - Floating-point registers.
# - The compiler needs to handle unused argument/return registers.
#

#define LIBCHERI_INVOKE_FRAME_SIZE (11*CHERICAP_SIZE + 96)

NESTED(libcheri_invoke, LIBCHERI_INVOKE_FRAME_SIZE, _FRAME_RETURN_REG)
XNESTED(cheri_invoke)
	#
	# Wrap up all caller-save state suitable to be preseved by CCall and
	# restored by CReturn.  This happens in two phases:
	#
	# 1. First, use the conventional stack to save as many caller-save
	# general-purpose and capability registers as possible.
	#
	# 2. Produce a value for $csp that bundles these up suitable to
	# bootstrap restoration.  This will save the original $idc, $csp,
	# and $ddc.
	#
	# Then after CReturn, reverse these steps by first unwrapping $idc,
	# and then performing a more normal function return.
	#
	# The caller $csp will describe a stack fragment, which gives us a bit
	# of space to store useful things that are otherwise hard to restore.
	#
	# NB: This is no longer required for the pure-capability ABI.
	#
	# Save callee-save general-purpose registers.
	#
	# Caller-save registers are: $s0..$s7, $gp, $s8 ($fp).
	#
	# NB: Use 96 rather than 88 for the stack to ensure 32-byte alignment
	# for capabilities stored and loaded from it later.
	#
	# XXXRW: Possibly with the __ccall calling convention, the compiler
	# should be doing this?
	#
libcheri_invoke_save_regs:
	cincoffset	$csp, $csp, -96
	csd	$s0, $zero, 0($csp)
	csd	$s1, $zero, 8($csp)
    ...
	csd	$s7, $zero, 56($csp)
	csd	$gp, $zero, 64($csp)
	csd	$fp, $zero, 72($csp)
	csd	$ra, $zero, 80($csp)

	#
	# Save capability registers we later need to restore (that won't be
	# handled by CCall for us).
	#
libcheri_invoke_save_caps:
	cgetdefault	$c12
	cincoffset	$csp, $csp, -11*CHERICAP_SIZE
	csc	$c17, $zero, 0($csp)
	csc	$c18, $zero, CHERICAP_SIZE($csp)
    ...
	csc	$c26, $zero, 9*CHERICAP_SIZE($csp)
	csc	$c12, $zero, 10*CHERICAP_SIZE($csp)

	#
	# The compiler is responsible for scrubbing unused argument registers
	# (since only it has the information required to do so).  CCall is
	# responsible for scrubbing all other registers.
	#

	#
	# Construct link-address PCC.
	#
	# XXXRW: Do we want a CCall variant like CJALR that automatically
	# builds the desired capability?
	#
	dla		$t0, libcheri_invoke_ccall_linkaddr
	cgetpcc		$c17
	csetoffset	$c17, $c17, $t0

	#
	# Invoke object capability.  CCall/CReturn will save and restore $csp.
	#
libcheri_invoke_ccall:
	CCALL($c1, $c2)
libcheri_invoke_ccall_linkaddr:

	#
	# Restore capability registers from stack.
	#
libcheri_invoke_restore_caps:
	clc	$c17, $zero, 0($csp)
	clc	$c18, $zero, CHERICAP_SIZE($csp)
    ...
	clc	$c26, $zero, 9*CHERICAP_SIZE($csp)
	clc	$c12, $zero, 10*CHERICAP_SIZE($csp)
	csetdefault	$c12
    
    ...

	# Restore general-purpose registers from the stack.
	#
	# XXXRW: Possibly with the __ccall calling convention, the compiler
	# should be doing this?
	#
libcheri_invoke_restore_regs:
	cld	$s0, $zero, 0($csp)
	cld	$s1, $zero, 8($csp)
	cld	$s2, $zero, 16($csp)
	cld	$s3, $zero, 24($csp)
	cld	$s4, $zero, 32($csp)
	cld	$s5, $zero, 40($csp)
	cld	$s6, $zero, 48($csp)
	cld	$s7, $zero, 56($csp)
	cld	$gp, $zero, 64($csp)
	cld	$fp, $zero, 72($csp)
	cld	$ra, $zero, 80($csp)
	cincoffset	$csp, $csp, 96

	#
	# Return to C-language caller.
	#
libcheri_invoke_return:
	cjr	$c17
	nop				# Branch-delay slot

END(libcheri_invoke)

CCALL is defined as a macro wrapper for ccall in CheriBSD, source code: (github)

// file: sys/mips/include/cheriasm.h

#define CCALL(cb, cd)						\
	.set push;						\
	.set noreorder;						\
	ccall cb, cd, 1;					\
	nop; /* Fill branch delay slot for old harware*/	\
	.set pop;

The libcheri_invoke_ccall after compilation:

ccall

128-bit mode are the same: ccall

Reference1


  1. reference ↩
Created Jul 17, 2019 // Last Updated May 18, 2021

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?