/* -*- 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 # if !defined WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # include #endif #ifdef ANDROID # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) { sPath = sPath1 + OUStringLiteral1(SAL_PATHSEPARATOR) + sPath2; } } OSL_ASSERT(!sPath.isEmpty()); } ret = OUStringToOString(sPath, osl_getThreadTextEncoding()); return ret; } #endif // UNX std::unique_ptr createJavaInfo( const rtl::Reference & info) { 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(); return std::unique_ptr( new JavaInfo{ info->getVendor(), info->getHome(), info->getVersion(), sal_uInt64(info->supportsAccessibility() ? 1 : 0), sal_uInt64(info->needsRestart() ? JFW_REQUIRE_NEEDRESTART : 0), rtl::ByteSequence( reinterpret_cast(sVendorData.pData->buffer), sVendorData.getLength() * sizeof(sal_Unicode))}); } 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. @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, std::vector const & arExcludeList) { 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 (auto const & sExVer: arExcludeList) { 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, jfw::VendorSettings const & vendorSettings, std::vector>* parJavaInfo, std::vector> & infos) { assert(parJavaInfo); //Find all JREs vector > vecInfos = addAllJREInfos(checkJavaHomeAndPath, infos); vector > vecVerifiedInfos; for (auto const& vecInfo : vecInfos) { if (auto const versionInfo = vendorSettings.getVersionInformation(vecInfo->getVendor())) { javaPluginError err = checkJavaVersionRequirements( vecInfo, versionInfo->sMinVersion, versionInfo->sMaxVersion, versionInfo->vecExcludeVersions); if (err == javaPluginError::FailedVersion || err == javaPluginError::WrongArch) continue; else if (err == javaPluginError::WrongVersionFormat) return err; } vecVerifiedInfos.push_back(vecInfo); } //Now vecVerifiedInfos contains all those JREs which meet the version requirements //Transfer them into the array that is passed out. parJavaInfo->clear(); for (auto const& vecVerifiedInfo : vecVerifiedInfos) { parJavaInfo->push_back(createJavaInfo(vecVerifiedInfo)); } return javaPluginError::NONE; } javaPluginError jfw_plugin_getJavaInfoByPath( OUString const& sPath, jfw::VendorSettings const & vendorSettings, std::unique_ptr * ppInfo) { assert(ppInfo != nullptr); OSL_ASSERT(!sPath.isEmpty()); if (sPath.isEmpty()) return javaPluginError::InvalidArg; rtl::Reference aVendorInfo = getJREInfoByPath(sPath); if (!aVendorInfo.is()) return javaPluginError::NoJre; //Check if the detected JRE matches the version requirements javaPluginError errorcode = javaPluginError::NONE; if (auto const versionInfo = vendorSettings.getVersionInformation(aVendorInfo->getVendor())) { errorcode = checkJavaVersionRequirements( aVendorInfo, versionInfo->sMinVersion, versionInfo->sMaxVersion, versionInfo->vecExcludeVersions); } if (errorcode == javaPluginError::NONE) *ppInfo = createJavaInfo(aVendorInfo); return errorcode; } javaPluginError jfw_plugin_getJavaInfoFromJavaHome( jfw::VendorSettings const & vendorSettings, std::unique_ptr * ppInfo, std::vector> & infos) { assert(ppInfo); 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 auto const versionInfo = vendorSettings.getVersionInformation(infoJavaHome[0]->getVendor()); if (!versionInfo || (checkJavaVersionRequirements( infoJavaHome[0], versionInfo->sMinVersion, versionInfo->sMaxVersion, versionInfo->vecExcludeVersions) == javaPluginError::NONE)) { *ppInfo = createJavaInfo(infoJavaHome[0]); return javaPluginError::NONE; } return javaPluginError::NoJre; } javaPluginError jfw_plugin_getJavaInfosFromPath( jfw::VendorSettings const & vendorSettings, 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 for (auto const& infosFromPath : vecInfosFromPath) { auto const versionInfo = vendorSettings.getVersionInformation(infosFromPath->getVendor()); if (!versionInfo || (checkJavaVersionRequirements( infosFromPath, versionInfo->sMinVersion, versionInfo->sMaxVersion, versionInfo->vecExcludeVersions) == javaPluginError::NONE)) { vecVerifiedInfos.push_back(createJavaInfo(infosFromPath)); } } if (vecVerifiedInfos.empty()) return javaPluginError::NoJre; javaInfosFromPath = std::move(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(OUString const & jvm_dll, OUStringLiteral msvcr) { // 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. sal_Int32 slash = jvm_dll.lastIndexOf('\\'); if (slash == -1) { // Huh, weird path to jvm.dll. Oh well. SAL_WARN("jfw", "JVM pathname <" + jvm_dll + "> w/o backslash"); return; } if (LoadLibraryW( o3tl::toW(OUString(jvm_dll.copy(0, slash+1) + msvcr).getStr()))) 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 = jvm_dll.lastIndexOf('\\', slash); if (slash == -1) return; LoadLibraryW( o3tl::toW(OUString(jvm_dll.copy(0, slash+1) + msvcr).getStr())); } // 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(OUString const &jvm_dll) { struct stat st; OUString Module; osl::FileBase::RC nError = osl::FileBase::getSystemPathFromFileURL( jvm_dll, Module); if ( osl::FileBase::E_None != nError ) { SAL_WARN( "jfw", "getSystemPathFromFileURL(" << jvm_dll << "): " << +nError); return; } FILE *f = _wfopen(o3tl::toW(Module.getStr()), L"rb"); if (!f) { SAL_WARN("jfw", "_wfopen(" << Module << ") failed"); return; } if (fstat(fileno(f), &st) == -1) { SAL_WARN("jfw", "fstat(" << Module << ") failed"); 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 > static_cast(st.st_size - sizeof(IMAGE_NT_HEADERS))) { SAL_WARN("jfw", "analyzing <" << Module << "> failed"); 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? { SAL_WARN("jfw", "analyzing <" << Module << "> failed"); 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 < static_cast(st.st_size)) { static OUStringLiteral msvcrts[] = { "msvcr71.dll", "msvcr100.dll" }; char const* importName = reinterpret_cast(dos_hdr) + imports->Name + VAtoPhys; sal_Int32 importNameLen = rtl_str_getLength(importName); for (size_t i = 0; i < SAL_N_ELEMENTS(msvcrts); ++i) { if (0 == rtl_str_compareIgnoreAsciiCase_WithLength( importName, importNameLen, msvcrts[i].data, msvcrts[i].size)) { load_msvcr(Module, msvcrts[i]); 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) { assert(pInfo != nullptr); assert(ppVm != nullptr); assert(ppEnv != nullptr); // 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; #ifdef MACOSX rtl::Reference aVendorInfo = getJREInfoByPath( pInfo->sLocation ); if ( !aVendorInfo.is() || aVendorInfo->compareVersions( 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); #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