jdk-24/test/hotspot/jtreg/runtime/StackGuardPages/exeinvoke.c
2023-02-14 13:11:44 +00:00

338 lines
9.6 KiB
C

/*
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* This code tests the fact that we actually remove stack guard page when calling
* JavaThread::exit() i.e. when detaching from current thread.
* We overflow the stack and check that we get access error because of a guard page.
* Than we detach from vm thread and overflow stack once again. This time we shouldn't
* get access error because stack guard page is removed
*
* Notice: due a complicated interaction of signal handlers, the test may crash.
* It's OK - don't file a bug.
*/
#include <assert.h>
#include <jni.h>
#include <jvm.h>
#include <alloca.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/ucontext.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <pthread.h>
#define CLASS_PATH_OPT "-Djava.class.path="
JavaVM* _jvm;
static jmp_buf context;
static volatile int _last_si_code = -1;
static volatile int _failures = 0;
static volatile int _rec_count = 0; // Number of allocations to hit stack guard page
static volatile int _kp_rec_count = 0; // Kept record of rec_count, for retrying
static int _peek_value = 0; // Used for accessing memory to cause SIGSEGV
pid_t gettid() {
return (pid_t) syscall(SYS_gettid);
}
static void handler(int sig, siginfo_t *si, void *unused) {
_last_si_code = si->si_code;
printf("Got SIGSEGV(%d) at address: 0x%lx\n",si->si_code, (long) si->si_addr);
longjmp(context, 1);
}
static char* altstack = NULL;
void set_signal_handler() {
if (altstack == NULL) {
// Dynamically allocated in case SIGSTKSZ is not constant
altstack = (char*)malloc(SIGSTKSZ);
if (altstack == NULL) {
fprintf(stderr, "Test ERROR. Unable to malloc altstack space\n");
exit(7);
}
}
stack_t ss = {
.ss_size = SIGSTKSZ,
.ss_flags = 0,
.ss_sp = altstack
};
struct sigaction sa = {
.sa_sigaction = handler,
.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESETHAND
};
_last_si_code = -1;
sigaltstack(&ss, 0);
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
fprintf(stderr, "Test ERROR. Can't set sigaction (%d)\n", errno);
exit(7);
}
}
size_t get_java_stacksize () {
pthread_attr_t attr;
JDK1_1InitArgs jdk_args;
memset(&jdk_args, 0, (sizeof jdk_args));
jdk_args.version = JNI_VERSION_1_1;
JNI_GetDefaultJavaVMInitArgs(&jdk_args);
if (jdk_args.javaStackSize <= 0) {
fprintf(stderr, "Test ERROR. Can't get a valid value for the default stacksize.\n");
exit(7);
}
return jdk_args.javaStackSize;
}
// Call DoOverflow::`method` on JVM
void call_method_on_jvm(const char* method) {
JNIEnv *env;
jclass class_id;
jmethodID method_id;
int res;
res = (*_jvm)->AttachCurrentThread(_jvm, (void **)&env, NULL);
if (res != JNI_OK) {
fprintf(stderr, "Test ERROR. Can't attach to current thread\n");
exit(7);
}
class_id = (*env)->FindClass(env, "DoOverflow");
if (class_id == NULL) {
fprintf(stderr, "Test ERROR. Can't load class DoOverflow\n");
exit(7);
}
method_id = (*env)->GetStaticMethodID(env, class_id, method, "()V");
if (method_id == NULL) {
fprintf(stderr, "Test ERROR. Can't find method DoOverflow.%s\n", method);
exit(7);
}
(*env)->CallStaticVoidMethod(env, class_id, method_id, NULL);
}
void *run_java_overflow (void *p) {
volatile int res;
call_method_on_jvm("printIt");
res = (*_jvm)->DetachCurrentThread(_jvm);
if (res != JNI_OK) {
fprintf(stderr, "Test ERROR. Can't call detach from current thread\n");
exit(7);
}
return NULL;
}
void do_overflow(){
volatile int *p = NULL;
if (_kp_rec_count == 0 || _rec_count < _kp_rec_count) {
for(;;) {
_rec_count++;
p = (int*)alloca(128);
_peek_value = p[0]; // Peek
}
}
}
void *run_native_overflow(void *p) {
// Test that stack guard page is correctly set for initial and non initial thread
// and correctly removed for the initial thread
volatile int res;
printf("run_native_overflow %ld\n", (long) gettid());
call_method_on_jvm("printAlive");
// Initialize statics used in do_overflow
_kp_rec_count = 0;
_rec_count = 0;
set_signal_handler();
if (! setjmp(context)) {
do_overflow();
}
if (_last_si_code == SEGV_ACCERR) {
printf("Test PASSED. Got access violation accessing guard page at %d\n", _rec_count);
}
res = (*_jvm)->DetachCurrentThread(_jvm);
if (res != JNI_OK) {
fprintf(stderr, "Test ERROR. Can't call detach from current thread\n");
exit(7);
}
if (getpid() != gettid()) {
// For non-initial thread we don't unmap the region but call os::uncommit_memory and keep PROT_NONE
// so if host has enough swap space we will get the same SEGV with code SEGV_ACCERR(2) trying
// to access it as if the guard page is present.
// We have no way to check this, so bail out, marking test as succeeded
printf("Test PASSED. Not initial thread\n");
return NULL;
}
// Limit depth of recursion for second run. It can't exceed one for first run.
_kp_rec_count = _rec_count;
_rec_count = 0;
set_signal_handler();
if (! setjmp(context)) {
do_overflow();
}
if (_last_si_code == SEGV_ACCERR) {
++ _failures;
fprintf(stderr,"Test FAILED. Stack guard page is still there at %d\n", _rec_count);
} else if (_last_si_code == -1) {
printf("Test PASSED. No stack guard page is present. Maximum recursion level reached at %d\n", _rec_count);
}
else{
printf("Test PASSED. No stack guard page is present. SIGSEGV(%d) at %d\n", _last_si_code, _rec_count);
}
return NULL;
}
void usage() {
fprintf(stderr, "Usage: invoke test_java_overflow\n");
fprintf(stderr, " invoke test_java_overflow_initial\n");
fprintf(stderr, " invoke test_native_overflow\n");
fprintf(stderr, " invoke test_native_overflow_initial\n");
}
void init_thread_or_die(pthread_t *thr, pthread_attr_t *thread_attr) {
size_t stack_size = get_java_stacksize();
if (pthread_attr_init(thread_attr) != 0 ||
pthread_attr_setstacksize(thread_attr, stack_size) != 0) {
printf("Failed to set stacksize. Exiting test.\n");
exit(0);
}
}
int main (int argc, const char** argv) {
JavaVMInitArgs vm_args;
JavaVMOption options[3];
JNIEnv* env;
int optlen;
char *javaclasspath = NULL;
char javaclasspathopt[4096];
printf("Test started with pid: %ld\n", (long) getpid());
/* set the java class path so the DoOverflow class can be found */
javaclasspath = getenv("CLASSPATH");
if (javaclasspath == NULL) {
fprintf(stderr, "Test ERROR. CLASSPATH is not set\n");
exit(7);
}
optlen = strlen(CLASS_PATH_OPT) + strlen(javaclasspath) + 1;
if (optlen > 4096) {
fprintf(stderr, "Test ERROR. CLASSPATH is too long\n");
exit(7);
}
snprintf(javaclasspathopt, sizeof(javaclasspathopt), "%s%s",
CLASS_PATH_OPT, javaclasspath);
options[0].optionString = "-Xint";
options[1].optionString = "-Xss1M";
options[2].optionString = javaclasspathopt;
vm_args.version = JNI_VERSION_1_2;
vm_args.ignoreUnrecognized = JNI_TRUE;
vm_args.options = options;
vm_args.nOptions = 3;
if (JNI_CreateJavaVM (&_jvm, (void **)&env, &vm_args) < 0 ) {
fprintf(stderr, "Test ERROR. Can't create JavaVM\n");
exit(7);
}
pthread_t thr;
pthread_attr_t thread_attr;
if (argc < 2) {
fprintf(stderr, "No test selected");
usage();
exit(7);
}
if (strcmp(argv[1], "test_java_overflow_initial") == 0) {
printf("\nTesting JAVA_OVERFLOW\n");
printf("Testing stack guard page behaviour for initial thread\n");
run_java_overflow(NULL);
// This test crash on error
exit(0);
}
if (strcmp(argv[1], "test_java_overflow") == 0) {
init_thread_or_die(&thr, &thread_attr);
printf("\nTesting JAVA_OVERFLOW\n");
printf("Testing stack guard page behaviour for other thread\n");
pthread_create(&thr, &thread_attr, run_java_overflow, NULL);
pthread_join(thr, NULL);
// This test crash on error
exit(0);
}
if (strcmp(argv[1], "test_native_overflow_initial") == 0) {
printf("\nTesting NATIVE_OVERFLOW\n");
printf("Testing stack guard page behaviour for initial thread\n");
run_native_overflow(NULL);
exit((_failures > 0) ? 1 : 0);
}
if (strcmp(argv[1], "test_native_overflow") == 0) {
init_thread_or_die(&thr, &thread_attr);
printf("\nTesting NATIVE_OVERFLOW\n");
printf("Testing stack guard page behaviour for other thread\n");
pthread_create(&thr, &thread_attr, run_native_overflow, NULL);
pthread_join(thr, NULL);
exit((_failures > 0) ? 1 : 0);
}
fprintf(stderr, "Test ERROR. Unknown parameter %s\n", ((argc > 1) ? argv[1] : "none"));
usage();
exit(7);
}