[kernel] Allow 7+ argument syscalls

The syscall interface is designed to closely follow the System V amd64
calling convention, so that as much as possible, the call into the
assembly trampoline for the syscall sets up the call correctly. Before
this change, the only exception was using r10 (a caller-saved register
already) to stash the contents of rcx, which gets clobbered by the
syscall instruction. However, this only preserves registers for the
function call, as the stack is switched upon kernel entry, and
additional call frames have been added by the time the syscall gets back
into C++ land.

This change adds a new parameter to the syscall in rbx. Since rbx is
callee-saved, the syscall trampoline pushes it to the stack, and then
puts the address of the stack-passed arguments into rbx. Now that the
syscall implementations are wrapped in the _syscall_verify_* functions,
we can piggy-back on those to also set up the extra arguments from the
user stack.

Now, for any syscall with 7 or more arguments, the verify wrapper takes
the first six arguments normally, then gets a stack pointer (the rbx
value) as its 7th and final argument. It's then the job of the verify
wrapper to get the remaining arguments from that stack pointer and pass
them to the implementation function as normal arguments.
This commit is contained in:
Justin C. Miller
2022-01-30 12:25:11 -08:00
parent 17ca402aa0
commit b6218a1121
4 changed files with 44 additions and 4 deletions

View File

@@ -49,6 +49,9 @@ namespace syscalls
for type, suffix in param.type.c_names(param.options): for type, suffix in param.type.c_names(param.options):
args.append(f"{type} {param.name}{suffix}") args.append(f"{type} {param.name}{suffix}")
if len(args) > 6:
args = args[:6] + ["uint64_t *sp"]
cog.outl(f"""j6_status_t _syscall_verify_{name} ({", ".join(args)});""") cog.outl(f"""j6_status_t _syscall_verify_{name} ({", ".join(args)});""")
]]]*/ ]]]*/
//[[[end]]] //[[[end]]]

View File

@@ -50,6 +50,11 @@ syscall_handler_prelude:
push r14 push r14
push r15 push r15
; if we've got more than 6 arguments, the rest
; are on the user stack, and pointed to by rbx.
; push rbx so that it's the 7th argument.
push rbx
inc qword [rel __counter_syscall_enter] inc qword [rel __counter_syscall_enter]
cmp rax, NUM_SYSCALLS cmp rax, NUM_SYSCALLS
@@ -62,6 +67,8 @@ syscall_handler_prelude:
call r11 call r11
add rsp, 8 ; account for passing rbx on the stack
inc qword [rel __counter_syscall_sysret] inc qword [rel __counter_syscall_sysret]
jmp kernel_to_user_trampoline jmp kernel_to_user_trampoline

View File

@@ -28,6 +28,14 @@ namespace {
return reinterpret_cast<uintptr_t>(param) < arch::kernel_offset && return reinterpret_cast<uintptr_t>(param) < arch::kernel_offset &&
(optional || param); (optional || param);
} }
// When called with more than 6 arguments, the 7th argument to the
// verify function is the stack pointer holding the rest. "Pop" them
// into variables to be passed normally with pop_from.
template <typename T>
inline T pop_from(uint64_t *&sp) {
return *reinterpret_cast<T*>(sp++);
}
} }
namespace syscalls { namespace syscalls {
@@ -56,13 +64,13 @@ for id, scope, method in syscalls.methods:
objparams = [] objparams = []
if method.constructor: if method.constructor:
argdefs.append("j6_handle_t *self") argdefs.append(("j6_handle_t *", "self"))
cxxargdefs.append("j6_handle_t *self") cxxargdefs.append("j6_handle_t *self")
args.append("self") args.append("self")
refparams.append(("self", False)) refparams.append(("self", False))
elif not method.static: elif not method.static:
argdefs.append("j6_handle_t self") argdefs.append(("j6_handle_t", "self"))
cxxargdefs.append(f"obj::{scope.cname} *self") cxxargdefs.append(f"obj::{scope.cname} *self")
args.append("self_obj") args.append("self_obj")
@@ -75,7 +83,7 @@ for id, scope, method in syscalls.methods:
for type, suffix in param.type.c_names(param.options): for type, suffix in param.type.c_names(param.options):
arg = f"{param.name}{suffix}" arg = f"{param.name}{suffix}"
argdefs.append(f"{type} {arg}") argdefs.append((type, arg))
for type, suffix in param.type.cxx_names(param.options): for type, suffix in param.type.cxx_names(param.options):
arg = f"{param.name}{suffix}" arg = f"{param.name}{suffix}"
@@ -101,8 +109,20 @@ for id, scope, method in syscalls.methods:
for sub in subs[1:]: for sub in subs[1:]:
refparams.append((param.name + sub[1], param.optional)) refparams.append((param.name + sub[1], param.optional))
first_args = ", ".join(map(lambda x: f"{x[0]} {x[1]}", argdefs[:6]))
extra_argdefs = argdefs[6:]
if extra_argdefs:
first_args += ", uint64_t *sp"
cog.outl(f"""j6_status_t {name} ({", ".join(cxxargdefs)});""") cog.outl(f"""j6_status_t {name} ({", ".join(cxxargdefs)});""")
cog.outl(f"""j6_status_t _syscall_verify_{name} ({", ".join(argdefs)}) {{""") cog.outl(f"""j6_status_t _syscall_verify_{name} ({first_args}) {{""")
for type, arg in extra_argdefs:
cog.outl(f" {type} {arg} = pop_from<{type}>(sp);")
if extra_argdefs:
cog.outl()
for pname, optional in refparams: for pname, optional in refparams:
cog.outl(f" if (!check_refparam({pname}, {cbool[optional]}))") cog.outl(f" if (!check_refparam({pname}, {cbool[optional]}))")

View File

@@ -6,6 +6,15 @@
push rbp push rbp
mov rbp, rsp mov rbp, rsp
; if the syscall has more than 6 arguments, the rest
; will be pushed on the stack. in that case, we'd need
; to pass this stack pointer to the kernel, so stash
; off rbx (callee-saved) and pass the pointer to the
; arguments there.
push rbx
mov rbx, rbp
add rbx, 16 ; account for stack frame
; args should already be in rdi, etc, but rcx will ; args should already be in rdi, etc, but rcx will
; get stomped, so stash it in r10, which isn't a ; get stomped, so stash it in r10, which isn't a
; callee-saved register, but also isn't used in the ; callee-saved register, but also isn't used in the
@@ -16,6 +25,7 @@
syscall syscall
; result is now already in rax, so just return ; result is now already in rax, so just return
pop rbx
pop rbp pop rbp
ret ret
%endmacro %endmacro