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:
Paweł Dziepak
2014-09-14 22:39:07 +02:00
parent e81b792e8f
commit 95e97463d2
10 changed files with 170 additions and 170 deletions
-7
View File
@@ -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
+57
View File
@@ -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
+2 -1
View File
@@ -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 */
+6 -6
View File
@@ -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,
+4 -3
View File
@@ -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;
}
+6 -6
View File
@@ -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,
-136
View File
@@ -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)
+4 -3
View File
@@ -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;
}
+5 -8
View File
@@ -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;