/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #ifdef _WIN32 # include # include # include #endif #ifdef ANDROID # include #endif #include #include #include "config_options.h" #include "osl/diagnose.h" #include "rtl/ustring.hxx" #include "rtl/ustrbuf.hxx" #include "osl/module.hxx" #include "osl/mutex.hxx" #include "osl/process.h" #include "osl/thread.hxx" #include "osl/file.hxx" #include "rtl/instance.hxx" #include "osl/getglobalmutex.hxx" #include #include #include #include "jni.h" #include "rtl/byteseq.hxx" #include "vendorplugin.hxx" #include "util.hxx" #include "sunversion.hxx" #include "vendorlist.hxx" #include "diagnostics.h" #ifdef MACOSX #include "util_cocoa.hxx" #endif #ifdef ANDROID #include #else #if !ENABLE_RUNTIME_OPTIMIZATIONS #define FORCE_INTERPRETED 1 #elif defined HAVE_VALGRIND_HEADERS #include #define FORCE_INTERPRETED RUNNING_ON_VALGRIND #else #define FORCE_INTERPRETED 0 #endif #endif #if defined LINUX && (defined X86 || defined X86_64) #include #endif using namespace osl; using namespace std; using namespace jfw_plugin; namespace { struct PluginMutex: public ::rtl::Static {}; #if defined(UNX) && !defined(ANDROID) OString getPluginJarPath( const OUString & sVendor, const OUString& sLocation, const OUString& sVersion) { OString ret; OUString sName1("javaplugin.jar"); OUString sName2("plugin.jar"); OUString sPath; if ( sVendor == "Sun Microsystems Inc." ) { SunVersion ver142("1.4.2-ea"); SunVersion ver150("1.5.0-ea"); SunVersion ver(sVersion); OSL_ASSERT(ver142 && ver150 && ver); OUString sName; if (ver < ver142) { sName = sName1; } else if (ver < ver150) {//this will cause ea, beta etc. to have plugin.jar in path. //but this does not harm. 1.5.0-beta < 1.5.0 sName = sName2; } if (!sName.isEmpty()) { sName = sLocation + "/lib/" + sName; OSL_VERIFY( osl_getSystemPathFromFileURL(sName.pData, & sPath.pData) == osl_File_E_None); } } else { OUString sName(sLocation + "/lib/" + sName1); OUString sPath1; OUString sPath2; if (osl_getSystemPathFromFileURL(sName.pData, & sPath1.pData) == osl_File_E_None) { sName = sLocation + "/lib/" + sName2; if (osl_getSystemPathFromFileURL(sName.pData, & sPath2.pData) == osl_File_E_None) { char sep[] = {SAL_PATHSEPARATOR, 0}; sPath = sPath1 + OUString::createFromAscii(sep) + sPath2; } } OSL_ASSERT(!sPath.isEmpty()); } ret = OUStringToOString(sPath, osl_getThreadTextEncoding()); return ret; } #endif // UNX JavaInfo* createJavaInfo(const rtl::Reference & info) { JavaInfo* pInfo = new JavaInfo; pInfo->sVendor = info->getVendor(); pInfo->sLocation = info->getHome(); pInfo->sVersion = info->getVersion(); pInfo->nFeatures = info->supportsAccessibility() ? 1 : 0; pInfo->nRequirements = info->needsRestart() ? JFW_REQUIRE_NEEDRESTART : 0; OUStringBuffer buf(1024); buf.append(info->getRuntimeLibrary()); if (!info->getLibraryPath().isEmpty()) { buf.append("\n"); buf.append(info->getLibraryPath()); buf.append("\n"); } OUString sVendorData = buf.makeStringAndClear(); pInfo->arVendorData = rtl::ByteSequence( reinterpret_cast(sVendorData.pData->buffer), sVendorData.getLength() * sizeof(sal_Unicode)); return pInfo; } OUString getRuntimeLib(const rtl::ByteSequence & data) { const sal_Unicode* chars = reinterpret_cast(data.getConstArray()); sal_Int32 len = data.getLength(); OUString sData(chars, len / 2); //the runtime lib is on the first line sal_Int32 index = 0; OUString aToken = sData.getToken( 0, '\n', index); return aToken; } jmp_buf jmp_jvm_abort; sig_atomic_t g_bInGetJavaVM = 0; extern "C" void JNICALL abort_handler() { // If we are within JNI_CreateJavaVM then we jump back into getJavaVM if( g_bInGetJavaVM != 0 ) { fprintf(stderr, "JavaVM: JNI_CreateJavaVM called os::abort(), caught by abort_handler in javavm.cxx\n"); longjmp( jmp_jvm_abort, 0); } } /** helper function to check Java version requirements This function checks if the Java version of the given VendorBase meets the given Java version requirements. @param aVendorInfo [in] the object to be inspected whether it meets the version requirements @param sMinVersion [in] represents the minimum version of a JRE. The string can be empty. @param sMaxVersion [in] represents the maximum version of a JRE. The string can be empty. @param arExcludeList [in] contains a list of "bad" versions. JREs which have one of these versions must not be returned by this function. It can be NULL. @param nLenList [in] the number of version strings contained in arExcludeList. @return javaPluginError::NONE the function ran successfully and the version requirements are met javaPluginError::FailedVersion at least one of the version requirements (minVersion, maxVersion, excludeVersions) was violated javaPluginError::WrongVersionFormat the version strings in sMinVersion,sMaxVersion,arExcludeList are not recognized as valid version strings. */ javaPluginError checkJavaVersionRequirements( rtl::Reference const & aVendorInfo, OUString const& sMinVersion, OUString const& sMaxVersion, rtl_uString * * arExcludeList, sal_Int32 nLenList) { if (!aVendorInfo->isValidArch()) { return javaPluginError::WrongArch; } if (!sMinVersion.isEmpty()) { try { if (aVendorInfo->compareVersions(sMinVersion) < 0) return javaPluginError::FailedVersion; } catch (MalformedVersionException&) { //The minVersion was not recognized as valid for this vendor. JFW_ENSURE( false, "[Java framework]sunjavaplugin does not know version: " + sMinVersion + " for vendor: " + aVendorInfo->getVendor() + " .Check minimum Version." ); return javaPluginError::WrongVersionFormat; } } if (!sMaxVersion.isEmpty()) { try { if (aVendorInfo->compareVersions(sMaxVersion) > 0) return javaPluginError::FailedVersion; } catch (MalformedVersionException&) { //The maxVersion was not recognized as valid for this vendor. JFW_ENSURE( false, "[Java framework]sunjavaplugin does not know version: " + sMaxVersion + " for vendor: " + aVendorInfo->getVendor() + " .Check maximum Version." ); return javaPluginError::WrongVersionFormat; } } for (int i = 0; i < nLenList; i++) { OUString sExVer(arExcludeList[i]); try { if (aVendorInfo->compareVersions(sExVer) == 0) return javaPluginError::FailedVersion; } catch (MalformedVersionException&) { //The excluded version was not recognized as valid for this vendor. JFW_ENSURE( false, "[Java framework]sunjavaplugin does not know version: " + sExVer + " for vendor: " + aVendorInfo->getVendor() + " .Check excluded versions." ); return javaPluginError::WrongVersionFormat; } } return javaPluginError::NONE; } } javaPluginError jfw_plugin_getAllJavaInfos( bool checkJavaHomeAndPath, OUString const& sVendor, OUString const& sMinVersion, OUString const& sMaxVersion, rtl_uString * *arExcludeList, sal_Int32 nLenList, JavaInfo*** parJavaInfo, sal_Int32 *nLenInfoList, std::vector> & infos) { OSL_ASSERT(parJavaInfo); OSL_ASSERT(nLenInfoList); if (!parJavaInfo || !nLenInfoList) return javaPluginError::InvalidArg; //nLenlist contains the number of elements in arExcludeList. //If no exclude list is provided then nLenList must be 0 OSL_ASSERT( ! (arExcludeList == nullptr && nLenList > 0)); if (arExcludeList == nullptr && nLenList > 0) return javaPluginError::InvalidArg; OSL_ASSERT(!sVendor.isEmpty()); if (sVendor.isEmpty()) return javaPluginError::InvalidArg; JavaInfo** arInfo = nullptr; //Find all JREs vector > vecInfos = addAllJREInfos(checkJavaHomeAndPath, infos); vector > vecVerifiedInfos; typedef vector >::iterator it; for (it i= vecInfos.begin(); i != vecInfos.end(); ++i) { const rtl::Reference& cur = *i; if (!sVendor.equals(cur->getVendor())) continue; javaPluginError err = checkJavaVersionRequirements( cur, sMinVersion, sMaxVersion, arExcludeList, nLenList); if (err == javaPluginError::FailedVersion || err == javaPluginError::WrongArch) continue; else if (err == javaPluginError::WrongVersionFormat) return err; vecVerifiedInfos.push_back(*i); } //Now vecVerifiedInfos contains all those JREs which meet the version requirements //Transfer them into the array that is passed out. arInfo = static_cast(rtl_allocateMemory(vecVerifiedInfos.size() * sizeof (JavaInfo*))); int j = 0; typedef vector >::const_iterator cit; for (cit ii = vecVerifiedInfos.begin(); ii != vecVerifiedInfos.end(); ++ii, ++j) { arInfo[j] = createJavaInfo(*ii); } *nLenInfoList = vecVerifiedInfos.size(); *parJavaInfo = arInfo; return javaPluginError::NONE; } javaPluginError jfw_plugin_getJavaInfoByPath( OUString const& sPath, OUString const& sVendor, OUString const& sMinVersion, OUString const& sMaxVersion, rtl_uString * *arExcludeList, sal_Int32 nLenList, JavaInfo ** ppInfo) { if (!ppInfo) return javaPluginError::InvalidArg; OSL_ASSERT(!sPath.isEmpty()); if (sPath.isEmpty()) return javaPluginError::InvalidArg; //nLenlist contains the number of elements in arExcludeList. //If no exclude list is provided then nLenList must be 0 OSL_ASSERT( ! (arExcludeList == nullptr && nLenList > 0)); if (arExcludeList == nullptr && nLenList > 0) return javaPluginError::InvalidArg; OSL_ASSERT(!sVendor.isEmpty()); if (sVendor.isEmpty()) return javaPluginError::InvalidArg; rtl::Reference aVendorInfo = getJREInfoByPath(sPath); if (!aVendorInfo.is()) return javaPluginError::NoJre; //Check if the detected JRE matches the version requirements if (!sVendor.equals(aVendorInfo->getVendor())) return javaPluginError::NoJre; javaPluginError errorcode = checkJavaVersionRequirements( aVendorInfo, sMinVersion, sMaxVersion, arExcludeList, nLenList); if (errorcode == javaPluginError::NONE) *ppInfo = createJavaInfo(aVendorInfo); return errorcode; } javaPluginError jfw_plugin_getJavaInfoFromJavaHome( std::vector> const& vecVendorInfos, JavaInfo ** ppInfo, std::vector> & infos) { if (!ppInfo) return javaPluginError::InvalidArg; std::vector> infoJavaHome; addJavaInfoFromJavaHome(infos, infoJavaHome); if (infoJavaHome.empty()) return javaPluginError::NoJre; assert(infoJavaHome.size() == 1); //Check if the detected JRE matches the version requirements typedef std::vector>::const_iterator ci_pl; for (ci_pl vendorInfo = vecVendorInfos.begin(); vendorInfo != vecVendorInfos.end(); ++vendorInfo) { const OUString& vendor = vendorInfo->first; jfw::VersionInfo versionInfo = vendorInfo->second; if (vendor.equals(infoJavaHome[0]->getVendor())) { javaPluginError errorcode = checkJavaVersionRequirements( infoJavaHome[0], versionInfo.sMinVersion, versionInfo.sMaxVersion, versionInfo.getExcludeVersions(), versionInfo.getExcludeVersionSize()); if (errorcode == javaPluginError::NONE) { *ppInfo = createJavaInfo(infoJavaHome[0]); return javaPluginError::NONE; } } } return javaPluginError::NoJre; } javaPluginError jfw_plugin_getJavaInfosFromPath( std::vector> const& vecVendorInfos, std::vector & javaInfosFromPath, std::vector> & infos) { // find JREs from PATH vector> vecInfosFromPath; addJavaInfosFromPath(infos, vecInfosFromPath); vector vecVerifiedInfos; // copy infos of JREs that meet version requirements to vecVerifiedInfos typedef vector >::iterator it; for (it i= vecInfosFromPath.begin(); i != vecInfosFromPath.end(); ++i) { const rtl::Reference& currentInfo = *i; typedef std::vector>::const_iterator ci_pl; for (ci_pl vendorInfo = vecVendorInfos.begin(); vendorInfo != vecVendorInfos.end(); ++vendorInfo) { const OUString& vendor = vendorInfo->first; jfw::VersionInfo versionInfo = vendorInfo->second; if (vendor.equals(currentInfo->getVendor())) { javaPluginError errorcode = checkJavaVersionRequirements( currentInfo, versionInfo.sMinVersion, versionInfo.sMaxVersion, versionInfo.getExcludeVersions(), versionInfo.getExcludeVersionSize()); if (errorcode == javaPluginError::NONE) { vecVerifiedInfos.push_back(createJavaInfo(currentInfo)); } } } } if (vecVerifiedInfos.empty()) return javaPluginError::NoJre; javaInfosFromPath = vecVerifiedInfos; return javaPluginError::NONE; } #if defined(_WIN32) // Load msvcr71.dll using an explicit full path from where it is // present as bundled with the JRE. In case it is not found where we // think it should be, do nothing, and just let the implicit loading // that happens when loading the JVM take care of it. static void load_msvcr(LPCWSTR jvm_dll, wchar_t const* msvcr) { wchar_t msvcr_dll[MAX_PATH]; wchar_t *slash; if (wcslen(jvm_dll) > MAX_PATH - 15) return; wcscpy(msvcr_dll, jvm_dll); // First check if msvcr71.dll is in the same folder as jvm.dll. It // normally isn't, at least up to 1.6.0_22, but who knows if it // might be in the future. slash = wcsrchr(msvcr_dll, L'\\'); if (!slash) { // Huh, weird path to jvm.dll. Oh well. return; } wcscpy(slash+1, msvcr); if (LoadLibraryW(msvcr_dll)) return; // Then check if msvcr71.dll is in the parent folder of where // jvm.dll is. That is currently (1.6.0_22) as far as I know the // normal case. *slash = 0; slash = wcsrchr(msvcr_dll, L'\\'); if (!slash) return; wcscpy(slash+1, msvcr); LoadLibraryW(msvcr_dll); } // Check if the jvm DLL imports msvcr71.dll, and in that case try // loading it explicitly. In case something goes wrong, do nothing, // and just let the implicit loading try to take care of it. static void do_msvcr_magic(rtl_uString *jvm_dll) { rtl_uString* Module(nullptr); struct stat st; oslFileError nError = osl_getSystemPathFromFileURL(jvm_dll, &Module); if ( osl_File_E_None != nError ) rtl_uString_assign(&Module, jvm_dll); FILE *f = _wfopen(reinterpret_cast(Module->buffer), L"rb"); if (!f) return; if (fstat(fileno(f), &st) == -1) { fclose(f); return; } PIMAGE_DOS_HEADER dos_hdr = static_cast(malloc(st.st_size)); if (fread(dos_hdr, st.st_size, 1, f) != 1 || memcmp(dos_hdr, "MZ", 2) != 0 || dos_hdr->e_lfanew < 0 || dos_hdr->e_lfanew > (LONG) (st.st_size - sizeof(IMAGE_NT_HEADERS))) { free(dos_hdr); fclose(f); return; } fclose(f); IMAGE_NT_HEADERS *nt_hdr = reinterpret_cast(reinterpret_cast(dos_hdr) + dos_hdr->e_lfanew); DWORD importsVA = nt_hdr->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; // first determine Virtual-to-File-address mapping for the section // that contains the import directory IMAGE_SECTION_HEADER *sections = IMAGE_FIRST_SECTION(nt_hdr); ptrdiff_t VAtoPhys = -1; for (int i = 0; i < nt_hdr->FileHeader.NumberOfSections; ++i) { if (sections->VirtualAddress <= importsVA && importsVA < sections->VirtualAddress + sections->SizeOfRawData) { VAtoPhys = static_cast(sections->PointerToRawData) - static_cast(sections->VirtualAddress); break; } ++sections; } if (-1 == VAtoPhys) // not found? { free(dos_hdr); return; } IMAGE_IMPORT_DESCRIPTOR *imports = reinterpret_cast(reinterpret_cast(dos_hdr) + importsVA + VAtoPhys); while (imports <= reinterpret_cast(reinterpret_cast(dos_hdr) + st.st_size - sizeof (IMAGE_IMPORT_DESCRIPTOR)) && imports->Name != 0 && imports->Name + VAtoPhys < (DWORD) st.st_size) { static struct { char const * name; wchar_t const * wname; } msvcrts[] = { { "msvcr71.dll" , L"msvcr71.dll" }, { "msvcr100.dll", L"msvcr100.dll" }, }; char const* importName = reinterpret_cast(dos_hdr) + imports->Name + VAtoPhys; for (size_t i = 0; i < SAL_N_ELEMENTS(msvcrts); ++i) { if (0 == strnicmp(importName, // Intentional strlen() + 1 here to include terminating zero msvcrts[i].name, strlen(msvcrts[i].name) + 1)) { load_msvcr(reinterpret_cast(Module->buffer), msvcrts[i].wname); free(dos_hdr); return; } } imports++; } free(dos_hdr); } #endif /** starts a Java Virtual Machine.

