mac: Handle _dyld_get_all_image_infos() not being available on 10.13

_dyld_get_all_image_infos() was only used in test code in Crashpad.

This addresses two related problems.

When running on 10.13 or later, _dyld_get_all_image_infos() is not
available. It appears to still be implemented in dyld, but its symbol is
now private. This was always known to be an “internal” interface. When
it’s not available, fall back to obtaining the address of the process’
dyld_all_image_infos structure by calling task_info(…, TASK_DYLD_INFO,
…). Note that this is the same thing that the code being tested does,
although the tests are not rendered entirely pointless because the code
being tested consumes dyld_all_image_infos through its own
implementation of an out-of-process reader interface, while the
dyld_all_image_infos data obtained by _dyld_get_all_image_infos() is
handled strictly in-process by ordinary memory reads. This is covered by
bug 187.

When building with the 10.13 SDK, no _dyld_get_all_image_infos symbol is
available to link against. In this case, access the symbol strictly at
runtime via dlopen() if it may be available, or when expecting to only
run on 10.13 and later, don’t even bother looking for this symbol. This
is covered by part of bug 188.

Bug: crashpad:185, crashpad:187, crashpad:188
Change-Id: Ib283e070faf5d1ec35deee420213b53ec24fb1d3
Reviewed-on: https://chromium-review.googlesource.com/534633
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Mark Mentovai 2017-06-14 10:48:30 -04:00
parent 2851e5cfc8
commit 107fb76317
12 changed files with 326 additions and 101 deletions

View File

@ -53,4 +53,10 @@
#define MAC_OS_X_VERSION_10_12 101200
#endif
// 10.13 SDK
#ifndef MAC_OS_X_VERSION_10_13
#define MAC_OS_X_VERSION_10_13 101300
#endif
#endif // CRASHPAD_COMPAT_MAC_AVAILABILITYMACROS_H_

View File

