kernel: add generic wrapper for accessing user memory
This patch adds user_access() which can be used to gracefully handle
page faults that may happen when accessing user memory. It is used
by arch_cpu_user{memcpy, memset, strlcpy}() to allow using optimized
functions from the standard library.
Currently only x64 uses this, but nothing really is arch specific here.
Signed-off-by: Paweł Dziepak <[email protected]>
This commit is contained in:
@@ -34,13 +34,6 @@ void arch_cpu_invalidate_TLB_list(addr_t pages[], int num_pages);
|
||||
void arch_cpu_user_TLB_invalidate(void);
|
||||
void arch_cpu_global_TLB_invalidate(void);
|
||||
|
||||
status_t arch_cpu_user_memcpy(void *to, const void *from, size_t size,
|
||||
addr_t *faultHandler);
|
||||
ssize_t arch_cpu_user_strlcpy(char *to, const char *from, size_t size,
|
||||
addr_t *faultHandler);
|
||||
status_t arch_cpu_user_memset(void *s, char c, size_t count,
|
||||
addr_t *faultHandler);
|
||||
|
||||
void arch_cpu_sync_icache(void *address, size_t length);
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2014, Paweł Dziepak, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _KERNEL_ARCH_GENERIC_USER_MEMORY_H
|
||||
#define _KERNEL_ARCH_GENERIC_USER_MEMORY_H
|
||||
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <thread.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
struct FaultHandlerGuard {
|
||||
FaultHandlerGuard()
|
||||
{
|
||||
ASSERT(thread_get_current_thread()->fault_handler == nullptr);
|
||||
thread_get_current_thread()->fault_handler = HandleFault;
|
||||
std::atomic_signal_fence(std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
|
||||
~FaultHandlerGuard()
|
||||
{
|
||||
std::atomic_signal_fence(std::memory_order_acq_rel);
|
||||
thread_get_current_thread()->fault_handler = nullptr;
|
||||
}
|
||||
|
||||
|
||||
[[noreturn]] static void HandleFault()
|
||||
{
|
||||
longjmp(thread_get_current_thread()->fault_handler_state, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Function>
|
||||
bool user_access(Function function)
|
||||
{
|
||||
FaultHandlerGuard guard;
|
||||
// TODO: try { } catch (...) { } would be much nicer, wouldn't it?
|
||||
// And faster... And world wouldn't end in a terrible disaster if function()
|
||||
// or anything it calls created on stack an object with non-trivial
|
||||
// destructor.
|
||||
auto fail = setjmp(thread_get_current_thread()->fault_handler_state);
|
||||
if (fail == 0) {
|
||||
function();
|
||||
return true;
|
||||
}
|
||||
dprintf("Hi! This is fault handler speaking.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
inline status_t
|
||||
arch_cpu_user_memcpy(void* src, const void* dst, size_t n)
|
||||
{
|
||||
return user_access([=] { memcpy(src, dst, n); }) ? B_OK : B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
inline status_t
|
||||
arch_cpu_user_memset(void* src, char v, size_t n)
|
||||
{
|
||||
return user_access([=] { memset(src, v, n); }) ? B_OK : B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
inline ssize_t
|
||||
arch_cpu_user_strlcpy(char* src, const char* dst, size_t n)
|
||||
{
|
||||
ssize_t result;
|
||||
return user_access([=, &result] { result = strlcpy(src, dst, n); })
|
||||
? result : B_ERROR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // _KERNEL_ARCH_GENERIC_USER_MEMORY_H
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2014, Paweł Dziepak, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _KERNEL_ARCH_USER_MEMORY_H
|
||||
#define _KERNEL_ARCH_USER_MEMORY_H
|
||||
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
#include <thread.h>
|
||||
|
||||
|
||||
#ifdef __x86_64__
|
||||
# include <arch/generic/user_memory.h>
|
||||
#else
|
||||
|
||||
extern "C" {
|
||||
|
||||
status_t _arch_cpu_user_memcpy(void* to, const void* from, size_t size,
|
||||
void (**faultHandler)(void));
|
||||
ssize_t _arch_cpu_user_strlcpy(char* to, const char* from, size_t size,
|
||||
void (**faultHandler)(void));
|
||||
status_t _arch_cpu_user_memset(void* s, char c, size_t count,
|
||||
void (**faultHandler)(void));
|
||||
|
||||
}
|
||||
|
||||
|
||||
static inline status_t
|
||||
arch_cpu_user_memcpy(void* to, const void* from, size_t size)
|
||||
{
|
||||
return _arch_cpu_user_memcpy(to, from, size,
|
||||
&thread_get_current_thread()->fault_handler);
|
||||
}
|
||||
|
||||
|
||||
static inline ssize_t
|
||||
arch_cpu_user_strlcpy(char* to, const char* from, size_t size)
|
||||
{
|
||||
return _arch_cpu_user_strlcpy(to, from, size,
|
||||
&thread_get_current_thread()->fault_handler);
|
||||
}
|
||||
|
||||
|
||||
static inline status_t
|
||||
arch_cpu_user_memset(void* s, char c, size_t count)
|
||||
{
|
||||
return _arch_cpu_user_memset(s, c, count,
|
||||
&thread_get_current_thread()->fault_handler);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#endif // _KERNEL_ARCH_USER_MEMORY_H
|
||||
|
||||
@@ -479,7 +479,8 @@ struct Thread : TeamThreadIteratorEntry<thread_id>, KernelReferenceable {
|
||||
} msg; // write_sem/read_sem are protected by fLock when accessed by
|
||||
// others, the other fields are protected by write_sem/read_sem
|
||||
|
||||
addr_t fault_handler;
|
||||
void (*fault_handler)(void);
|
||||
jmp_buf fault_handler_state;
|
||||
int32 page_faults_allowed;
|
||||
/* this field may only stay in debug builds in the future */
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ FUNCTION_END(arm_get_fp);
|
||||
|
||||
|
||||
/* status_t arch_cpu_user_memcpy(void *to, const void *from, size_t size, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memcpy):
|
||||
FUNCTION(_arch_cpu_user_memcpy):
|
||||
stmfd sp!, { r4-r6, lr }
|
||||
|
||||
ldr r6, [r3]
|
||||
@@ -140,10 +140,10 @@ FUNCTION(arch_cpu_user_memcpy):
|
||||
mov r0, #-1
|
||||
|
||||
ldmfd sp!, { r4-r6, pc }
|
||||
FUNCTION_END(arch_cpu_user_memcpy)
|
||||
FUNCTION_END(_arch_cpu_user_memcpy)
|
||||
|
||||
/* status_t arch_cpu_user_memset(void *to, char c, size_t count, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memset):
|
||||
FUNCTION(_arch_cpu_user_memset):
|
||||
stmfd sp!, { r4-r5, lr }
|
||||
|
||||
ldr r5, [r3]
|
||||
@@ -179,10 +179,10 @@ FUNCTION(arch_cpu_user_memset):
|
||||
str r5, [r3]
|
||||
|
||||
ldmfd sp!, { r4-r5, pc }
|
||||
FUNCTION_END(arch_cpu_user_memset)
|
||||
FUNCTION_END(_arch_cpu_user_memset)
|
||||
|
||||
/* ssize_t arch_cpu_user_strlcpy(void *to, const void *from, size_t size, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_strlcpy):
|
||||
FUNCTION(_arch_cpu_user_strlcpy):
|
||||
stmfd sp!, { r4-r6, lr }
|
||||
ldr r5, [r3]
|
||||
ldr r4, =.L_user_strlcpy_error
|
||||
@@ -210,7 +210,7 @@ FUNCTION(arch_cpu_user_strlcpy):
|
||||
str r5, [r3]
|
||||
|
||||
ldmfd sp!, { r4-r6, pc }
|
||||
FUNCTION_END(arch_cpu_user_strlcpy)
|
||||
FUNCTION_END(_arch_cpu_user_strlcpy)
|
||||
|
||||
|
||||
/*! \fn void arch_debug_call_with_fault_handler(cpu_ent* cpu,
|
||||
|
||||
@@ -270,7 +270,7 @@ arch_arm_data_abort(struct iframe *frame)
|
||||
"debugger!\n");
|
||||
debug_set_page_fault_info(far, frame->pc,
|
||||
isWrite ? DEBUG_PAGE_FAULT_WRITE : 0);
|
||||
frame->pc = thread->fault_handler;
|
||||
frame->pc = reinterpret_cast<uintptr_t>(thread->fault_handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -286,9 +286,10 @@ arch_arm_data_abort(struct iframe *frame)
|
||||
// TODO: Now we are generally allowing user_memcpy() with interrupts
|
||||
// disabled, which in most cases is a bug. We should add some thread
|
||||
// flag allowing to explicitly indicate that this handling is desired.
|
||||
uintptr_t handler = reinterpret_cast<uintptr_t>(thread->fault_handler);
|
||||
if (thread && thread->fault_handler != 0) {
|
||||
if (frame->pc != thread->fault_handler) {
|
||||
frame->pc = thread->fault_handler;
|
||||
if (frame->pc != handler) {
|
||||
frame->pc = handler;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ FUNCTION_END(x86_reboot)
|
||||
|
||||
|
||||
/* status_t arch_cpu_user_memcpy(void *to, const void *from, size_t size, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memcpy):
|
||||
FUNCTION(_arch_cpu_user_memcpy):
|
||||
pushl %esi
|
||||
pushl %edi
|
||||
movl 12(%esp),%edi /* dest */
|
||||
@@ -182,11 +182,11 @@ FUNCTION(arch_cpu_user_memcpy):
|
||||
popl %edi
|
||||
popl %esi
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_memcpy)
|
||||
FUNCTION_END(_arch_cpu_user_memcpy)
|
||||
|
||||
|
||||
/* status_t arch_cpu_user_memset(void *to, char c, size_t count, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memset):
|
||||
FUNCTION(_arch_cpu_user_memset):
|
||||
pushl %esi
|
||||
pushl %edi
|
||||
movl 12(%esp),%edi /* dest */
|
||||
@@ -217,11 +217,11 @@ FUNCTION(arch_cpu_user_memset):
|
||||
popl %edi
|
||||
popl %esi
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_memset)
|
||||
FUNCTION_END(_arch_cpu_user_memset)
|
||||
|
||||
|
||||
/* ssize_t arch_cpu_user_strlcpy(void *to, const void *from, size_t size, addr_t *faultHandler) */
|
||||
FUNCTION(arch_cpu_user_strlcpy):
|
||||
FUNCTION(_arch_cpu_user_strlcpy):
|
||||
pushl %esi
|
||||
pushl %edi
|
||||
pushl %ebx
|
||||
@@ -292,7 +292,7 @@ FUNCTION(arch_cpu_user_strlcpy):
|
||||
popl %edi
|
||||
popl %esi
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_strlcpy)
|
||||
FUNCTION_END(_arch_cpu_user_strlcpy)
|
||||
|
||||
|
||||
/*! \fn void arch_debug_call_with_fault_handler(cpu_ent* cpu,
|
||||
|
||||
@@ -63,142 +63,6 @@ done:
|
||||
FUNCTION_END(x86_reboot)
|
||||
|
||||
|
||||
/* status_t arch_cpu_user_memcpy(void* to, const void* from, size_t size,
|
||||
addr_t* faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memcpy):
|
||||
// faultHandler -> r8, size -> rcx.
|
||||
movq %rcx, %r8
|
||||
movq %rdx, %rcx
|
||||
|
||||
// Set the fault handler, preserve old in rax.
|
||||
movq (%r8), %rax
|
||||
movq $.L_user_memcpy_error, (%r8)
|
||||
|
||||
// Move by quadwords.
|
||||
cld
|
||||
movq %rcx, %r9
|
||||
shrq $3, %rcx
|
||||
rep
|
||||
movsq
|
||||
|
||||
// Move any remaining data by bytes.
|
||||
movq %r9, %rcx
|
||||
andq $7, %rcx
|
||||
rep
|
||||
movsb
|
||||
|
||||
// Restore the old fault handler and return.
|
||||
movq %rax, (%r8)
|
||||
xorl %eax, %eax
|
||||
ret
|
||||
|
||||
.L_user_memcpy_error:
|
||||
// Restore the old fault handler. Return a generic error, the wrapper
|
||||
// routine will deal with it.
|
||||
movq %rax, (%r8)
|
||||
movl $-1, %eax
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_memcpy)
|
||||
|
||||
|
||||
/* status_t arch_cpu_user_memset(void* to, char c, size_t count,
|
||||
addr_t* faultHandler) */
|
||||
FUNCTION(arch_cpu_user_memset):
|
||||
// c -> al, faultHandler -> r8, size -> rcx.
|
||||
movw %si, %ax
|
||||
movq %rcx, %r8
|
||||
movq %rdx, %rcx
|
||||
|
||||
// Set the fault handler, preserve old in rdx.
|
||||
movq (%r8), %rdx
|
||||
movq $.L_user_memset_error, (%r8)
|
||||
|
||||
rep
|
||||
stosb
|
||||
|
||||
// Restore the old fault handler and return.
|
||||
movq %rdx, (%r8)
|
||||
xorl %eax, %eax
|
||||
ret
|
||||
|
||||
.L_user_memset_error:
|
||||
// Restore the old fault handler. Return a generic error, the wrapper
|
||||
// routine will deal with it.
|
||||
movq %rdx, (%r8)
|
||||
movl $-1, %eax
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_memset)
|
||||
|
||||
|
||||
/* ssize_t arch_cpu_user_strlcpy(void* to, const void* from, size_t size,
|
||||
addr_t* faultHandler) */
|
||||
FUNCTION(arch_cpu_user_strlcpy):
|
||||
// faultHandler -> r8, size -> rcx, source -> r9 (original value needed to
|
||||
// calculate return value).
|
||||
movq %rcx, %r8
|
||||
movq %rdx, %rcx
|
||||
movq %rsi, %r9
|
||||
|
||||
// Set the fault handler, preserve old in rax.
|
||||
movq (%r8), %rax
|
||||
movq $.L_user_strlcpy_error, (%r8)
|
||||
|
||||
// Check for 0 length.
|
||||
cmp $0, %rcx
|
||||
je .L_user_strlcpy_source_count
|
||||
|
||||
// Copy at most count - 1 bytes.
|
||||
dec %rcx
|
||||
|
||||
// If count is now 0, skip straight to null terminating as our loop will
|
||||
// otherwise overflow.
|
||||
jnz .L_user_strlcpy_copy_begin
|
||||
movb $0, (%rdi)
|
||||
jmp .L_user_strlcpy_source_count
|
||||
|
||||
.L_user_strlcpy_copy_begin:
|
||||
cld
|
||||
.L_user_strlcpy_copy_loop:
|
||||
// Move data by bytes.
|
||||
lodsb
|
||||
stosb
|
||||
test %al, %al
|
||||
jz .L_user_strlcpy_source_done
|
||||
loop .L_user_strlcpy_copy_loop
|
||||
|
||||
// Null terminate string.
|
||||
movb $0, (%rdi)
|
||||
dec %rsi
|
||||
|
||||
.L_user_strlcpy_source_count:
|
||||
// Count remaining bytes in src
|
||||
not %rcx
|
||||
# %rcx was 0 and is now max
|
||||
xor %al, %al
|
||||
movq %rsi, %rdi
|
||||
repnz
|
||||
scasb
|
||||
movq %rdi, %rsi
|
||||
|
||||
.L_user_strlcpy_source_done:
|
||||
// Restore the old fault handler
|
||||
movq %rax, (%r8)
|
||||
|
||||
// Calculate total string length and return.
|
||||
movq %rsi, %rax
|
||||
subq %r9, %rax
|
||||
dec %rax
|
||||
ret
|
||||
|
||||
.L_user_strlcpy_error:
|
||||
// Restore the old fault handler. Return a generic error, the wrapper
|
||||
// routine will deal with it.
|
||||
movq %rax, (%r8)
|
||||
movq $-1, %rax
|
||||
ret
|
||||
FUNCTION_END(arch_cpu_user_strlcpy)
|
||||
|
||||
|
||||
/*! \fn void arch_debug_call_with_fault_handler(cpu_ent* cpu,
|
||||
jmp_buf jumpBuffer, void (*function)(void*), void* parameter)
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ x86_page_fault_exception(struct iframe* frame)
|
||||
debug_set_page_fault_info(cr2, frame->ip,
|
||||
(frame->error_code & 0x2) != 0
|
||||
? DEBUG_PAGE_FAULT_WRITE : 0);
|
||||
frame->ip = thread->fault_handler;
|
||||
frame->ip = reinterpret_cast<uintptr_t>(thread->fault_handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -295,9 +295,10 @@ x86_page_fault_exception(struct iframe* frame)
|
||||
// TODO: Now we are generally allowing user_memcpy() with interrupts
|
||||
// disabled, which in most cases is a bug. We should add some thread
|
||||
// flag allowing to explicitly indicate that this handling is desired.
|
||||
uintptr_t handler = reinterpret_cast<uintptr_t>(thread->fault_handler);
|
||||
if (thread && thread->fault_handler != 0) {
|
||||
if (frame->ip != thread->fault_handler) {
|
||||
frame->ip = thread->fault_handler;
|
||||
if (frame->ip != handler) {
|
||||
frame->ip = handler;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <arch/cpu.h>
|
||||
#include <arch/vm.h>
|
||||
#include <arch/user_memory.h>
|
||||
#include <boot/elf.h>
|
||||
#include <boot/stage2.h>
|
||||
#include <condition_variable.h>
|
||||
@@ -4219,7 +4220,7 @@ vm_page_fault(addr_t address, addr_t faultAddress, bool isWrite, bool isExecute,
|
||||
// this will cause the arch dependant page fault handler to
|
||||
// modify the IP on the interrupt frame or whatever to return
|
||||
// to this address
|
||||
*newIP = thread->fault_handler;
|
||||
*newIP = reinterpret_cast<uintptr_t>(thread->fault_handler);
|
||||
} else {
|
||||
// unhandled page fault in the kernel
|
||||
panic("vm_page_fault: unhandled page fault in kernel space at "
|
||||
@@ -5255,8 +5256,7 @@ user_memcpy(void* to, const void* from, size_t size)
|
||||
if ((addr_t)from + size < (addr_t)from || (addr_t)to + size < (addr_t)to)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
if (arch_cpu_user_memcpy(to, from, size,
|
||||
&thread_get_current_thread()->fault_handler) < B_OK)
|
||||
if (arch_cpu_user_memcpy(to, from, size) < B_OK)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
return B_OK;
|
||||
@@ -5286,8 +5286,7 @@ user_strlcpy(char* to, const char* from, size_t size)
|
||||
// NOTE: Since arch_cpu_user_strlcpy() determines the length of \a from,
|
||||
// the source address might still overflow.
|
||||
|
||||
ssize_t result = arch_cpu_user_strlcpy(to, from, maxSize,
|
||||
&thread_get_current_thread()->fault_handler);
|
||||
ssize_t result = arch_cpu_user_strlcpy(to, from, maxSize);
|
||||
|
||||
// If we hit the address overflow boundary, fail.
|
||||
if (result < 0 || (result >= 0 && (size_t)result >= maxSize
|
||||
@@ -5305,9 +5304,7 @@ user_memset(void* s, char c, size_t count)
|
||||
// don't allow address overflows
|
||||
if ((addr_t)s + count < (addr_t)s)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
if (arch_cpu_user_memset(s, c, count,
|
||||
&thread_get_current_thread()->fault_handler) < B_OK)
|
||||
if (arch_cpu_user_memset(s, c, count) < B_OK)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
return B_OK;
|
||||
|
||||
Reference in New Issue
Block a user