The function shall ensure, that the VM does not abort the process during instantiation.

*/ javaPluginError jfw_plugin_startJavaVirtualMachine( const JavaInfo *pInfo, const JavaVMOption* arOptions, sal_Int32 cOptions, JavaVM ** ppVm, JNIEnv ** ppEnv) { // unless guard is volatile the following warning occurs on gcc: // warning: variable 't' might be clobbered by `longjmp' or `vfork' volatile osl::MutexGuard guard(PluginMutex::get()); // unless errorcode is volatile the following warning occurs on gcc: // warning: variable 'errorcode' might be clobbered by `longjmp' or `vfork' volatile javaPluginError errorcode = javaPluginError::NONE; if ( pInfo == nullptr || ppVm == nullptr || ppEnv == nullptr) return javaPluginError::InvalidArg; //Check if the Vendor (pInfo->sVendor) is supported by this plugin if ( ! isVendorSupported(pInfo->sVendor)) return javaPluginError::WrongVendor; #ifdef MACOSX rtl::Reference aVendorInfo = getJREInfoByPath( OUString( pInfo->sLocation ) ); if ( !aVendorInfo.is() || aVendorInfo->compareVersions( OUString( pInfo->sVersion ) ) < 0 ) return javaPluginError::VmCreationFailed; #endif OUString sRuntimeLib = getRuntimeLib(pInfo->arVendorData); #ifdef MACOSX if ( !JvmfwkUtil_isLoadableJVM( sRuntimeLib ) ) return javaPluginError::VmCreationFailed; #endif JFW_TRACE2("Using Java runtime library: " << sRuntimeLib); #ifndef ANDROID // On linux we load jvm with RTLD_GLOBAL. This is necessary for debugging, because // libjdwp.so need a symbol (fork1) from libjvm which it only gets if the jvm is loaded // with RTLD_GLOBAL. On Solaris libjdwp.so is correctly linked with libjvm.so osl::Module moduleRt; #if defined(LINUX) if (!moduleRt.load(sRuntimeLib, SAL_LOADMODULE_GLOBAL | SAL_LOADMODULE_NOW)) #elif defined MACOSX // Must be SAL_LOADMODULE_GLOBAL when e.g. specifying a // -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 option to // JDK 1.8.0_121 at least, as JNI_CreateJavaVM -> Threads::create_vm -> // JvmtiExport::post_vm_initialized -> cbEarlyVMInit -> initialize -> // util_initialize -> sun.misc.VMSupport.getAgentProperties -> // Java_sun_misc_VMSupport_initAgentProperties -> // JDK_FindJvmEntry("JVM_INitAgentProperties") -> // dlsym(RTLD_DEFAULT, "JVM_INitAgentProperties"): if (!moduleRt.load(sRuntimeLib, SAL_LOADMODULE_GLOBAL)) #else #if defined(_WIN32) do_msvcr_magic(sRuntimeLib.pData); #endif if (!moduleRt.load(sRuntimeLib)) #endif { JFW_ENSURE(false, "[Java framework]sunjavaplugin" SAL_DLLEXTENSION " could not load Java runtime library: \n" + sRuntimeLib + "\n"); JFW_TRACE0("Could not load Java runtime library: " << sRuntimeLib); return javaPluginError::VmCreationFailed; } #if defined UNX && !defined MACOSX //Setting the JAVA_HOME is needed for awt OUString sPathLocation; osl::FileBase::getSystemPathFromFileURL(pInfo->sLocation, sPathLocation); osl_setEnvironment(OUString("JAVA_HOME").pData, sPathLocation.pData); #endif typedef jint JNICALL JNI_CreateVM_Type(JavaVM **, JNIEnv **, void *); OUString sSymbolCreateJava("JNI_CreateJavaVM"); JNI_CreateVM_Type * pCreateJavaVM = reinterpret_cast(moduleRt.getFunctionSymbol(sSymbolCreateJava)); if (!pCreateJavaVM) { OSL_ASSERT(false); OString sLib = OUStringToOString( sRuntimeLib, osl_getThreadTextEncoding()); OString sSymbol = OUStringToOString( sSymbolCreateJava, osl_getThreadTextEncoding()); fprintf(stderr,"[Java framework]sunjavaplugin" SAL_DLLEXTENSION "Java runtime library: %s does not export symbol %s !\n", sLib.getStr(), sSymbol.getStr()); return javaPluginError::VmCreationFailed; } moduleRt.release(); // Valgrind typically emits many false errors when executing JIT'ed JVM // code, so force the JVM into interpreted mode: bool addForceInterpreted = FORCE_INTERPRETED > 0; // Some testing with Java 1.4 showed that JavaVMOption.optionString has to // be encoded with the system encoding (i.e., osl_getThreadTextEncoding): JavaVMInitArgs vm_args; struct Option { Option(OString const & theOptionString, void * theExtraInfo): optionString(theOptionString), extraInfo(theExtraInfo) {} OString optionString; void * extraInfo; }; std::vector