@ -21,6 +21,7 @@
#include "client/crashpad_info.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/scoped_module_handle.h"
#include "test/test_paths.h"
#if defined(OS_MACOSX)
@ -138,48 +139,6 @@ TEST(CrashpadInfoClientOptions, OneModule) {
}
}
#if defined(OS_POSIX)
using DlHandle = void*;
#elif defined(OS_WIN)
using DlHandle = HMODULE;
#endif // OS_POSIX
class ScopedDlHandle {
public:
explicit ScopedDlHandle(DlHandle dl_handle)
: dl_handle_(dl_handle) {
}
~ScopedDlHandle() {
if (dl_handle_) {
#if defined(OS_POSIX)
if (dlclose(dl_handle_) != 0) {
LOG(ERROR) << "dlclose: " << dlerror();
}
#elif defined(OS_WIN)
if (!FreeLibrary(dl_handle_))
PLOG(ERROR) << "FreeLibrary";
#endif // OS_POSIX
}
}
bool valid() const { return dl_handle_ != nullptr; }
template <typename T>
T LookUpSymbol(const char* symbol_name) {
#if defined(OS_POSIX)
return reinterpret_cast<T>(dlsym(dl_handle_, symbol_name));
#elif defined(OS_WIN)
return reinterpret_cast<T>(GetProcAddress(dl_handle_, symbol_name));
#endif // OS_POSIX
}
private:
DlHandle dl_handle_;
DISALLOW_COPY_AND_ASSIGN(ScopedDlHandle);
};
TEST(CrashpadInfoClientOptions, TwoModules) {
// Open the module, which has its own CrashpadInfo structure.
#if defined(OS_MACOSX)
@ -190,14 +149,14 @@ TEST(CrashpadInfoClientOptions, TwoModules) {
base::FilePath module_path = TestPaths::Executable().DirName().Append(
FILE_PATH_LITERAL("crashpad_snapshot_test_module") + kDlExtension);
#if defined(OS_MACOSX)
ScopedDlHandle dl_handle(
ScopedModuleHandle module(
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
ASSERT_TRUE(dl_handle.valid()) << "dlopen " << module_path.value() << ": "
ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": "
<< dlerror();
#elif defined(OS_WIN)
ScopedDlHandle dl_handle(LoadLibrary(module_path.value().c_str()));
ASSERT_TRUE(dl_handle.valid())
<< "LoadLibrary " << base::UTF16ToUTF8(module_path.value()) << ": "
ScopedModuleHandle module(LoadLibrary(module_path.value().c_str()));
ASSERT_TRUE(module.valid()) << "LoadLibrary "
<< base::UTF16ToUTF8(module_path.value()) << ": "
<< ErrorMessage();
#else
#error Port.
@ -207,7 +166,7 @@ TEST(CrashpadInfoClientOptions, TwoModules) {
// because it runs in the module, it returns the remote modules CrashpadInfo
// structure.
CrashpadInfo* (*TestModule_GetCrashpadInfo)() =
dl_handle.LookUpSymbol<CrashpadInfo* (*)()>("TestModule_GetCrashpadInfo");
module.LookUpSymbol<CrashpadInfo* (*)()>("TestModule_GetCrashpadInfo");
ASSERT_TRUE(TestModule_GetCrashpadInfo);
auto options = SelfProcessSnapshotAndGetCrashpadOptions();

View File

@ -577,8 +577,7 @@ TEST(MachOImageReader, Self_DyldImages) {
// Now that all of the modules have been verified, make sure that dyld itself
// can be read properly too.
const struct dyld_all_image_infos* dyld_image_infos =
_dyld_get_all_image_infos();
const dyld_all_image_infos* dyld_image_infos = DyldGetAllImageInfos();
ASSERT_GE(dyld_image_infos->version, 1u);
EXPECT_EQ(dyld_image_infos->infoArrayCount, count);

View File

@ -198,6 +198,46 @@ const std::vector<ProcessReader::Module>& ProcessReader::Modules() {
return modules_;
}
mach_vm_address_t ProcessReader::DyldAllImageInfo(
mach_vm_size_t* all_image_info_size) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
task_dyld_info_data_t dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kern_return_t kr = task_info(
task_, TASK_DYLD_INFO, reinterpret_cast<task_info_t>(&dyld_info), &count);
if (kr != KERN_SUCCESS) {
MACH_LOG(WARNING, kr) << "task_info";
return 0;
}
// TODO(mark): Deal with statically linked executables which dont use dyld.
// This may look for the module that matches the executable path in the same
// data set that vmmap uses.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
// The task_dyld_info_data_t struct grew in 10.7, adding the format field.
// Dont check this field if its not present, which can happen when either
// the SDK used at compile time or the kernel at run time are too old and
// dont know about it.
if (count >= TASK_DYLD_INFO_COUNT) {
const integer_t kExpectedFormat =
!Is64Bit() ? TASK_DYLD_ALL_IMAGE_INFO_32 : TASK_DYLD_ALL_IMAGE_INFO_64;
if (dyld_info.all_image_info_format != kExpectedFormat) {
LOG(WARNING) << "unexpected task_dyld_info_data_t::all_image_info_format "
<< dyld_info.all_image_info_format;
DCHECK_EQ(dyld_info.all_image_info_format, kExpectedFormat);
return 0;
}
}
#endif
if (all_image_info_size) {
*all_image_info_size = dyld_info.all_image_info_size;
}
return dyld_info.all_image_info_addr;
}
void ProcessReader::InitializeThreads() {
DCHECK(!initialized_threads_);
DCHECK(threads_.empty());
@ -345,38 +385,12 @@ void ProcessReader::InitializeModules() {
initialized_modules_ = true;
task_dyld_info_data_t dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kern_return_t kr = task_info(
task_, TASK_DYLD_INFO, reinterpret_cast<task_info_t>(&dyld_info), &count);
if (kr != KERN_SUCCESS) {
MACH_LOG(WARNING, kr) << "task_info";
return;
}
// TODO(mark): Deal with statically linked executables which dont use dyld.
// This may look for the module that matches the executable path in the same
// data set that vmmap uses.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
// The task_dyld_info_data_t struct grew in 10.7, adding the format field.
// Dont check this field if its not present, which can happen when either
// the SDK used at compile time or the kernel at run time are too old and
// dont know about it.
if (count >= TASK_DYLD_INFO_COUNT) {
const integer_t kExpectedFormat =
!Is64Bit() ? TASK_DYLD_ALL_IMAGE_INFO_32 : TASK_DYLD_ALL_IMAGE_INFO_64;
if (dyld_info.all_image_info_format != kExpectedFormat) {
LOG(WARNING) << "unexpected task_dyld_info_data_t::all_image_info_format "
<< dyld_info.all_image_info_format;
DCHECK_EQ(dyld_info.all_image_info_format, kExpectedFormat);
return;
}
}
#endif
mach_vm_size_t all_image_info_size;
mach_vm_address_t all_image_info_addr =
DyldAllImageInfo(&all_image_info_size);
process_types::dyld_all_image_infos all_image_infos;
if (!all_image_infos.Read(this, dyld_info.all_image_info_addr)) {
if (!all_image_infos.Read(this, all_image_info_addr)) {
LOG(WARNING) << "could not read dyld_all_image_infos";
return;
}
@ -390,10 +404,10 @@ void ProcessReader::InitializeModules() {
size_t expected_size =
process_types::dyld_all_image_infos::ExpectedSizeForVersion(
this, all_image_infos.version);
if (dyld_info.all_image_info_size < expected_size) {
LOG(WARNING) << "small dyld_all_image_infos size "
<< dyld_info.all_image_info_size << " < " << expected_size
<< " for version " << all_image_infos.version;
if (all_image_info_size < expected_size) {
LOG(WARNING) << "small dyld_all_image_infos size " << all_image_info_size
<< " < " << expected_size << " for version "
<< all_image_infos.version;
return;
}

View File

@ -150,6 +150,21 @@ class ProcessReader {
//! corresponds to the dynamic loader, dyld.
const std::vector<Module>& Modules();
//! \brief Determines the location of the `dyld_all_image_infos` structure in
//! the process address space.
//!
//! This function is an internal implementation detail of Modules(), and
//! should not normally be used directly. It is exposed solely for use by test
//! code.
//!
//! \param[out] all_image_info_size The size of the `dyld_all_image_infos`
//! structure. Optional, may be `nullptr` if not required.
//!
//! \return The address of the `dyld_all_image_infos` structure in the
//! process address space, with \a all_image_info_size set appropriately.
//! On failure, returns `0` with a message logged.
mach_vm_address_t DyldAllImageInfo(mach_vm_size_t* all_image_info_size);
private:
//! Performs lazy initialization of the \a threads_ vector on behalf of
//! Threads().

View File

@ -44,12 +44,13 @@
#include "util/stdlib/pointer_container.h"
#include "util/synchronization/semaphore.h"
#if !defined(MAC_OS_X_VERSION_10_10) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
extern "C" {
// Redeclare a typedef whose availability (OS X 10.10) is newer than the
// deployment target.
typedef struct _cl_device_id* cl_device_id;
} // extern "C"
#endif
@ -698,8 +699,7 @@ TEST(ProcessReader, SelfModules) {
// is also reported as 0.
EXPECT_EQ(modules[index].timestamp, 0);
const struct dyld_all_image_infos* dyld_image_infos =
_dyld_get_all_image_infos();
const dyld_all_image_infos* dyld_image_infos = DyldGetAllImageInfos();
if (dyld_image_infos->version >= 2) {
ASSERT_TRUE(modules[index].reader);
EXPECT_EQ(modules[index].reader->Address(),
@ -781,8 +781,7 @@ class ProcessReaderModulesChild final : public MachMultiprocess {
FileHandle write_handle = WritePipeHandle();
uint32_t dyld_image_count = _dyld_image_count();
const struct dyld_all_image_infos* dyld_image_infos =
_dyld_get_all_image_infos();
const dyld_all_image_infos* dyld_image_infos = DyldGetAllImageInfos();
uint32_t write_image_count = dyld_image_count;
if (dyld_image_infos->version >= 2) {

View File

@ -46,8 +46,7 @@ namespace {
TEST(ProcessTypes, DyldImagesSelf) {
// Get the in-process view of dyld_all_image_infos, and check it for sanity.
const struct dyld_all_image_infos* self_image_infos =
_dyld_get_all_image_infos();
const dyld_all_image_infos* self_image_infos = DyldGetAllImageInfos();
int mac_os_x_minor_version = MacOSXMinorVersion();
if (mac_os_x_minor_version >= 12) {
EXPECT_GE(self_image_infos->version, 15u);

99
test/mac/dyld.cc Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/mac/dyld.h"
#include <AvailabilityMacros.h>
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach-o/dyld.h>
#include <stdint.h>
#include "base/logging.h"
#include "snapshot/mac/process_reader.h"
#include "test/scoped_module_handle.h"
#include "util/numeric/safe_assignment.h"
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
extern "C" {
// A non-public dyld API, declared in 10.12.4
// dyld-433.5/include/mach-o/dyld_priv.h. The code still exists in 10.13, but
// its symbol is no longer public, so it cant be used there.
const dyld_all_image_infos* _dyld_get_all_image_infos()
__attribute__((weak_import));
} // extern "C"
#endif
namespace crashpad {
namespace test {
const dyld_all_image_infos* DyldGetAllImageInfos() {
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
// When building with the pre-10.13 SDK, the weak_import declaration above is
// available and a symbol will be present in the SDK to link against. If the
// old interface is also available at run time (running on pre-10.13), use it.
if (_dyld_get_all_image_infos) {
return _dyld_get_all_image_infos();
}
#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13
// When building with the 10.13 SDK or later, but able to run on pre-10.13,
// look for _dyld_get_all_image_infos in the same module that provides
// _dyld_image_count. Theres no symbol in the SDK to link against, so this is
// a little more involved than the pre-10.13 SDK case above.
Dl_info dli;
if (!dladdr(reinterpret_cast<void*>(_dyld_image_count), &dli)) {
LOG(WARNING) << "dladdr: failed";
} else {
ScopedModuleHandle module(
dlopen(dli.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD));
if (!module.valid()) {
LOG(WARNING) << "dlopen: " << dlerror();
} else {
using DyldGetAllImageInfosType = const dyld_all_image_infos*(*)();
const auto _dyld_get_all_image_infos =
module.LookUpSymbol<DyldGetAllImageInfosType>(
"_dyld_get_all_image_infos");
if (_dyld_get_all_image_infos) {
return _dyld_get_all_image_infos();
}
}
}
#endif
// On 10.13 and later, do it the hard way.
ProcessReader process_reader;
if (!process_reader.Initialize(mach_task_self())) {
return nullptr;
}
mach_vm_address_t all_image_info_addr_m =
process_reader.DyldAllImageInfo(nullptr);
if (!all_image_info_addr_m) {
return nullptr;
}
uintptr_t all_image_info_addr_u;
if (!AssignIfInRange(&all_image_info_addr_u, all_image_info_addr_m)) {
LOG(ERROR) << "all_image_info_addr_m " << all_image_info_addr_m
<< " out of range";
return nullptr;
}
return reinterpret_cast<const dyld_all_image_infos*>(all_image_info_addr_u);
}
} // namespace test
} // namespace crashpad

View File

@ -17,13 +17,17 @@
#include <mach-o/dyld_images.h>
extern "C" {
namespace crashpad {
namespace test {
// Returns a pointer to this process dyld_all_image_infos structure. This is
// implemented as a non-public dyld API, declared in 10.9.2
// dyld-239.4/include/mach-o/dyld_priv.h.
const struct dyld_all_image_infos* _dyld_get_all_image_infos();
//! \brief Calls or emulates the `_dyld_get_all_image_infos()` private/internal
//! function.
//!
//! \return A pointer to this process dyld_all_image_infos structure, or
//! `nullptr` on failure with a message logged.
const dyld_all_image_infos* DyldGetAllImageInfos();
} // extern "C"
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_TEST_MAC_DYLD_H_

View File

@ -0,0 +1,46 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/scoped_module_handle.h"
#include "base/logging.h"
namespace crashpad {
namespace test {
// static
void ScopedModuleHandle::Impl::Close(ModuleHandle handle) {
#if defined(OS_POSIX)
if (dlclose(handle) != 0) {
LOG(ERROR) << "dlclose: " << dlerror();
}
#elif defined(OS_WIN)
if (!FreeLibrary(handle)) {
PLOG(ERROR) << "FreeLibrary";
}
#else
#error Port
#endif
}
ScopedModuleHandle::ScopedModuleHandle(ModuleHandle handle) : handle_(handle) {}
ScopedModuleHandle::~ScopedModuleHandle() {
if (valid()) {
Impl::Close(handle_);
}
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,81 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_TEST_SCOPED_MODULE_HANDLE_H_
#define CRASHPAD_TEST_SCOPED_MODULE_HANDLE_H_
#include "base/macros.h"
#include "build/build_config.h"
#if defined(OS_POSIX)
#include <dlfcn.h>
#elif defined(OS_WIN)
#include <windows.h>
#endif
namespace crashpad {
namespace test {
//! \brief Maintains ownership of a loadable module handle, releasing it as
//! appropriate on destruction.
class ScopedModuleHandle {
private:
class Impl {
public:
#if defined(OS_POSIX)
using ModuleHandle = void*;
static void* LookUpSymbol(ModuleHandle handle, const char* symbol_name) {
return dlsym(handle, symbol_name);
}
#elif defined(OS_WIN)
using ModuleHandle = HMODULE;
static void* LookUpSymbol(ModuleHandle handle, const char* symbol_name) {
return GetProcAddress(handle, symbol_name);
}
#endif
static void Close(ModuleHandle handle);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(Impl);
};
public:
using ModuleHandle = Impl::ModuleHandle;
explicit ScopedModuleHandle(ModuleHandle handle);
~ScopedModuleHandle();
//! \return `true` if this object manages a valid loadable module handle.
bool valid() const { return handle_ != nullptr; }
//! \return The value of the symbol named by \a symbol_name, or `nullptr` on
//! failure.
template <typename T>
T LookUpSymbol(const char* symbol_name) const {
return reinterpret_cast<T>(Impl::LookUpSymbol(handle_, symbol_name));
}
private:
ModuleHandle handle_;
DISALLOW_COPY_AND_ASSIGN(ScopedModuleHandle);
};
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_TEST_SCOPED_MODULE_HANDLE_H_

View File

@ -22,6 +22,7 @@
'type': 'static_library',
'dependencies': [
'../compat/compat.gyp:crashpad_compat',
'../snapshot/snapshot.gyp:crashpad_snapshot',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
@ -37,6 +38,7 @@
'gtest_death_check.h',
'hex_string.cc',
'hex_string.h',
'mac/dyld.cc',
'mac/dyld.h',
'mac/mach_errors.cc',
'mac/mach_errors.h',
@ -49,6 +51,8 @@
'multiprocess_exec_posix.cc',
'multiprocess_exec_win.cc',
'multiprocess_posix.cc',
'scoped_module_handle.cc',
'scoped_module_handle.h',
'scoped_temp_dir.cc',
'scoped_temp_dir.h',
'scoped_temp_dir_posix.cc',