blob: 1773ed53f7e1551dfbadf98a63e9c2292dff9b2c [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "native_client/tests/thread_suspension/suspend_test.h"
#include <assert.h>
#include <setjmp.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/untrusted/nacl/syscall_bindings_trampoline.h"
#include "native_client/tests/common/register_set.h"
typedef int (*TYPE_nacl_test_syscall_1)(struct SuspendTestShm *test_shm);
__thread jmp_buf return_jmp_buf;
__thread struct SuspendTestShm *thread_test_shm;
static void MutatorThread(struct SuspendTestShm *test_shm) {
uint32_t next_val = 0;
while (!test_shm->should_exit) {
test_shm->var = next_val++;
}
}
static void SyscallReturnThread(struct SuspendTestShm *test_shm) {
int rc = NACL_SYSCALL(test_syscall_1)(test_shm);
assert(rc == 0);
/* Set value to indicate that the syscall returned. */
test_shm->var = 99;
}
static void SyscallInvokerThread(struct SuspendTestShm *test_shm) {
uint32_t next_val = 0;
while (!test_shm->should_exit) {
NACL_SYSCALL(null)();
test_shm->var = next_val++;
}
}
void spin_instruction(void);
REGS_SAVER_FUNC(ContinueAfterSuspension, CheckSavedRegisters);
void CheckSavedRegisters(struct NaClSignalContext *regs) {
RegsAssertEqual(regs, &thread_test_shm->expected_regs);
_exit(0);
}
static void RegisterSetterThread(struct SuspendTestShm *test_shm) {
struct NaClSignalContext *regs = &test_shm->expected_regs;
char stack[0x10000];
RegsFillTestValues(regs, /* seed= */ 0);
regs->stack_ptr = (uintptr_t) stack + sizeof(stack);
regs->prog_ctr = (uintptr_t) spin_instruction;
RegsApplySandboxConstraints(regs);
thread_test_shm = test_shm;
/*
* Set registers to known test values and then spin. We do not
* block by entering a NaCl syscall because that would disturb the
* register state.
*/
test_shm->continue_after_suspension_func =
(uintptr_t) ContinueAfterSuspension;
assert(offsetof(struct SuspendTestShm, var) == 0);
#if defined(__i386__)
regs->eax = (uintptr_t) test_shm;
ASM_WITH_REGS(
regs,
/* Align to ensure no NOPs are inserted in the code that follows. */
".p2align 5\n"
/* Set "test_shm->var = test_shm" to indicate that we are ready. */
"movl %%eax, (%%eax)\n"
"spin_instruction:\n"
"jmp spin_instruction\n");
#elif defined(__x86_64__)
regs->rax = (uintptr_t) test_shm;
ASM_WITH_REGS(
regs,
/* Align to ensure no NOPs are inserted in the code that follows. */
".p2align 5\n"
/* Set "test_shm->var = test_shm" to indicate that we are ready. */
"movl %%eax, %%nacl:(%%r15, %%rax)\n"
"spin_instruction:\n"
"jmp spin_instruction\n");
#elif defined(__arm__)
regs->r0 = (uintptr_t) test_shm;
ASM_WITH_REGS(
regs,
/* Align to ensure no NOPs are inserted in the code that follows. */
".p2align 4\n"
/* Set "test_shm->var = test_shm" to indicate that we are ready. */
"bic r0, r0, #0xc0000000\n"
"str r0, [r0]\n"
"spin_instruction:\n"
"b spin_instruction\n");
#elif defined(__mips__)
regs->a0 = (uintptr_t) test_shm;
ASM_WITH_REGS(
regs,
/* Align to ensure no NOPs are inserted in the code that follows. */
".p2align 4\n"
/* Set "test_shm->var = test_shm" to indicate that we are ready. */
"and $a0, $a0, $t7\n"
"sw $a0, 0($a0)\n"
".global spin_instruction\n"
"spin_instruction:\n"
"b spin_instruction\n"
"nop\n");
#else
# error Unsupported architecture
#endif
assert(!"Should not reach here");
}
static void ContinueAfterSyscall(void) {
longjmp(return_jmp_buf, 1);
}
/* Set registers to known values and enter a NaCl syscall. */
static void SyscallRegisterSetterThread(struct SuspendTestShm *test_shm) {
struct NaClSignalContext call_regs;
char stack[0x10000];
RegsFillTestValues(&call_regs, /* seed= */ 0);
call_regs.stack_ptr = (uintptr_t) stack + sizeof(stack);
call_regs.prog_ctr = (uintptr_t) ContinueAfterSyscall;
RegsApplySandboxConstraints(&call_regs);
/*
* call_regs are the registers we set on entry to the syscall.
* expected_regs are the registers that should be reported by
* NaClAppThreadGetSuspendedRegisters(). Since not all registers
* are saved when entering a syscall, expected_regs will be the same
* as call_regs but with various registers zeroed out.
*/
test_shm->expected_regs = call_regs;
RegsUnsetNonCalleeSavedRegisters(&test_shm->expected_regs);
uintptr_t syscall_addr = (uintptr_t) NACL_SYSCALL(test_syscall_1);
if (!setjmp(return_jmp_buf)) {
#if defined(__i386__)
test_shm->expected_regs.stack_ptr -= 4; /* Account for argument */
call_regs.eax = syscall_addr;
call_regs.ecx = (uintptr_t) test_shm; /* Scratch register */
ASM_WITH_REGS(
&call_regs,
"push %%ecx\n" /* Push syscall argument */
"push $ContinueAfterSyscall\n" /* Push return address */
"nacljmp %%eax\n");
#elif defined(__x86_64__)
call_regs.rax = syscall_addr;
call_regs.rdi = (uintptr_t) test_shm; /* Set syscall argument */
ASM_WITH_REGS(
&call_regs,
"push $ContinueAfterSyscall\n" /* Push return address */
"nacljmp %%eax, %%r15\n");
#elif defined(__arm__)
call_regs.r0 = (uintptr_t) test_shm; /* Set syscall argument */
call_regs.r1 = syscall_addr; /* Scratch register */
call_regs.lr = (uintptr_t) ContinueAfterSyscall;
ASM_WITH_REGS(
&call_regs,
"bic r1, r1, #0xf000000f\n"
"bx r1\n");
#elif defined(__mips__)
call_regs.a0 = (uintptr_t) test_shm; /* Set syscall argument */
call_regs.t9 = syscall_addr; /* Scratch register */
call_regs.return_addr = (uintptr_t) ContinueAfterSyscall;
ASM_WITH_REGS(
&call_regs,
"and $t9, $t9, $t6\n"
"jr $t9\n"
"nop\n");
#else
# error Unsupported architecture
#endif
assert(!"Should not reach here");
}
}
void SyscallReturnAddress(void);
#if defined(__i386__)
__asm__(".pushsection .text, \"ax\", @progbits\n"
"SyscallLoop:"
"naclcall %esi\n"
"SyscallReturnAddress:\n"
"jmp SyscallLoop\n"
".popsection\n");
#elif defined(__x86_64__)
__asm__(".pushsection .text, \"ax\", @progbits\n"
"SyscallLoop:\n"
/* Call via a temporary register so as not to modify %r12. */
"mov %r12d, %eax\n"
"naclcall %eax, %r15\n"
"SyscallReturnAddress:\n"
"jmp SyscallLoop\n"
".popsection\n");
#elif defined(__arm__)
__asm__(".pushsection .text, \"ax\", %progbits\n"
".p2align 4\n"
"SyscallReturnAddress:\n"
"adr lr, SyscallReturnAddress\n"
"bic r4, r4, #0xc000000f\n"
"bx r4\n"
".popsection\n");
#elif defined(__mips__)
__asm__(".pushsection .text, \"ax\", %progbits\n"
".p2align 4\n"
".global SyscallReturnAddress\n"
"SyscallReturnAddress:\n"
"lui $ra, %hi(SyscallReturnAddress)\n"
"addiu $ra, $ra, %lo(SyscallReturnAddress)\n"
"and $s0, $s0, $t6\n"
"jr $s0\n"
"nop\n"
".popsection\n");
#else
# error Unsupported architecture
#endif
/*
* Set registers to known values and call a NaCl syscall in an
* infinite loop. This is used for testing that the same register
* state is reported while the thread is in untrusted code or inside
* the syscall.
*/
static void SyscallRegisterSetterLoopThread(struct SuspendTestShm *test_shm) {
struct NaClSignalContext *regs = &test_shm->expected_regs;
char stack[0x10000];
RegsFillTestValues(regs, /* seed= */ 0);
regs->stack_ptr = (uintptr_t) stack + sizeof(stack);
regs->prog_ctr = (uintptr_t) SyscallReturnAddress;
RegsApplySandboxConstraints(regs);
RegsUnsetNonCalleeSavedRegisters(regs);
uintptr_t syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_test_syscall_2);
#if defined(__i386__)
regs->esi = syscall_addr;
#elif defined(__x86_64__)
regs->r12 = syscall_addr;
#elif defined(__arm__)
regs->r4 = syscall_addr;
#elif defined(__mips__)
regs->s0 = syscall_addr;
#else
# error Unsupported architecture
#endif
JUMP_WITH_REGS(regs, SyscallReturnAddress);
}
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Expected 2 arguments: <test-type> <memory-address>\n");
return 1;
}
char *test_type = argv[1];
char *end;
struct SuspendTestShm *test_shm =
(struct SuspendTestShm *) strtoul(argv[2], &end, 0);
assert(*end == '\0');
if (strcmp(test_type, "MutatorThread") == 0) {
MutatorThread(test_shm);
} else if (strcmp(test_type, "SyscallReturnThread") == 0) {
SyscallReturnThread(test_shm);
} else if (strcmp(test_type, "SyscallInvokerThread") == 0) {
SyscallInvokerThread(test_shm);
} else if (strcmp(test_type, "RegisterSetterThread") == 0) {
RegisterSetterThread(test_shm);
} else if (strcmp(test_type, "SyscallRegisterSetterThread") == 0) {
SyscallRegisterSetterThread(test_shm);
} else if (strcmp(test_type, "SyscallRegisterSetterLoopThread") == 0) {
SyscallRegisterSetterLoopThread(test_shm);
} else {
fprintf(stderr, "Unknown test type: %s\n", test_type);
return 1;
}
return 0;
}