diff options
author | Brian Smith <brian@dbsoft.org> | 2022-04-26 10:56:11 -0500 |
---|---|---|
committer | Brian Smith <brian@dbsoft.org> | 2022-04-26 10:56:11 -0500 |
commit | 1a31160dde95477993fb7d3aa1327b2a45f8fca8 (patch) | |
tree | f030c1903971dce5981795141374e828ff0669b4 | |
parent | e764f258e2530aa172102ff091ef205cc9bd5154 (diff) | |
download | uxp-1a31160dde95477993fb7d3aa1327b2a45f8fca8.tar.gz |
Issue #1829 - Revert "Issue #1751 -- Remove XP_MACOSX conditionals and support files from /xpcom"
This reverts commit 1bf0734a7249eb0dc1a96d825e7310eb46cac6dc.
36 files changed, 2739 insertions, 21 deletions
diff --git a/xpcom/base/MacHelpers.h b/xpcom/base/MacHelpers.h new file mode 100644 index 0000000000..2bff0367ef --- /dev/null +++ b/xpcom/base/MacHelpers.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_MacHelpers_h +#define mozilla_MacHelpers_h + +#include "nsString.h" + +namespace mozilla { + +nsresult GetSelectedCityInfo(nsAString& aCountryCode); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/MacHelpers.mm b/xpcom/base/MacHelpers.mm new file mode 100644 index 0000000000..e0b5e6c35b --- /dev/null +++ b/xpcom/base/MacHelpers.mm @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsString.h" +#include "MacHelpers.h" +#include "nsObjCExceptions.h" + +#import <Foundation/Foundation.h> + +namespace mozilla { + +nsresult +GetSelectedCityInfo(nsAString& aCountryCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Can be replaced with [[NSLocale currentLocale] countryCode] once we build + // with the 10.12 SDK. + id countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; + + if (![countryCode isKindOfClass:[NSString class]]) { + return NS_ERROR_FAILURE; + } + + const char* countryCodeUTF8 = [(NSString*)countryCode UTF8String]; + + if (!countryCodeUTF8) { + return NS_ERROR_FAILURE; + } + + AppendUTF8toUTF16(countryCodeUTF8, aCountryCode); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace Mozilla + diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build index 03a0fa43a7..a59528c576 100644 --- a/xpcom/base/moz.build +++ b/xpcom/base/moz.build @@ -28,6 +28,17 @@ XPIDL_SOURCES += [ 'nsrootidl.idl', ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsIMacUtils.idl', + ] + EXPORTS.mozilla += [ + 'MacHelpers.h', + ] + UNIFIED_SOURCES += [ + 'MacHelpers.mm', + ] + XPIDL_MODULE = 'xpcom_base' EXPORTS += [ @@ -125,7 +136,11 @@ if CONFIG['OS_ARCH'] == 'Linux': 'SystemMemoryReporter.cpp', ] -if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsMacUtilsImpl.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': SOURCES += [ 'nsCrashOnException.cpp', ] diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp index fb4831eeda..c4d4437a9b 100644 --- a/xpcom/base/nsDebugImpl.cpp +++ b/xpcom/base/nsDebugImpl.cpp @@ -34,7 +34,7 @@ #include "nsString.h" #endif -#if defined(__DragonFly__) || defined(__FreeBSD__) \ +#if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) #include <stdbool.h> #include <unistd.h> @@ -58,7 +58,9 @@ #define KINFO_PROC struct kinfo_proc #endif -#if defined(__DragonFly__) +#if defined(XP_MACOSX) +#define KP_FLAGS kp_proc.p_flag +#elif defined(__DragonFly__) #define KP_FLAGS kp_flags #elif defined(__FreeBSD__) #define KP_FLAGS ki_flag @@ -162,7 +164,7 @@ nsDebugImpl::GetIsDebuggerAttached(bool* aResult) #if defined(XP_WIN) *aResult = ::IsDebuggerPresent(); -#elif defined(__DragonFly__) || defined(__FreeBSD__) \ +#elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) // Specify the info we're looking for int mib[] = { @@ -424,6 +426,8 @@ RealBreak() { #if defined(_WIN32) ::DebugBreak(); +#elif defined(XP_MACOSX) + raise(SIGTRAP); #elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) asm("int $3"); #elif defined(__arm__) @@ -507,6 +511,12 @@ Break(const char* aMsg) } RealBreak(); +#elif defined(XP_MACOSX) + /* Note that we put this Mac OS X test above the GNUC/x86 test because the + * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86 + * impls to be the same. + */ + RealBreak(); #elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__)) RealBreak(); #elif defined(__arm__) diff --git a/xpcom/base/nsIMacUtils.idl b/xpcom/base/nsIMacUtils.idl new file mode 100644 index 0000000000..9a60df47cd --- /dev/null +++ b/xpcom/base/nsIMacUtils.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +/** + * nsIMacUtils: Generic globally-available Mac-specific utilities. + */ + +[scriptable, uuid(5E9072D7-FF95-455E-9466-8AF9841A72EC)] +interface nsIMacUtils : nsISupports +{ + /** + * True when the main executable is a fat file supporting at least + * ppc and x86 (universal binary). + */ + readonly attribute boolean isUniversalBinary; + + /** + * Returns a string containing a list of architectures delimited + * by "-". Architecture sets are always in the same order: + * ppc > i386 > ppc64 > x86_64 > (future additions) + */ + readonly attribute AString architecturesInBinary; + + /** + * True when running under binary translation (Rosetta). + */ + readonly attribute boolean isTranslated; +}; diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp new file mode 100644 index 0000000000..eb93cc555e --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsMacUtilsImpl.h" + +#include <CoreFoundation/CoreFoundation.h> + +NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils) + +nsresult +nsMacUtilsImpl::GetArchString(nsAString& aArchString) +{ + if (!mBinaryArchs.IsEmpty()) { + aArchString.Assign(mBinaryArchs); + return NS_OK; + } + + aArchString.Truncate(); + + bool foundPPC = false, + foundX86 = false, + foundPPC64 = false, + foundX86_64 = false; + + CFBundleRef mainBundle = ::CFBundleGetMainBundle(); + if (!mainBundle) { + return NS_ERROR_FAILURE; + } + + CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle); + if (!archList) { + return NS_ERROR_FAILURE; + } + + CFIndex archCount = ::CFArrayGetCount(archList); + for (CFIndex i = 0; i < archCount; i++) { + CFNumberRef arch = + static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i)); + + int archInt = 0; + if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) { + ::CFRelease(archList); + return NS_ERROR_FAILURE; + } + + if (archInt == kCFBundleExecutableArchitecturePPC) { + foundPPC = true; + } else if (archInt == kCFBundleExecutableArchitectureI386) { + foundX86 = true; + } else if (archInt == kCFBundleExecutableArchitecturePPC64) { + foundPPC64 = true; + } else if (archInt == kCFBundleExecutableArchitectureX86_64) { + foundX86_64 = true; + } + } + + ::CFRelease(archList); + + // The order in the string must always be the same so + // don't do this in the loop. + if (foundPPC) { + mBinaryArchs.AppendLiteral("ppc"); + } + + if (foundX86) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("i386"); + } + + if (foundPPC64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("ppc64"); + } + + if (foundX86_64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("x86_64"); + } + + aArchString.Assign(mBinaryArchs); + + return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK); +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetIsUniversalBinary(bool* aIsUniversalBinary) +{ + if (NS_WARN_IF(!aIsUniversalBinary)) { + return NS_ERROR_INVALID_ARG; + } + *aIsUniversalBinary = false; + + nsAutoString archString; + nsresult rv = GetArchString(archString); + if (NS_FAILED(rv)) { + return rv; + } + + // The delimiter char in the arch string is '-', so if that character + // is in the string we know we have multiple architectures. + *aIsUniversalBinary = (archString.Find("-") > -1); + + return NS_OK; +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) +{ + return GetArchString(aArchString); +} + +// True when running under binary translation (Rosetta). +NS_IMETHODIMP +nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) +{ +#ifdef __ppc__ + static bool sInitialized = false; + + // Initialize sIsNative to 1. If the sysctl fails because it doesn't + // exist, then translation is not possible, so the process must not be + // running translated. + static int32_t sIsNative = 1; + + if (!sInitialized) { + size_t sz = sizeof(sIsNative); + sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0); + sInitialized = true; + } + + *aIsTranslated = !sIsNative; +#else + // Translation only exists for ppc code. Other architectures aren't + // translated. + *aIsTranslated = false; +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h new file mode 100644 index 0000000000..12a1add41a --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsMacUtilsImpl_h___ +#define nsMacUtilsImpl_h___ + +#include "nsIMacUtils.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsMacUtilsImpl final : public nsIMacUtils +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACUTILS + + nsMacUtilsImpl() + { + } + +private: + ~nsMacUtilsImpl() + { + } + + nsresult GetArchString(nsAString& aArchString); + + // A string containing a "-" delimited list of architectures + // in our binary. + nsString mBinaryArchs; +}; + +// Global singleton service +// 697BD3FD-43E5-41CE-AD5E-C339175C0818 +#define NS_MACUTILSIMPL_CID \ + {0x697BD3FD, 0x43E5, 0x41CE, {0xAD, 0x5E, 0xC3, 0x39, 0x17, 0x5C, 0x08, 0x18}} +#define NS_MACUTILSIMPL_CONTRACTID "@mozilla.org/xpcom/mac-utils;1" + +#endif /* nsMacUtilsImpl_h___ */ diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index 4397f470e8..c47d3c8415 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -394,6 +394,168 @@ ResidentFastDistinguishedAmount(int64_t* aN) return ResidentDistinguishedAmount(aN); } +#elif defined(XP_MACOSX) + +#include <mach/mach_init.h> +#include <mach/mach_vm.h> +#include <mach/shared_region.h> +#include <mach/task.h> +#include <sys/sysctl.h> + +static MOZ_MUST_USE bool +GetTaskBasicInfo(struct task_basic_info* aTi) +{ + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, + (task_info_t)aTi, &count); + return kr == KERN_SUCCESS; +} + +// The VSIZE figure on Mac includes huge amounts of shared memory and is always +// absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report +// it, so we might as well too. +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.virtual_size; + return NS_OK; +} + +// If we're using jemalloc on Mac, we need to instruct jemalloc to purge the +// pages it has madvise(MADV_FREE)'d before we read our RSS in order to get +// an accurate result. The OS will take away MADV_FREE'd pages when there's +// memory pressure, so ideally, they shouldn't count against our RSS. +// +// Purging these pages can take a long time for some users (see bug 789975), +// so we provide the option to get the RSS without purging first. +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge) +{ +#ifdef HAVE_JEMALLOC_STATS + if (aDoPurge) { + jemalloc_purge_freed_pages(); + } +#endif + + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.resident_size; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +static bool +InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) +{ + mach_vm_address_t base; + mach_vm_address_t size; + + switch (aType) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + default: + return false; + } + + return base <= aAddr && aAddr < (base + size); +} + +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + if (!aN) { + return NS_ERROR_FAILURE; + } + + cpu_type_t cpu_type; + size_t len = sizeof(cpu_type); + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { + return NS_ERROR_FAILURE; + } + + // Roughly based on libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c + size_t privatePages = 0; + mach_vm_size_t size = 0; + for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; ; addr += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT; + mach_port_t objectName; + + kern_return_t kr = + mach_vm_region(mach_task_self(), &addr, &size, VM_REGION_TOP_INFO, + reinterpret_cast<vm_region_info_t>(&info), + &infoCount, &objectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) { + continue; + } + + switch (info.share_mode) { + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. + case SM_PRIVATE: + privatePages += info.private_pages_resident; + privatePages += info.shared_pages_resident; + break; + case SM_COW: + privatePages += info.private_pages_resident; + if (info.ref_count == 1) { + // Treat copy-on-write pages as private if they only have one reference. + privatePages += info.shared_pages_resident; + } + break; + case SM_SHARED: + default: + break; + } + } + + vm_size_t pageSize; + if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) { + pageSize = PAGE_SIZE; + } + + *aN = privatePages * pageSize; + return NS_OK; +} + #elif defined(XP_WIN) #include <windows.h> @@ -984,7 +1146,9 @@ ResidentPeakDistinguishedAmount(int64_t* aN) // - Solaris: pages? But some sources it actually always returns 0, so // check for that // - Linux, {Net/Open/Free}BSD, DragonFly: KiB -#if defined(XP_SOLARIS) +#ifdef XP_MACOSX + *aN = usage.ru_maxrss; +#elif defined(XP_SOLARIS) *aN = usage.ru_maxrss * getpagesize(); #else *aN = usage.ru_maxrss * 1024; diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp index df106e4e05..28c4915976 100644 --- a/xpcom/base/nsSystemInfo.cpp +++ b/xpcom/base/nsSystemInfo.cpp @@ -25,6 +25,10 @@ #include "nsWindowsHelpers.h" #endif +#ifdef XP_MACOSX +#include "MacHelpers.h" +#endif + #ifdef MOZ_WIDGET_GTK #include <gtk/gtk.h> #include <dlfcn.h> @@ -40,6 +44,10 @@ #include <string> #endif +#ifdef XP_MACOSX +#include <sys/sysctl.h> +#endif + // Slot for NS_InitXPCOM2 to pass information to nsSystemInfo::Init. // Only set to nonzero (potentially) if XP_UNIX. On such systems, the // system call to discover the appropriate value is not thread-safe, @@ -409,6 +417,67 @@ nsSystemInfo::Init() cpuFamily = si.wProcessorLevel; cpuModel = si.wProcessorRevision >> 8; cpuStepping = si.wProcessorRevision & 0xFF; +#elif defined (XP_MACOSX) + // CPU speed + uint64_t sysctlValue64 = 0; + uint32_t sysctlValue32 = 0; + size_t len = 0; + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.cpufrequency_max", &sysctlValue64, &len, NULL, 0)) { + cpuSpeed = static_cast<int>(sysctlValue64/1000000); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + physicalCPUs = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + logicalCPUs = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL2 = static_cast<int>(sysctlValue64/1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL3 = static_cast<int>(sysctlValue64/1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + if (!sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0)) { + char* cpuVendorStr = new char[len]; + if (!sysctlbyname("machdep.cpu.vendor", cpuVendorStr, &len, NULL, 0)) { + cpuVendor = cpuVendorStr; + } + delete [] cpuVendorStr; + } + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.family", &sysctlValue32, &len, NULL, 0)) { + cpuFamily = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) { + cpuModel = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) { + cpuStepping = static_cast<int>(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + #elif defined(XP_LINUX) // Get vendor, family, model, stepping, physical cores, L3 cache size // from /proc/cpuinfo file @@ -585,6 +654,14 @@ nsSystemInfo::Init() } #endif +#if defined(XP_MACOSX) + nsAutoString countryCode; + if (NS_SUCCEEDED(GetSelectedCityInfo(countryCode))) { + rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + #if defined(MOZ_WIDGET_GTK) // This must be done here because NSPR can only separate OS's when compiled, not libraries. // 64 bytes is going to be well enough for "GTK " followed by 3 integers diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp index e3974b5fca..645b34b87e 100644 --- a/xpcom/base/nsUUIDGenerator.cpp +++ b/xpcom/base/nsUUIDGenerator.cpp @@ -6,6 +6,8 @@ #if defined(XP_WIN) #include <windows.h> #include <objbase.h> +#elif defined(XP_MACOSX) +#include <CoreFoundation/CoreFoundation.h> #else #include <stdlib.h> #include "prrng.h" @@ -32,7 +34,7 @@ nsUUIDGenerator::Init() // We're a service, so we're guaranteed that Init() is not going // to be reentered while we're inside Init(). -#if !defined(XP_WIN) && !defined(HAVE_ARC4RANDOM) +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) /* initialize random number generator using NSPR random noise */ unsigned int seed; @@ -69,7 +71,7 @@ nsUUIDGenerator::Init() } #endif -#endif /* non XP_WIN and non ARC4RANDOM */ +#endif /* non XP_WIN and non XP_MACOSX and non ARC4RANDOM */ return NS_OK; } @@ -104,7 +106,17 @@ nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) if (FAILED(hr)) { return NS_ERROR_FAILURE; } -#else /* not windows; generate randomness using random(). */ +#elif defined(XP_MACOSX) + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + if (!uuid) { + return NS_ERROR_FAILURE; + } + + CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid); + memcpy(aId, &bytes, sizeof(nsID)); + + CFRelease(uuid); +#else /* not windows or OS X; generate randomness using random(). */ /* XXX we should be saving the return of setstate here and switching * back to it; instead, we use the value returned when we called * initstate, since older glibc's have broken setstate() return values diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h index 1eadd14eea..bfd2805eaf 100644 --- a/xpcom/base/nsUUIDGenerator.h +++ b/xpcom/base/nsUUIDGenerator.h @@ -28,7 +28,7 @@ private: protected: mozilla::Mutex mLock; -#if !defined(XP_WIN) && !defined(HAVE_ARC4RANDOM) +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) char mState[128]; char* mSavedState; uint8_t mRBytes; diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h index 8ad7e8aac2..6c2622a557 100644 --- a/xpcom/build/BinaryPath.h +++ b/xpcom/build/BinaryPath.h @@ -9,6 +9,8 @@ #include "nsXPCOMPrivate.h" // for MAXPATHLEN #ifdef XP_WIN #include <windows.h> +#elif defined(XP_MACOSX) +#include <CoreFoundation/CoreFoundation.h> #elif defined(XP_UNIX) #include <sys/stat.h> #include <string.h> @@ -41,6 +43,46 @@ private: return NS_ERROR_FAILURE; } +#elif defined(XP_MACOSX) + static nsresult Get(const char* argv0, char aResult[MAXPATHLEN]) + { + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (!appBundle) { + return NS_ERROR_FAILURE; + } + + CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle); + if (!executableURL) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (CFURLGetFileSystemRepresentation(executableURL, false, (UInt8*)aResult, + MAXPATHLEN)) { + // Sanitize path in case the app was launched from Terminal via + // './firefox' for example. + size_t readPos = 0; + size_t writePos = 0; + while (aResult[readPos] != '\0') { + if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { + readPos += 2; + } else { + aResult[writePos] = aResult[readPos]; + readPos++; + writePos++; + } + } + aResult[writePos] = '\0'; + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(executableURL); + return rv; + } + #elif defined(XP_UNIX) static nsresult Get(const char* aArgv0, char aResult[MAXPATHLEN]) { diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h index 6a34e39a0c..20edbd5aa8 100644 --- a/xpcom/build/PoisonIOInterposer.h +++ b/xpcom/build/PoisonIOInterposer.h @@ -35,7 +35,7 @@ void MozillaUnRegisterDebugFILE(FILE* aFile); MOZ_END_EXTERN_C -#if defined(XP_WIN) +#if defined(XP_WIN) || defined(XP_MACOSX) #ifdef __cplusplus namespace mozilla { @@ -53,6 +53,16 @@ bool IsDebugFile(intptr_t aFileID); */ void InitPoisonIOInterposer(); +#ifdef XP_MACOSX +/** + * Check that writes are dirty before reporting I/O (Mac OS X only) + * This is necessary for late-write checks on Mac OS X, but reading the buffer + * from file to see if we're writing dirty bits is expensive, so we don't want + * to do this for everything else that uses + */ +void OnlyReportDirtyWrites(); +#endif /* XP_MACOSX */ + /** * Clear IO poisoning, this is only safe to do on the main-thread when no other * threads are running. @@ -62,16 +72,19 @@ void ClearPoisonIOInterposer(); } // namespace mozilla #endif /* __cplusplus */ -#else /* XP_WIN */ +#else /* XP_WIN || XP_MACOSX */ #ifdef __cplusplus namespace mozilla { inline bool IsDebugFile(intptr_t aFileID) { return true; } inline void InitPoisonIOInterposer() {} inline void ClearPoisonIOInterposer() {} +#ifdef XP_MACOSX +inline void OnlyReportDirtyWrites() {} +#endif /* XP_MACOSX */ } // namespace mozilla #endif /* __cplusplus */ -#endif /* XP_WIN */ +#endif /* XP_WIN || XP_MACOSX */ #endif // mozilla_PoisonIOInterposer_h diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp new file mode 100644 index 0000000000..dca9ba0297 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "PoisonIOInterposer.h" +#include "mach_override.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "nsTraceRefcnt.h" +#include "plstr.h" +#include "prio.h" + +#include <algorithm> +#include <vector> + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <aio.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef MOZ_REPLACE_MALLOC +#include "replace_malloc_bridge.h" +#endif + +namespace { + +using namespace mozilla; + +// Bit tracking if poisoned writes are enabled +static bool sIsEnabled = false; + +// Check if writes are dirty before reporting IO +static bool sOnlyReportDirtyWrites = false; + +// Routines for write validation +bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount); +bool IsIPCWrite(int aFd, const struct stat& aBuf); + +/******************************** IO AutoTimer ********************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the IOInterposeObserver API. + */ +class MacIOAutoObservation : public IOInterposeObserver::Observation +{ +public: + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + } + + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd, + const void* aBuf, size_t aCount) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + } + + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() override; + + ~MacIOAutoObservation() + { + Report(); + if (mFilename) { + free(mFilename); + mFilename = nullptr; + } + } + +private: + int mFd; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; +}; + +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* +MacIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + return mFilename; + } + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + mFilename = UTF8ToNewUnicode(nsDependentCString(filename)); + } else { + mFilename = nullptr; + } + mHasQueriedFilename = true; + + // Return filename + return mFilename; +} + +/****************************** Write Validation ******************************/ + +// We want to detect "actual" writes, not IPC. Some IPC mechanisms are +// implemented with file descriptors, so filter them out. +bool +IsIPCWrite(int aFd, const struct stat& aBuf) +{ + if ((aBuf.st_mode & S_IFMT) == S_IFIFO) { + return true; + } + + if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) { + return false; + } + + sockaddr_storage address; + socklen_t len = sizeof(address); + if (getsockname(aFd, (sockaddr*)&address, &len) != 0) { + return true; // Ignore the aFd if we can't find what it is. + } + + return address.ss_family == AF_UNIX; +} + +// We want to report actual disk IO not things that don't move bits on the disk +bool +IsValidWrite(int aFd, const void* aWbuf, size_t aCount) +{ + // Ignore writes of zero bytes, Firefox does some during shutdown. + if (aCount == 0) { + return false; + } + + { + struct stat buf; + int rv = fstat(aFd, &buf); + if (rv != 0) { + return true; + } + + if (IsIPCWrite(aFd, buf)) { + return false; + } + } + + // For writev we pass a nullptr aWbuf. We should only get here from + // dbm, and it uses write, so assert that we have aWbuf. + if (!aWbuf) { + return true; + } + + // Break, here if we're allowed to report non-dirty writes + if (!sOnlyReportDirtyWrites) { + return true; + } + + // As a really bad hack, accept writes that don't change the on disk + // content. This is needed because dbm doesn't keep track of dirty bits + // and can end up writing the same data to disk twice. Once when the + // user (nss) asks it to sync and once when closing the database. + auto wbuf2 = MakeUniqueFallible<char[]>(aCount); + if (!wbuf2) { + return true; + } + off_t pos = lseek(aFd, 0, SEEK_CUR); + if (pos == -1) { + return true; + } + ssize_t r = read(aFd, wbuf2.get(), aCount); + if (r < 0 || (size_t)r != aCount) { + return true; + } + int cmp = memcmp(aWbuf, wbuf2.get(), aCount); + if (cmp != 0) { + return true; + } + off_t pos2 = lseek(aFd, pos, SEEK_SET); + if (pos2 != pos) { + return true; + } + + // Otherwise this is not a valid write + return false; +} + +/*************************** Function Interception ***************************/ + +/** Structure for declaration of function override */ +struct FuncData +{ + const char* Name; // Name of the function for the ones we use dlsym + const void* Wrapper; // The function that we will replace 'Function' with + void* Function; // The function that will be replaced with 'Wrapper' + void* Buffer; // Will point to the jump buffer that lets us call + // 'Function' after it has been replaced. +}; + +// Wrap aio_write. We have not seen it before, so just assert/report it. +typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp); +ssize_t wrap_aio_write(struct aiocb* aAioCbp); +FuncData aio_write_data = { 0, (void*)wrap_aio_write, (void*)aio_write }; +ssize_t +wrap_aio_write(struct aiocb* aAioCbp) +{ + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, + aAioCbp->aio_fildes); + + aio_write_t old_write = (aio_write_t)aio_write_data.Buffer; + return old_write(aAioCbp); +} + +// Wrap pwrite-like functions. +// We have not seen them before, so just assert/report it. +typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes, + off_t aOffset); +template<FuncData& foo> +ssize_t +wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes, off_t aOffset) +{ + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd); + pwrite_t old_write = (pwrite_t)foo.Buffer; + return old_write(aFd, aBuf, aNumBytes, aOffset); +} + +// Define a FuncData for a pwrite-like functions. +#define DEFINE_PWRITE_DATA(X, NAME) \ +FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> }; \ + +// This exists everywhere. +DEFINE_PWRITE_DATA(pwrite, "pwrite") +// These exist on 32 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003"); +DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); + + +typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount); +template<FuncData& foo> +ssize_t +wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) +{ + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd, nullptr, + aIovCount); + writev_t old_write = (writev_t)foo.Buffer; + return old_write(aFd, aIov, aIovCount); +} + +// Define a FuncData for a writev-like functions. +#define DEFINE_WRITEV_DATA(X, NAME) \ +FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \ + +// This exists everywhere. +DEFINE_WRITEV_DATA(writev, "writev"); +// These exist on 32 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003"); +DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); + +typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount); +template<FuncData& foo> +ssize_t +wrap_write_temp(int aFd, const void* aBuf, size_t aCount) +{ + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd, aBuf, aCount); + write_t old_write = (write_t)foo.Buffer; + return old_write(aFd, aBuf, aCount); +} + +// Define a FuncData for a write-like functions. +#define DEFINE_WRITE_DATA(X, NAME) \ +FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \ + +// This exists everywhere. +DEFINE_WRITE_DATA(write, "write"); +// These exist on 32 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003"); +DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL"); + +FuncData* Functions[] = { + &aio_write_data, + + &pwrite_data, + &pwrite_NOCANCEL_UNIX2003_data, + &pwrite_UNIX2003_data, + &pwrite_NOCANCEL_data, + + &write_data, + &write_NOCANCEL_UNIX2003_data, + &write_UNIX2003_data, + &write_NOCANCEL_data, + + &writev_data, + &writev_NOCANCEL_UNIX2003_data, + &writev_UNIX2003_data, + &writev_NOCANCEL_data +}; + +const int NumFunctions = ArrayLength(Functions); + +} // namespace + +/******************************** IO Poisoning ********************************/ + +namespace mozilla { + +void +InitPoisonIOInterposer() +{ + // Enable reporting from poisoned write methods + sIsEnabled = true; + + // Make sure we only poison writes once! + static bool WritesArePoisoned = false; + if (WritesArePoisoned) { + return; + } + WritesArePoisoned = true; + + // stdout and stderr are OK. + MozillaRegisterDebugFD(1); + MozillaRegisterDebugFD(2); + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + for (int i = 0; i < NumFunctions; ++i) { + FuncData* d = Functions[i]; + if (!d->Function) { + d->Function = dlsym(RTLD_DEFAULT, d->Name); + } + if (!d->Function) { + continue; + } + DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper, + &d->Buffer); + MOZ_ASSERT(t == err_none); + } +} + +void +OnlyReportDirtyWrites() +{ + sOnlyReportDirtyWrites = true; +} + +void +ClearPoisonIOInterposer() +{ + // Not sure how or if we can unpoison the functions. Would be nice, but no + // worries we won't need to do this anyway. + sIsEnabled = false; +} + +} // namespace mozilla diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 3291c06c07..185a823155 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -1007,6 +1007,9 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) PROFILER_MARKER("Shutdown xpcom"); // If we are doing any shutdown checks, poison writes. if (gShutdownChecks != SCM_NOTHING) { +#ifdef XP_MACOSX + mozilla::OnlyReportDirtyWrites(); +#endif /* XP_MACOSX */ mozilla::BeginLateWriteChecks(); } diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c new file mode 100644 index 0000000000..9e4940d298 --- /dev/null +++ b/xpcom/build/mach_override.c @@ -0,0 +1,789 @@ +// Copied from upstream at revision 195c13743fe0ebc658714e2a9567d86529f20443. +// mach_override.c semver:1.2.0 +// Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit +// https://github.com/rentzsch/mach_override + +#include "mach_override.h" + +#include <mach-o/dyld.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/vm_map.h> +#include <sys/mman.h> + +#include <CoreServices/CoreServices.h> + +/************************** +* +* Constants +* +**************************/ +#pragma mark - +#pragma mark (Constants) + +#define kPageSize 4096 +#if defined(__ppc__) || defined(__POWERPC__) + +long kIslandTemplate[] = { + 0x9001FFFC, // stw r0,-4(SP) + 0x3C00DEAD, // lis r0,0xDEAD + 0x6000BEEF, // ori r0,r0,0xBEEF + 0x7C0903A6, // mtctr r0 + 0x8001FFFC, // lwz r0,-4(SP) + 0x60000000, // nop ; optionally replaced + 0x4E800420 // bctr +}; + +#define kAddressHi 3 +#define kAddressLo 5 +#define kInstructionHi 10 +#define kInstructionLo 11 + +#elif defined(__i386__) + +#define kOriginalInstructionsSize 16 +// On X86 we migh need to instert an add with a 32 bit immediate after the +// original instructions. +#define kMaxFixupSizeIncrease 5 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xE9, 0xEF, 0xBE, 0xAD, 0xDE +}; + +#define kInstructions 0 +#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1 +#elif defined(__x86_64__) + +#define kOriginalInstructionsSize 32 +// On X86-64 we never need to instert a new instruction. +#define kMaxFixupSizeIncrease 0 + +#define kJumpAddress kOriginalInstructionsSize + 6 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +#endif + +/************************** +* +* Data Types +* +**************************/ +#pragma mark - +#pragma mark (Data Types) + +typedef struct { + char instructions[sizeof(kIslandTemplate)]; +} BranchIsland; + +/************************** +* +* Funky Protos +* +**************************/ +#pragma mark - +#pragma mark (Funky Protos) + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress); + + mach_error_t +freeBranchIsland( + BranchIsland *island ); + +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ); +#endif + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ); +void +atomic_mov64( + uint64_t *targetAddress, + uint64_t value ); + + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ); + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ); +#endif + +/******************************************************************************* +* +* Interface +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Interface) + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t makeIslandExecutable(void *address) { + mach_error_t err = err_none; + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1); + int e = err_none; + e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, kPageSize, MS_INVALIDATE ); + if (e) { + err = err_cannot_override; + } + return err; +} +#endif + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ) +{ + assert( originalFunctionAddress ); + assert( overrideFunctionAddress ); + + // this addresses overriding such functions as AudioOutputUnitStart() + // test with modified DefaultOutputUnit project +#if defined(__x86_64__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????] + originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1)); + else break; + } +#elif defined(__i386__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x???????? + originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1); + else break; + } +#endif + + long *originalFunctionPtr = (long*) originalFunctionAddress; + mach_error_t err = err_none; + +#if defined(__ppc__) || defined(__POWERPC__) + // Ensure first instruction isn't 'mfctr'. + #define kMFCTRMask 0xfc1fffff + #define kMFCTRInstruction 0x7c0903a6 + + long originalInstruction = *originalFunctionPtr; + if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) ) + err = err_cannot_override; +#elif defined(__i386__) || defined(__x86_64__) + int eatenCount = 0; + int originalInstructionCount = 0; + char originalInstructions[kOriginalInstructionsSize]; + uint8_t originalInstructionSizes[kOriginalInstructionsSize]; + uint64_t jumpRelativeInstruction = 0; // JMP + + Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr, + &jumpRelativeInstruction, &eatenCount, + originalInstructions, &originalInstructionCount, + originalInstructionSizes ); + if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) { + //printf ("Too many instructions eaten\n"); + overridePossible = false; + } + if (!overridePossible) err = err_cannot_override; + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); +#endif + + // Make the original function implementation writable. + if( !err ) { + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_ALL | VM_PROT_COPY) ); + if( err ) + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_DEFAULT | VM_PROT_COPY) ); + } + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + // Allocate and target the escape island to the overriding function. + BranchIsland *escapeIsland = NULL; + if( !err ) + err = allocateBranchIsland( &escapeIsland, originalFunctionAddress ); + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + +#if defined(__ppc__) || defined(__POWERPC__) + if( !err ) + err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 ); + + // Build the branch absolute instruction to the escape island. + long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning. + if( !err ) { + long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF; + branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress; + } +#elif defined(__i386__) || defined(__x86_64__) + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + if( !err ) + err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 ); + + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + // Build the jump relative instruction to the escape island +#endif + + +#if defined(__i386__) || defined(__x86_64__) + if (!err) { + uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5); + addressOffset = OSSwapInt32(addressOffset); + + jumpRelativeInstruction |= 0xE900000000000000LL; + jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24; + jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction); + } +#endif + + // Optionally allocate & return the reentry island. This may contain relocated + // jmp instructions and so has all the same addressing reachability requirements + // the escape island has to the original function, except the escape island is + // technically our original function. + BranchIsland *reentryIsland = NULL; + if( !err && originalFunctionReentryIsland ) { + err = allocateBranchIsland( &reentryIsland, escapeIsland); + if( !err ) + *originalFunctionReentryIsland = reentryIsland; + } + +#if defined(__ppc__) || defined(__POWERPC__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instruction into the reentry island. + // o Target the reentry island at the 2nd instruction of the + // original function. + // o Replace the original instruction with the branch absolute. + if( !err ) { + int escapeIslandEngaged = false; + do { + if( reentryIsland ) + err = setBranchIslandTarget( reentryIsland, + (void*) (originalFunctionPtr+1), originalInstruction ); + if( !err ) { + escapeIslandEngaged = CompareAndSwap( originalInstruction, + branchAbsoluteInstruction, + (UInt32*)originalFunctionPtr ); + if( !escapeIslandEngaged ) { + // Someone replaced the instruction out from under us, + // re-read the instruction, make sure it's still not + // 'mfctr' and try again. + originalInstruction = *originalFunctionPtr; + if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction) + err = err_cannot_override; + } + } + } while( !err && !escapeIslandEngaged ); + } +#elif defined(__i386__) || defined(__x86_64__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instructions into the reentry island. + // o Target the reentry island at the first non-replaced + // instruction of the original function. + // o Replace the original first instructions with the jump relative. + // + // Note that on i386, we do not support someone else changing the code under our feet + if ( !err ) { + uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland; + fixupInstructions(offset, originalInstructions, + originalInstructionCount, originalInstructionSizes ); + + if( reentryIsland ) + err = setBranchIslandTarget_i386( reentryIsland, + (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions ); + // try making islands executable before planting the jmp +#if defined(__x86_64__) || defined(__i386__) + if( !err ) + err = makeIslandExecutable(escapeIsland); + if( !err && reentryIsland ) + err = makeIslandExecutable(reentryIsland); +#endif + if ( !err ) + atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction); + } +#endif + + // Clean up on error. + if( err ) { + if( reentryIsland ) + freeBranchIsland( reentryIsland ); + if( escapeIsland ) + freeBranchIsland( escapeIsland ); + } + + return err; +} + +/******************************************************************************* +* +* Implementation +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Implementation) + +static bool jump_in_range(intptr_t from, intptr_t to) { + intptr_t field_value = to - from - 5; + int32_t field_value_32 = field_value; + return field_value == field_value_32; +} + +/******************************************************************************* + Implementation: Allocates memory for a branch island. + + @param island <- The allocated island. + @result <- mach_error_t + + ***************************************************************************/ + +static mach_error_t +allocateBranchIslandAux( + BranchIsland **island, + void *originalFunctionAddress, + bool forward) +{ + assert( island ); + assert( sizeof( BranchIsland ) <= kPageSize ); + + vm_map_t task_self = mach_task_self(); + vm_address_t original_address = (vm_address_t) originalFunctionAddress; + vm_address_t address = original_address; + + for (;;) { + vm_size_t vmsize = 0; + memory_object_name_t object = 0; + kern_return_t kr = 0; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + // Find the region the address is in. +#if __WORDSIZE == 32 + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kr = vm_region(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#else + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = vm_region_64(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr != KERN_SUCCESS) + return kr; + assert((address & (kPageSize - 1)) == 0); + + // Go to the first page before or after this region + vm_address_t new_address = forward ? address + vmsize : address - kPageSize; +#if __WORDSIZE == 64 + if(!jump_in_range(original_address, new_address)) + break; +#endif + address = new_address; + + // Try to allocate this page. + kr = vm_allocate(task_self, &address, kPageSize, 0); + if (kr == KERN_SUCCESS) { + *island = (BranchIsland*) address; + return err_none; + } + if (kr != KERN_NO_SPACE) + return kr; + } + + return KERN_NO_SPACE; +} + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress) +{ + mach_error_t err = + allocateBranchIslandAux(island, originalFunctionAddress, true); + if (!err) + return err; + return allocateBranchIslandAux(island, originalFunctionAddress, false); +} + + +/******************************************************************************* + Implementation: Deallocates memory for a branch island. + + @param island -> The island to deallocate. + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +freeBranchIsland( + BranchIsland *island ) +{ + assert( island ); + assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); + assert( sizeof( BranchIsland ) <= kPageSize ); + return vm_deallocate( mach_task_self(), (vm_address_t) island, + kPageSize ); +} + +/******************************************************************************* + Implementation: Sets the branch island's target, with an optional + instruction. + + @param island -> The branch island to insert target into. + @param branchTo -> The address of the target. + @param instruction -> Optional instruction to execute prior to branch. Set + to zero for nop. + @result <- mach_error_t + + ***************************************************************************/ +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Fill in the address. + ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF; + ((short*)island->instructions)[kAddressHi] + = (((long) branchTo) >> 16) & 0x0000FFFF; + + // Fill in the (optional) instuction. + if( instruction != 0 ) { + ((short*)island->instructions)[kInstructionLo] + = instruction & 0x0000FFFF; + ((short*)island->instructions)[kInstructionHi] + = (instruction >> 16) & 0x0000FFFF; + } + + //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) ); + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + +#if defined(__i386__) + mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // copy original instructions + if (instructions) { + bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize); + } + + // Fill in the address. + int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4); + *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset; + + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + return err_none; +} + +#elif defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Copy original instructions. + if (instructions) { + bcopy (instructions, island->instructions, kOriginalInstructionsSize); + } + + // Fill in the address. + *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo; + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + + +#if defined(__i386__) || defined(__x86_64__) +// simplistic instruction matching +typedef struct { + unsigned int length; // max 15 + unsigned char mask[15]; // sequence of bytes in memory order + unsigned char constraint[15]; // sequence of bytes in memory order +} AsmInstructionMatch; + +#if defined(__i386__) +static AsmInstructionMatch possibleInstructions[] = { + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xFF}, {0x55} }, // push %esp + { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp + { 0x1, {0xFF}, {0x53} }, // push %ebx + { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate + { 0x1, {0xFF}, {0x57} }, // push %edi + { 0x1, {0xFF}, {0x56} }, // push %esi + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg + { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax + { 0x0 } +}; +#elif defined(__x86_64__) +static AsmInstructionMatch possibleInstructions[] = { + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %rX + { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp + { 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0f, 0xbe, 0xce} }, // movsbl %sil, %ecx + { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX + { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + + //leaq offset(%rip),%rax + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} }, + + { 0x0 } +}; +#endif + +static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction) +{ + Boolean match = true; + + size_t i; + for (i=0; i<instruction->length; i++) { + unsigned char mask = instruction->mask[i]; + unsigned char constraint = instruction->constraint[i]; + unsigned char codeValue = code[i]; + + match = ((codeValue & mask) == constraint); + if (!match) break; + } + + return match; +} + +#if defined(__i386__) || defined(__x86_64__) + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) +{ + Boolean allInstructionsKnown = true; + int totalEaten = 0; + unsigned char* ptr = code; + int remainsToEat = 5; // a JMP instruction takes 5 bytes + int instructionIndex = 0; + + if (howManyEaten) *howManyEaten = 0; + if (originalInstructionCount) *originalInstructionCount = 0; + while (remainsToEat > 0) { + Boolean curInstructionKnown = false; + + // See if instruction matches one we know + AsmInstructionMatch* curInstr = possibleInstructions; + do { + if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break; + curInstr++; + } while (curInstr->length > 0); + + // if all instruction matches failed, we don't know current instruction then, stop here + if (!curInstructionKnown) { + allInstructionsKnown = false; + fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n"); + break; + } + + // At this point, we've matched curInstr + int eaten = curInstr->length; + ptr += eaten; + remainsToEat -= eaten; + totalEaten += eaten; + + if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten; + instructionIndex += 1; + if (originalInstructionCount) *originalInstructionCount = instructionIndex; + } + + + if (howManyEaten) *howManyEaten = totalEaten; + + if (originalInstructions) { + Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize); + + if (enoughSpaceForOriginalInstructions) { + memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP + bcopy(code, originalInstructions, totalEaten); + } else { + // printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n"); + return false; + } + } + + if (allInstructionsKnown) { + // save last 3 bytes of first 64bits of codre we'll replace + uint64_t currentFirst64BitsOfCode = *((uint64_t *)code); + currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation + currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL; + + // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr + *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes + *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes + } + + return allInstructionsKnown; +} + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) +{ + // The start of "leaq offset(%rip),%rax" + static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05}; + + int index; + for (index = 0;index < instructionCount;index += 1) + { + if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative + { + uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); + *jumpOffsetPtr += offset; + } + + // leaq offset(%rip),%rax + if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) { + uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3); + *LeaqOffsetPtr += offset; + } + + // 32-bit call relative to the next addr; pop %eax + if (*(uint8_t*)instructionsToFix == 0xE8) + { + // Just this call is larger than the jump we use, so we + // know this is the last instruction. + assert(index == (instructionCount - 1)); + assert(instructionSizes[index] == 6); + + // Insert "addl $offset, %eax" in the end so that when + // we jump to the rest of the function %eax has the + // value it would have if eip had been pushed by the + // call in its original position. + uint8_t *op = instructionsToFix; + op += 6; + *op = 0x05; // addl + uint32_t *addImmPtr = (uint32_t*)(op + 1); + *addImmPtr = offset; + } + + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); + } +} +#endif + +#if defined(__i386__) +__asm( + ".text;" + ".align 2, 0x90;" + "_atomic_mov64:;" + " pushl %ebp;" + " movl %esp, %ebp;" + " pushl %esi;" + " pushl %ebx;" + " pushl %ecx;" + " pushl %eax;" + " pushl %edx;" + + // atomic push of value to an address + // we use cmpxchg8b, which compares content of an address with + // edx:eax. If they are equal, it atomically puts 64bit value + // ecx:ebx in address. + // We thus put contents of address in edx:eax to force ecx:ebx + // in address + " mov 8(%ebp), %esi;" // esi contains target address + " mov 12(%ebp), %ebx;" + " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address + " mov (%esi), %eax;" + " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address + " lock; cmpxchg8b (%esi);" // atomic move. + + // restore registers + " popl %edx;" + " popl %eax;" + " popl %ecx;" + " popl %ebx;" + " popl %esi;" + " popl %ebp;" + " ret" +); +#elif defined(__x86_64__) +void atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) +{ + *targetAddress = value; +} +#endif +#endif diff --git a/xpcom/build/mach_override.h b/xpcom/build/mach_override.h new file mode 100644 index 0000000000..d9be988a34 --- /dev/null +++ b/xpcom/build/mach_override.h @@ -0,0 +1,121 @@ +/******************************************************************************* + mach_override.h + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + Some rights reserved: <http://opensource.org/licenses/mit-license.php> + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + + This package, coded in C to the Mach API, allows you to override ("patch") + program- and system-supplied functions at runtime. You can fully replace + functions with your implementations, or merely head- or tail-patch the + original implementations. + + Use it by #include'ing mach_override.h from your .c, .m or .mm file(s). + + @todo Discontinue use of Carbon's MakeDataExecutable() and + CompareAndSwap() calls and start using the Mach equivalents, if they + exist. If they don't, write them and roll them in. That way, this + code will be pure Mach, which will make it easier to use everywhere. + Update: MakeDataExecutable() has been replaced by + msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but + I'm currently unsure if I can link against it. May have to roll in + my own version... + @todo Stop using an entire 4K high-allocated VM page per 28-byte escape + branch island. Done right, this will dramatically speed up escape + island allocations when they number over 250. Then again, if you're + overriding more than 250 functions, maybe speed isn't your main + concern... + @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl + first-instructions. Initially, we should refuse to override + functions beginning with these instructions. Eventually, we should + dynamically rewrite them to make them position-independent. + @todo Write mach_unoverride(), which would remove an override placed on a + function. Must be multiple-override aware, which means an almost + complete rewrite under the covers, because the target address can't + be spread across two load instructions like it is now since it will + need to be atomically updatable. + @todo Add non-rentry variants of overrides to test_mach_override. + + ***************************************************************************/ + +#ifndef _mach_override_ +#define _mach_override_ + +#include <sys/types.h> +#include <mach/error.h> + +#ifdef __cplusplus + extern "C" { +#endif + +/** + Returned if the function to be overrided begins with a 'mfctr' instruction. +*/ +#define err_cannot_override (err_local|1) + +/************************************************************************************//** + Dynamically overrides the function implementation referenced by + originalFunctionAddress with the implentation pointed to by overrideFunctionAddress. + Optionally returns a pointer to a "reentry island" which, if jumped to, will resume + the original implementation. + + @param originalFunctionAddress -> Required address of the function to + override (with overrideFunctionAddress). + @param overrideFunctionAddress -> Required address to the overriding + function. + @param originalFunctionReentryIsland <- Optional pointer to pointer to the + reentry island. Can be nullptr. + @result <- err_cannot_override if the original + function's implementation begins with + the 'mfctr' instruction. + + ************************************************************************************/ + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ); + +/************************************************************************************//** + + + ************************************************************************************/ + +#ifdef __cplusplus + +#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \ + { \ + static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \ + static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \ + class mach_override_class__##ORIGINAL_FUNCTION_NAME { \ + public: \ + static kern_return_t override(void *originalFunctionPtr) { \ + kern_return_t result = err_none; \ + if (!ORIGINAL_FUNCTION_NAME##_overriden) { \ + ORIGINAL_FUNCTION_NAME##_overriden = true; \ + result = mach_override_ptr( (void*)originalFunctionPtr, \ + (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \ + (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \ + } \ + return result; \ + } \ + static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS { + +#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \ + } \ + }; \ + \ + err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \ + } + +#endif + +#ifdef __cplusplus + } +#endif +#endif // _mach_override_ diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build index bc20b8aedd..c58b4db3f5 100644 --- a/xpcom/build/moz.build +++ b/xpcom/build/moz.build @@ -32,6 +32,13 @@ if CONFIG['OS_ARCH'] == 'WINNT': 'PoisonIOInterposerBase.cpp', 'PoisonIOInterposerWin.cpp', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += [ + 'PoisonIOInterposerBase.cpp', + 'PoisonIOInterposerMac.cpp', + ] + SOURCES += ['mach_override.c'] + SOURCES['mach_override.c'].flags += ['-Wno-unused-function'] else: SOURCES += ['PoisonIOInterposerStub.cpp'] diff --git a/xpcom/build/nsXPCOMPrivate.h b/xpcom/build/nsXPCOMPrivate.h index 6c3a2bc0ab..c5f6553c03 100644 --- a/xpcom/build/nsXPCOMPrivate.h +++ b/xpcom/build/nsXPCOMPrivate.h @@ -254,8 +254,15 @@ void LogTerm(); #define XPCOM_DLL XUL_DLL +// you have to love apple.. +#ifdef XP_MACOSX +#define XPCOM_SEARCH_KEY "DYLD_LIBRARY_PATH" +#define GRE_FRAMEWORK_NAME "XUL.framework" +#define XUL_DLL "XUL" +#else #define XPCOM_SEARCH_KEY "LD_LIBRARY_PATH" #define XUL_DLL "libxul" MOZ_DLL_SUFFIX +#endif #define GRE_CONF_NAME ".gre.config" #define GRE_CONF_PATH "/etc/gre.conf" diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h index 18ccca4158..56d2496b26 100644 --- a/xpcom/build/nsXULAppAPI.h +++ b/xpcom/build/nsXULAppAPI.h @@ -106,7 +106,7 @@ */ #define XRE_SYS_SHARE_EXTENSION_PARENT_DIR "XRESysSExtPD" -#if defined(XP_UNIX) +#if defined(XP_UNIX) || defined(XP_MACOSX) /** * Directory service keys for the system-wide and user-specific * directories where host manifests used by the WebExtensions diff --git a/xpcom/components/nsNativeModuleLoader.cpp b/xpcom/components/nsNativeModuleLoader.cpp index 95b8349f19..bec3a11757 100644 --- a/xpcom/components/nsNativeModuleLoader.cpp +++ b/xpcom/components/nsNativeModuleLoader.cpp @@ -33,6 +33,10 @@ #include <windows.h> #endif +#ifdef XP_MACOSX +#include <signal.h> +#endif + #ifdef DEBUG #define IMPLEMENT_BREAK_AFTER_LOAD #endif diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp index 04bba85cb4..8057cf8cb1 100644 --- a/xpcom/glue/FileUtils.cpp +++ b/xpcom/glue/FileUtils.cpp @@ -12,7 +12,16 @@ #include "mozilla/Assertions.h" #include "mozilla/FileUtils.h" -#if defined(XP_UNIX) +#if defined(XP_MACOSX) +#include <fcntl.h> +#include <unistd.h> +#include <mach/machine.h> +#include <mach-o/fat.h> +#include <mach-o/loader.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <limits.h> +#elif defined(XP_UNIX) #include <fcntl.h> #include <unistd.h> #if defined(LINUX) @@ -47,6 +56,20 @@ mozilla::fallocate(PRFileDesc* aFD, int64_t aLength) PR_Seek64(aFD, oldpos, PR_SEEK_SET); return retval; +#elif defined(XP_MACOSX) + int fd = PR_FileDesc2NativeHandle(aFD); + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength}; + // Try to get a continous chunk of disk space + int ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + return false; + } + } + return ftruncate(fd, aLength) == 0; #elif defined(XP_UNIX) // The following is copied from fcntlSizeHint in sqlite /* If the OS does not have posix_fallocate(), fake it. First use @@ -202,7 +225,7 @@ mozilla::ReadAheadLib(nsIFile* aFile) return; } ReadAheadLib(path.get()); -#elif defined(LINUX) +#elif defined(LINUX) || defined(XP_MACOSX) nsAutoCString nativePath; if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { return; @@ -221,7 +244,7 @@ mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset, return; } ReadAheadFile(path.get(), aOffset, aCount, aOutFd); -#elif defined(LINUX) +#elif defined(LINUX) || defined(XP_MACOSX) nsAutoCString nativePath; if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { return; @@ -248,6 +271,64 @@ static const unsigned char ELFCLASS = ELFCLASS32; typedef Elf32_Off Elf_Off; #endif +#elif defined(XP_MACOSX) + +#if defined(__i386__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86; +#elif defined(__x86_64__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86_64; +#elif defined(__ppc__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC; +#elif defined(__ppc64__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64; +#else +#error Unsupported CPU type +#endif + +#ifdef __LP64__ +#undef LC_SEGMENT +#define LC_SEGMENT LC_SEGMENT_64 +#undef MH_MAGIC +#define MH_MAGIC MH_MAGIC_64 +#define cpu_mach_header mach_header_64 +#define segment_command segment_command_64 +#else +#define cpu_mach_header mach_header +#endif + +class ScopedMMap +{ +public: + explicit ScopedMMap(const char* aFilePath) + : buf(nullptr) + { + fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + struct stat st; + if (fstat(fd, &st) < 0) { + return; + } + size = st.st_size; + buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + } + ~ScopedMMap() + { + if (buf) { + munmap(buf, size); + } + if (fd >= 0) { + close(fd); + } + } + operator char*() { return buf; } + int getFd() { return fd; } +private: + int fd; + char* buf; + size_t size; +}; #endif void @@ -304,6 +385,14 @@ mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset, readahead(aFd, aOffset, aCount); +#elif defined(XP_MACOSX) + + struct radvisory ra; + ra.ra_offset = aOffset; + ra.ra_count = aCount; + // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call. + fcntl(aFd, F_RDADVISE, &ra); + #endif } @@ -360,6 +449,62 @@ mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) ReadAhead(fd, 0, end); } close(fd); +#elif defined(XP_MACOSX) + ScopedMMap buf(aFilePath); + char* base = buf; + if (!base) { + return; + } + + // An OSX binary might either be a fat (universal) binary or a + // Mach-O binary. A fat binary actually embeds several Mach-O + // binaries. If we have a fat binary, find the offset where the + // Mach-O binary for our CPU type can be found. + struct fat_header* fh = (struct fat_header*)base; + + if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) { + uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); + struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)]; + for (; nfat_arch; arch++, nfat_arch--) { + if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) { + base += OSSwapBigToHostInt32(arch->offset); + break; + } + } + if (base == buf) { + return; + } + } + + // Check Mach-O magic in the Mach header + struct cpu_mach_header* mh = (struct cpu_mach_header*)base; + if (mh->magic != MH_MAGIC) { + return; + } + + // The Mach header is followed by a sequence of load commands. + // Each command has a header containing the command type and the + // command size. LD_SEGMENT commands describes how the dynamic + // loader is going to map the file in memory. We use that + // information to find the biggest offset from the library that + // will be mapped in memory. + char* cmd = &base[sizeof(struct cpu_mach_header)]; + uint32_t end = 0; + for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) { + struct segment_command* sh = (struct segment_command*)cmd; + if (sh->cmd != LC_SEGMENT) { + continue; + } + if (end < sh->fileoff + sh->filesize) { + end = sh->fileoff + sh->filesize; + } + cmd += sh->cmdsize; + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(buf.getFd(), base - buf, end); + } #endif } @@ -386,7 +531,7 @@ mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, if (!aOutFd) { CloseHandle(fd); } -#elif defined(LINUX) || defined(XP_SOLARIS) +#elif defined(LINUX) || defined(XP_MACOSX) || defined(XP_SOLARIS) if (!aFilePath) { if (aOutFd) { *aOutFd = -1; diff --git a/xpcom/glue/nsCRTGlue.h b/xpcom/glue/nsCRTGlue.h index 6eda4bf8df..d3c666d05c 100644 --- a/xpcom/glue/nsCRTGlue.h +++ b/xpcom/glue/nsCRTGlue.h @@ -124,7 +124,9 @@ void NS_MakeRandomString(char* aBuf, int32_t aBufLen); // identify or replace all known path separators. #define KNOWN_PATH_SEPARATORS "\\/" -#if defined(XP_WIN) +#if defined(XP_MACOSX) + #define FILE_PATH_SEPARATOR "/" +#elif defined(XP_WIN) #define FILE_PATH_SEPARATOR "\\" #elif defined(XP_UNIX) #define FILE_PATH_SEPARATOR "/" diff --git a/xpcom/glue/nsThreadUtils.cpp b/xpcom/glue/nsThreadUtils.cpp index 8743c0d5fb..b6a37a7b83 100644 --- a/xpcom/glue/nsThreadUtils.cpp +++ b/xpcom/glue/nsThreadUtils.cpp @@ -19,6 +19,8 @@ #ifdef XP_WIN #include <windows.h> +#elif defined(XP_MACOSX) +#include <sys/resource.h> #endif using namespace mozilla; @@ -437,6 +439,12 @@ nsAutoLowPriorityIO::nsAutoLowPriorityIO() #if defined(XP_WIN) lowIOPrioritySet = SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); +#elif defined(XP_MACOSX) + oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + lowIOPrioritySet = oldPriority != -1 && + setiopolicy_np(IOPOL_TYPE_DISK, + IOPOL_SCOPE_THREAD, + IOPOL_THROTTLE) != -1; #else lowIOPrioritySet = false; #endif @@ -449,5 +457,9 @@ nsAutoLowPriorityIO::~nsAutoLowPriorityIO() // On Windows the old thread priority is automatically restored SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); } +#elif defined(XP_MACOSX) + if (MOZ_LIKELY(lowIOPrioritySet)) { + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority); + } #endif } diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h index 9942a1060a..01270c1e94 100644 --- a/xpcom/glue/nsThreadUtils.h +++ b/xpcom/glue/nsThreadUtils.h @@ -1037,6 +1037,9 @@ public: private: bool lowIOPrioritySet; +#if defined(XP_MACOSX) + int oldPriority; +#endif }; void diff --git a/xpcom/glue/standalone/nsXPCOMGlue.cpp b/xpcom/glue/standalone/nsXPCOMGlue.cpp index e4a5d8bd44..68dd58d1ed 100644 --- a/xpcom/glue/standalone/nsXPCOMGlue.cpp +++ b/xpcom/glue/standalone/nsXPCOMGlue.cpp @@ -97,7 +97,11 @@ static LibHandleType GetLibHandle(pathstr_t aDependentLib) { LibHandleType libHandle = dlopen(aDependentLib, - RTLD_GLOBAL | RTLD_LAZY); + RTLD_GLOBAL | RTLD_LAZY +#ifdef XP_MACOSX + | RTLD_FIRST +#endif + ); if (!libHandle) { fprintf(stderr, "XPCOMGlueLoad error for file %s:\n%s\n", aDependentLib, dlerror()); @@ -231,6 +235,22 @@ XPCOMGlueLoad(const char* aXPCOMFile) char xpcomDir[MAXPATHLEN]; #ifdef XP_WIN const char* lastSlash = ns_strrpbrk(aXPCOMFile, "/\\"); +#elif XP_MACOSX + // On OSX, the dependentlibs.list file lives under Contents/Resources. + // However, the actual libraries listed in dependentlibs.list live under + // Contents/MacOS. We want to read the list from Contents/Resources, then + // load the libraries from Contents/MacOS. + const char *tempSlash = strrchr(aXPCOMFile, '/'); + size_t tempLen = size_t(tempSlash - aXPCOMFile); + if (tempLen > MAXPATHLEN) { + return nullptr; + } + char tempBuffer[MAXPATHLEN]; + memcpy(tempBuffer, aXPCOMFile, tempLen); + tempBuffer[tempLen] = '\0'; + const char *slash = strrchr(tempBuffer, '/'); + tempLen = size_t(slash - tempBuffer); + const char *lastSlash = aXPCOMFile + tempLen; #else const char* lastSlash = strrchr(aXPCOMFile, '/'); #endif @@ -239,11 +259,19 @@ XPCOMGlueLoad(const char* aXPCOMFile) size_t len = size_t(lastSlash - aXPCOMFile); if (len > MAXPATHLEN - sizeof(XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif XPCOM_DEPENDENT_LIBS_LIST)) { return nullptr; } memcpy(xpcomDir, aXPCOMFile, len); strcpy(xpcomDir + len, XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif XPCOM_DEPENDENT_LIBS_LIST); cursor = xpcomDir + len + 1; } else { @@ -261,6 +289,14 @@ XPCOMGlueLoad(const char* aXPCOMFile) return nullptr; } +#ifdef XP_MACOSX + tempLen = size_t(cursor - xpcomDir); + if (tempLen > MAXPATHLEN - sizeof("MacOS" XPCOM_FILE_PATH_SEPARATOR) - 1) { + return nullptr; + } + strcpy(cursor, "MacOS" XPCOM_FILE_PATH_SEPARATOR); + cursor += strlen(cursor); +#endif *cursor = '\0'; char buffer[MAXPATHLEN]; diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h new file mode 100644 index 0000000000..919c127162 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 namespace contains methods with Obj-C/Cocoa implementations. The header +// is C/C++ for inclusion in C/C++-only files. + +#ifndef CocoaFileUtils_h_ +#define CocoaFileUtils_h_ + +#include "nscore.h" +#include <CoreFoundation/CoreFoundation.h> + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef aUrl); +nsresult OpenURL(CFURLRef aUrl); +nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode); +nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode); +nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode); +nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode); +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL); +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb); +CFURLRef GetTemporaryFolderCFURLRef(); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 0000000000..a02b82ac1d --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* 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/. */ + +#include "CocoaFileUtils.h" +#include "nsCocoaFeatures.h" +#include "nsCocoaUtils.h" +#include <Cocoa/Cocoa.h> +#include "nsObjCExceptions.h" +#include "nsDebug.h" + +// Need to cope with us using old versions of the SDK and needing this on 10.10+ +#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) +const CFStringRef kCFURLQuarantinePropertiesKey = CFSTR("NSURLQuarantinePropertiesKey"); +#endif + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] inFileViewerRootedAtPath:@""]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult OpenURL(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; + if (!creatorNum) { + return NS_ERROR_FAILURE; + } + + *creatorCode = [creatorNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] forKey:NSFileHFSCreatorCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; + if (!typeNum) { + return NS_ERROR_FAILURE; + } + + *typeCode = [typeNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL) { + typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef); + static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + + CFBundleRef metadata_bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); + if (!metadata_bundle) { + return; + } + + mdItemSetAttributeFunc = (MDItemSetAttribute_type) + ::CFBundleGetFunctionPointerForName(metadata_bundle, CFSTR("MDItemSetAttribute")); + } + if (!mdItemSetAttributeFunc) { + return; + } + + MDItemRef mdItem = ::MDItemCreate(NULL, filePath); + if (!mdItem) { + return; + } + + CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL); + if (!list) { + ::CFRelease(mdItem); + return; + } + + // The first item in the list is the source URL of the downloaded file. + if (sourceURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL)); + } + + // If the referrer is known, store that in the second position. + if (referrerURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL)); + } + + mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list); + + ::CFRelease(list); + ::CFRelease(mdItem); +} + +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb) { + CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + filePath, + kCFURLPOSIXPathStyle, + false); + + // The properties key changed in 10.10: + CFStringRef quarantinePropKey; + if (nsCocoaFeatures::OnYosemiteOrLater()) { + quarantinePropKey = kCFURLQuarantinePropertiesKey; + } else { + quarantinePropKey = kLSItemQuarantineProperties; + } + CFDictionaryRef quarantineProps = NULL; + Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL, + quarantinePropKey, + &quarantineProps, + NULL); + + // If there aren't any quarantine properties then the user probably + // set up an exclusion and we don't need to add metadata. + if (!success || !quarantineProps) { + ::CFRelease(fileURL); + return; + } + + // We don't know what to do if the props aren't a dictionary. + if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) { + ::CFRelease(fileURL); + ::CFRelease(quarantineProps); + return; + } + + // Make a mutable copy of the properties. + CFMutableDictionaryRef mutQuarantineProps = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps); + ::CFRelease(quarantineProps); + + // Add metadata that the OS couldn't infer. + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) { + CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload; + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL); + } + + // Set quarantine properties on file. + ::CFURLSetResourcePropertyForKey(fileURL, + quarantinePropKey, + mutQuarantineProps, + NULL); + + ::CFRelease(fileURL); + ::CFRelease(mutQuarantineProps); +} + +CFURLRef GetTemporaryFolderCFURLRef() +{ + NSString* tempDir = ::NSTemporaryDirectory(); + return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir + isDirectory:YES]; +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build index 467d61682b..43ce517c45 100644 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -37,6 +37,11 @@ XPIDL_SOURCES += [ 'nsIUnicharOutputStream.idl', ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsILocalFileMac.idl', + ] + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': EXPORTS += ['nsLocalFileWin.h'] EXPORTS.mozilla += [ @@ -118,6 +123,11 @@ SOURCES += [ 'FilePreferences.cpp', ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'CocoaFileUtils.mm', + ] + include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 0000000000..d8655449bb --- /dev/null +++ b/xpcom/io/nsILocalFileMac.idl @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsILocalFile.idl" + +%{C++ +#include <Carbon/Carbon.h> +#include <CoreFoundation/CoreFoundation.h> +%} + + native OSType(OSType); + native FSSpec(FSSpec); + native FSRef(FSRef); +[ptr] native FSRefPtr(FSRef); + native CFURLRef(CFURLRef); + +[scriptable, builtinclass, uuid(623eca5b-c25d-4e27-be5a-789a66c4b2f7)] +interface nsILocalFileMac : nsILocalFile +{ + /** + * initWithCFURL + * + * Init this object with a CFURLRef + * + * NOTE: Supported only for XP_MACOSX + * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand. + * + * @param aCFURL the CoreFoundation URL + * + */ + [noscript] void initWithCFURL(in CFURLRef aCFURL); + + /** + * initWithFSRef + * + * Init this object with an FSRef + * + * NOTE: Supported only for XP_MACOSX + * + * @param aFSRef the native FSRef + * + */ + [noscript] void initWithFSRef([const] in FSRefPtr aFSRef); + + /** + * getCFURL + * + * Returns the CFURLRef of the file object. The caller is + * responsible for calling CFRelease() on it. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] CFURLRef getCFURL(); + + /** + * getFSRef + * + * Returns the FSRef of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] FSRef getFSRef(); + + /** + * getFSSpec + * + * Returns the FSSpec of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * @return + * + */ + [noscript] FSSpec getFSSpec(); + + /** + * fileSizeWithResFork + * + * Returns the combined size of both the data fork and the resource + * fork (if present) rather than just the size of the data fork + * as returned by GetFileSize() + * + */ + readonly attribute int64_t fileSizeWithResFork; + + /** + * fileType, creator + * + * File type and creator attributes + * + */ + [noscript] attribute OSType fileType; + [noscript] attribute OSType fileCreator; + + /** + * launchWithDoc + * + * Launch the application that this file points to with a document. + * + * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground); + + /** + * openDocWithApp + * + * Open the document that this file points to with the given application. + * + * @param aAppToOpenWith The application with which to open the document. + * If NULL, the creator code of the document is used + * to determine the application. + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground); + + /** + * isPackage + * + * returns true if a directory is determined to be a package under Mac OS 9/X + * + */ + boolean isPackage(); + + /** + * bundleDisplayName + * + * returns the display name of the application bundle (usually the human + * readable name of the application) + */ + readonly attribute AString bundleDisplayName; + + /** + * bundleIdentifier + * + * returns the identifier of the bundle + */ + readonly attribute AUTF8String bundleIdentifier; + + /** + * Last modified time of a bundle's contents (as opposed to its package + * directory). Our convention is to make the bundle's Info.plist file + * stand in for the rest of its contents -- since this file contains the + * bundle's version information and other identifiers. For non-bundles + * this is the same as lastModifiedTime. + */ + readonly attribute int64_t bundleContentsLastModifiedTime; +}; + +%{C++ +extern "C" +{ +NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result); +NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result); +} +%} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp index b6afb00ed2..d4a8a12fbc 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp @@ -53,7 +53,9 @@ __asm__ ( is what xptcstubs uses. */ ".align 2\n\t" ".globl " SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#ifndef XP_MACOSX ".type " SYMBOL_UNDERSCORE "NS_InvokeByIndex,@function\n" +#endif SYMBOL_UNDERSCORE "NS_InvokeByIndex:\n\t" "pushl %ebp\n\t" "movl %esp, %ebp\n\t" @@ -89,5 +91,7 @@ __asm__ ( "movl %ebp, %esp\n\t" "popl %ebp\n\t" "ret\n" +#ifndef XP_MACOSX ".size " SYMBOL_UNDERSCORE "NS_InvokeByIndex, . -" SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#endif ); diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp index 5e2a9c17f8..6811a26ad6 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp @@ -66,11 +66,20 @@ PrepareAndDispatch(uint32_t methodIndex, nsXPTCStubBase* self, uint32_t* args) } } // extern "C" +#if !defined(XP_MACOSX) + #define STUB_HEADER(a, b) ".hidden " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" \ ".type " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,@function\n" #define STUB_SIZE(a, b) ".size " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,.-" SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" +#else + +#define STUB_HEADER(a, b) +#define STUB_SIZE(a, b) + +#endif + // gcc3 mangling tends to insert the length of the method name #define STUB_ENTRY(n) \ asm(".text\n\t" \ @@ -103,12 +112,16 @@ asm(".text\n\t" \ // static nsresult SharedStub(uint32_t methodIndex) __attribute__((regparm(1))) asm(".text\n\t" ".align 2\n\t" +#if !defined(XP_MACOSX) ".type " SYMBOL_UNDERSCORE "SharedStub,@function\n\t" +#endif SYMBOL_UNDERSCORE "SharedStub:\n\t" "leal 0x08(%esp), %ecx\n\t" "movl 0x04(%esp), %edx\n\t" "jmp " SYMBOL_UNDERSCORE "PrepareAndDispatch\n\t" +#if !defined(XP_MACOSX) ".size " SYMBOL_UNDERSCORE "SharedStub,.-" SYMBOL_UNDERSCORE "SharedStub" +#endif ); #define SENTINEL_ENTRY(n) \ diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h index 46227b3fd7..e4e07e2ccd 100644 --- a/xpcom/threads/SharedThreadPool.h +++ b/xpcom/threads/SharedThreadPool.h @@ -87,7 +87,7 @@ public: // Use the system default in ASAN builds, because the default is assumed to be // larger than the size we want to use and is hopefully sufficient for ASAN. static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE; -#elif defined(XP_WIN) || defined(LINUX) +#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX) static const uint32_t kStackSize = (256 * 1024); #else // All other platforms use their system defaults. diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h index 21d28f1019..b9d0641770 100644 --- a/xpcom/threads/nsProcess.h +++ b/xpcom/threads/nsProcess.h @@ -19,7 +19,9 @@ #include "nsIWeakReferenceUtils.h" #include "nsIObserver.h" #include "nsString.h" +#ifndef XP_MACOSX #include "prproces.h" +#endif #if defined(PROCESSMODEL_WINAPI) #include <windows.h> #include <shellapi.h> @@ -71,7 +73,7 @@ private: int32_t mExitValue; #if defined(PROCESSMODEL_WINAPI) HANDLE mProcess; -#else +#elif !defined(XP_MACOSX) PRProcess* mProcess; #endif }; diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp index 7d490c5952..558f5e2890 100644 --- a/xpcom/threads/nsProcessCommon.cpp +++ b/xpcom/threads/nsProcessCommon.cpp @@ -33,12 +33,31 @@ #include "nsLiteralString.h" #include "nsReadableUtils.h" #else +#ifdef XP_MACOSX +#include <crt_externs.h> +#include <spawn.h> +#include <sys/wait.h> +#include <sys/errno.h> +#endif #include <sys/types.h> #include <signal.h> #endif using namespace mozilla; +#ifdef XP_MACOSX +cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY +}; +#endif + //-------------------------------------------------------------------// // nsIProcess implementation //-------------------------------------------------------------------// @@ -55,7 +74,9 @@ nsProcess::nsProcess() , mObserver(nullptr) , mWeakObserver(nullptr) , mExitValue(-1) +#if !defined(XP_MACOSX) , mProcess(nullptr) +#endif { } @@ -241,15 +262,33 @@ nsProcess::Monitor(void* aArg) } } #else +#ifdef XP_MACOSX + int exitCode = -1; + int status = 0; + pid_t result; + do { + result = waitpid(process->mPid, &status, 0); + } while (result == -1 && errno == EINTR); + if (result == process->mPid) { + if (WIFEXITED(status)) { + exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitCode = 256; // match NSPR's signal exit status + } + } +#else int32_t exitCode = -1; if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) { exitCode = -1; } +#endif // Lock in case Kill or GetExitCode are called during this { MutexAutoLock lock(process->mLock); +#if !defined(XP_MACOSX) process->mProcess = nullptr; +#endif process->mExitValue = exitCode; if (process->mShutdown) { return; @@ -466,6 +505,34 @@ nsProcess::RunProcess(bool aBlocking, char** aMyArgv, nsIObserver* aObserver, } mPid = GetProcessId(mProcess); +#elif defined(XP_MACOSX) + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + return NS_ERROR_FAILURE; + } + + // Set spawn attributes. + size_t attr_count = ArrayLength(pref_cpu_types); + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, + &attr_ocount) != 0 || + attr_ocount != attr_count) { + posix_spawnattr_destroy(&spawnattr); + return NS_ERROR_FAILURE; + } + + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast<int32_t>(newPid); + + posix_spawnattr_destroy(&spawnattr); + + if (result != 0) { + return NS_ERROR_FAILURE; + } #else mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); if (!mProcess) { @@ -545,6 +612,10 @@ nsProcess::Kill() if (TerminateProcess(mProcess, 0) == 0) { return NS_ERROR_FAILURE; } +#elif defined(XP_MACOSX) + if (kill(mPid, SIGKILL) != 0) { + return NS_ERROR_FAILURE; + } #else if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) { return NS_ERROR_FAILURE; diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index ed67fa6594..71e71822fa 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -52,6 +52,11 @@ #define HAVE_SCHED_SETAFFINITY #endif +#ifdef XP_MACOSX +#include <mach/mach.h> +#include <mach/thread_policy.h> +#endif + #ifdef MOZ_CANARY # include <unistd.h> # include <execinfo.h> @@ -370,6 +375,16 @@ SetThreadAffinity(unsigned int cpu) sched_setaffinity(0, sizeof(cpus), &cpus); // Don't assert sched_setaffinity's return value because it intermittently (?) // fails with EINVAL on Linux x64 try runs. +#elif defined(XP_MACOSX) + // OS X does not provide APIs to pin threads to specific processors, but you + // can tag threads as belonging to the same "affinity set" and the OS will try + // to run them on the same processor. To run threads on different processors, + // tag them as belonging to different affinity sets. Tag 0, the default, means + // "no affinity" so let's pretend each CPU has its own tag `cpu+1`. + thread_affinity_policy_data_t policy; + policy.affinity_tag = cpu + 1; + MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, + &policy.affinity_tag, 1) == KERN_SUCCESS); #elif defined(XP_WIN) MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != -1); #endif |