win: Add and use GET_FUNCTION() and GET_FUNCTION_REQUIRED()

These wrap the GetProcAddress(LoadLibrary(), …) idiom into macros that
are much less wordy.

TEST=crashpad_util_test GetFunction.GetFunction and all others
R=scottmg@chromium.org

Review URL: https://codereview.chromium.org/1405323003 .
This commit is contained in:
Mark Mentovai 2015-10-19 14:32:07 -04:00
parent 2adcd13fd6
commit d075a9eb2e
12 changed files with 290 additions and 57 deletions

View File

@ -25,6 +25,7 @@
#include "client/crashpad_client.h"
#include "tools/tool_support.h"
#include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.h"
namespace crashpad {
namespace {
@ -32,10 +33,8 @@ namespace {
CRITICAL_SECTION g_test_critical_section;
ULONG RtlNtStatusToDosError(NTSTATUS status) {
static decltype(::RtlNtStatusToDosError)* rtl_nt_status_to_dos_error =
reinterpret_cast<decltype(::RtlNtStatusToDosError)*>(
GetProcAddress(LoadLibrary(L"ntdll.dll"), "RtlNtStatusToDosError"));
DCHECK(rtl_nt_status_to_dos_error);
static const auto rtl_nt_status_to_dos_error =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::RtlNtStatusToDosError);
return rtl_nt_status_to_dos_error(status);
}

View File

@ -19,6 +19,7 @@
#include "gtest/gtest.h"
#include "snapshot/win/process_reader_win.h"
#include "util/win/get_function.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
@ -30,10 +31,8 @@ BOOL CrashpadGetModuleInformation(HANDLE process,
HMODULE module,
MODULEINFO* module_info,
DWORD cb) {
static decltype(GetModuleInformation)* get_module_information =
reinterpret_cast<decltype(GetModuleInformation)*>(
GetProcAddress(LoadLibrary(L"psapi.dll"), "GetModuleInformation"));
DCHECK(get_module_information);
static const auto get_module_information =
GET_FUNCTION_REQUIRED(L"psapi.dll", ::GetModuleInformation);
return get_module_information(process, module, module_info, cb);
}

View File

@ -156,6 +156,8 @@
'win/critical_section_with_debug_info.h',
'win/exception_handler_server.cc',
'win/exception_handler_server.h',
'win/get_function.cc',
'win/get_function.h',
'win/module_version.cc',
'win/module_version.h',
'win/nt_internals.cc',

View File

@ -82,6 +82,7 @@
'win/capture_context_test.cc',
'win/critical_section_with_debug_info_test.cc',
'win/exception_handler_server_test.cc',
'win/get_function_test.cc',
'win/process_info_test.cc',
'win/scoped_process_suspend_test.cc',
'win/time_test.cc',

View File

@ -15,6 +15,7 @@
#include "util/win/critical_section_with_debug_info.h"
#include "base/logging.h"
#include "util/win/get_function.h"
namespace crashpad {
@ -24,13 +25,8 @@ BOOL CrashpadInitializeCriticalSectionEx(
CRITICAL_SECTION* critical_section,
DWORD spin_count,
DWORD flags) {
static decltype(InitializeCriticalSectionEx)* initialize_critical_section_ex =
reinterpret_cast<decltype(InitializeCriticalSectionEx)*>(GetProcAddress(
LoadLibrary(L"kernel32.dll"), "InitializeCriticalSectionEx"));
if (!initialize_critical_section_ex) {
PLOG(ERROR) << "GetProcAddress";
return false;
}
static const auto initialize_critical_section_ex =
GET_FUNCTION_REQUIRED(L"kernel32.dll", ::InitializeCriticalSectionEx);
bool ret =
initialize_critical_section_ex(critical_section, spin_count, flags);
if (!ret) {

View File

@ -27,6 +27,7 @@
#include "util/file/file_writer.h"
#include "util/misc/tri_state.h"
#include "util/misc/uuid.h"
#include "util/win/get_function.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/xp_compat.h"
@ -35,10 +36,9 @@ namespace crashpad {
namespace {
decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
static decltype(GetNamedPipeClientProcessId)* func =
reinterpret_cast<decltype(GetNamedPipeClientProcessId)*>(GetProcAddress(
GetModuleHandle(L"kernel32.dll"), "GetNamedPipeClientProcessId"));
return func;
static const auto get_named_pipe_client_process_id =
GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
return get_named_pipe_client_process_id;
}
HANDLE DuplicateEvent(HANDLE process, HANDLE event) {

44
util/win/get_function.cc Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2015 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 "util/win/get_function.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
namespace crashpad {
namespace internal {
FARPROC GetFunctionInternal(
const wchar_t* library, const char* function, bool required) {
HMODULE module = LoadLibrary(library);
DPCHECK(!required || module) << "LoadLibrary " << base::UTF16ToUTF8(library);
if (!module) {
return nullptr;
}
// Strip off any leading :: that may have come from stringifying the
// functions name.
if (function[0] == ':' && function[1] == ':' &&
function[2] && function[2] != ':') {
function += 2;
}
FARPROC proc = GetProcAddress(module, function);
DPCHECK(!required || proc) << "GetProcAddress " << function;
return proc;
}
} // namespace internal
} // namespace crashpad

121
util/win/get_function.h Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2015 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_UTIL_WIN_GET_FUNCTION_H_
#define CRASHPAD_UTIL_WIN_GET_FUNCTION_H_
#include <windows.h>
//! \file
namespace crashpad {
namespace internal {
//! \brief Returns a function pointer to a named function in a library.
//!
//! Do not call this directly, use the GET_FUNCTION() or GET_FUNCTION_REQUIRED()
//! macros instead.
//!
//! This accesses \a library by calling `LoadLibrary()` and is subject to the
//! same restrictions as that function. Notably, it cant be used from a
//! `DllMain()` entry point.
//!
//! \param[in] library The library to search in.
//! \param[in] function The function to search for. If a leading `::` is
//! present, it will be stripped.
//! \param[in] required If `true`, require the function to resolve by `DCHECK`.
//!
//! \return A pointer to the requested function on success. If \a required is
//! `true`, triggers a `DCHECK` assertion on failure, otherwise, `nullptr`
//! on failure.
FARPROC GetFunctionInternal(
const wchar_t* library, const char* function, bool required);
//! \copydoc GetFunctionInternal
template <typename FunctionType>
FunctionType* GetFunction(
const wchar_t* library, const char* function, bool required) {
return reinterpret_cast<FunctionType*>(
internal::GetFunctionInternal(library, function, required));
}
} // namespace internal
} // namespace crashpad
//! \brief Returns a function pointer to a named function in a library without
//! requiring that it be found.
//!
//! If the library or function cannot be found, this will return `nullptr`. This
//! macro is intended to be used to access functions that may not be available
//! at runtime.
//!
//! This macro returns a properly-typed function pointer. It is expected to be
//! used in this way:
//! \code
//! static const auto get_named_pipe_client_process_id =
//! GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
//! if (get_named_pipe_client_process_id) {
//! BOOL rv = get_named_pipe_client_process_id(pipe, &client_process_id);
//! }
//! \endcode
//!
//! This accesses \a library by calling `LoadLibrary()` and is subject to the
//! same restrictions as that function. Notably, it cant be used from a
//! `DllMain()` entry point.
//!
//! \param[in] library The library to search in.
//! \param[in] function The function to search for. A leading `::` is
//! recommended when a wrapper function of the same name is present.
//!
//! \return A pointer to the requested function on success, or `nullptr` on
//! failure.
//!
//! \sa GET_FUNCTION_REQUIRED
#define GET_FUNCTION(library, function) \
crashpad::internal::GetFunction<decltype(function)>( \
library, #function, false)
//! \brief Returns a function pointer to a named function in a library,
//! requiring that it be found.
//!
//! If the library or function cannot be found, this will trigger a `DCHECK`
//! assertion. This macro is intended to be used to access functions that are
//! always expected to be available at runtime but which are not present in any
//! import library.
//!
//! This macro returns a properly-typed function pointer. It is expected to be
//! used in this way:
//! \code
//! static const auto nt_query_object =
//! GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryObject);
//! NTSTATUS status =
//! nt_query_object(handle, type, &info, info_length, &return_length);
//! \endcode
//!
//! This accesses \a library by calling `LoadLibrary()` and is subject to the
//! same restrictions as that function. Notably, it cant be used from a
//! `DllMain()` entry point.
//!
//! \param[in] library The library to search in.
//! \param[in] function The function to search for. A leading `::` is
//! recommended when a wrapper function of the same name is present.
//!
//! \return A pointer to the requested function.
//!
//! \sa GET_FUNCTION
#define GET_FUNCTION_REQUIRED(library, function) \
crashpad::internal::GetFunction<decltype(function)>( \
library, #function, true)
#endif // CRASHPAD_UTIL_WIN_GET_FUNCTION_H_

View File

@ -0,0 +1,78 @@
// Copyright 2015 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 "util/win/get_function.h"
#include <windows.h>
#include <winternl.h>
#include "gtest/gtest.h"
namespace crashpad {
namespace test {
namespace {
TEST(GetFunction, GetFunction) {
// Check equivalence of GET_FUNCTION_REQUIRED() with functions that are
// available in the SDK normally.
EXPECT_EQ(&GetProcAddress,
GET_FUNCTION_REQUIRED(L"kernel32.dll", GetProcAddress));
EXPECT_EQ(&LoadLibraryW,
GET_FUNCTION_REQUIRED(L"kernel32.dll", LoadLibraryW));
// Make sure that a function pointer retrieved by GET_FUNCTION_REQUIRED() can
// be called and that it works correctly.
const auto get_current_process_id =
GET_FUNCTION_REQUIRED(L"kernel32.dll", GetCurrentProcessId);
EXPECT_EQ(&GetCurrentProcessId, get_current_process_id);
ASSERT_TRUE(get_current_process_id);
EXPECT_EQ(GetCurrentProcessId(), get_current_process_id());
// GET_FUNCTION_REQUIRED() and GET_FUNCTION() should behave identically when
// the function is present.
EXPECT_EQ(get_current_process_id,
GET_FUNCTION(L"kernel32.dll", GetCurrentProcessId));
// Using a leading :: should also work.
EXPECT_EQ(get_current_process_id,
GET_FUNCTION(L"kernel32.dll", ::GetCurrentProcessId));
EXPECT_EQ(get_current_process_id,
GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetCurrentProcessId));
// Try a function thats declared in the SDKs headers but that has no import
// library.
EXPECT_TRUE(GET_FUNCTION_REQUIRED(L"ntdll.dll", RtlNtStatusToDosError));
// GetNamedPipeClientProcessId() is only available on Vista and later.
const auto get_named_pipe_client_process_id =
GET_FUNCTION(L"kernel32.dll", GetNamedPipeClientProcessId);
const DWORD version = GetVersion();
const DWORD major_version = LOBYTE(LOWORD(version));
EXPECT_EQ(major_version >= 6, get_named_pipe_client_process_id != nullptr);
// Test that GET_FUNCTION() can fail by trying a nonexistent library and a
// symbol that doesnt exist in the specified library.
EXPECT_FALSE(GET_FUNCTION(L"not_a_real_library.dll", TerminateProcess));
EXPECT_FALSE(GET_FUNCTION(L"ntdll.dll", TerminateProcess));
EXPECT_FALSE(GET_FUNCTION(L"not_a_real_library.dll", ::TerminateProcess));
EXPECT_FALSE(GET_FUNCTION(L"ntdll.dll", ::TerminateProcess));
// Here it is!
EXPECT_TRUE(GET_FUNCTION(L"kernel32.dll", TerminateProcess));
EXPECT_TRUE(GET_FUNCTION(L"kernel32.dll", ::TerminateProcess));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -15,6 +15,16 @@
#include "util/win/nt_internals.h"
#include "base/logging.h"
#include "util/win/get_function.h"
// Declarations that the system headers should provide but dont.
struct CLIENT_ID;
NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle,
ACCESS_MASK DesiredAccess,
OBJECT_ATTRIBUTES* ObjectAttributes,
CLIENT_ID* ClientId);
namespace crashpad {
@ -23,10 +33,8 @@ NTSTATUS NtQuerySystemInformation(
PVOID system_information,
ULONG system_information_length,
PULONG return_length) {
static decltype(::NtQuerySystemInformation)* nt_query_system_information =
reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation"));
DCHECK(nt_query_system_information);
static const auto nt_query_system_information =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQuerySystemInformation);
return nt_query_system_information(system_information_class,
system_information,
system_information_length,
@ -38,10 +46,8 @@ NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
PVOID thread_information,
ULONG thread_information_length,
PULONG return_length) {
static decltype(::NtQueryInformationThread)* nt_query_information_thread =
reinterpret_cast<decltype(::NtQueryInformationThread)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQueryInformationThread"));
DCHECK(nt_query_information_thread);
static const auto nt_query_information_thread =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationThread);
return nt_query_information_thread(thread_handle,
thread_information_class,
thread_information,
@ -49,27 +55,18 @@ NTSTATUS NtQueryInformationThread(HANDLE thread_handle,
return_length);
}
// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast
// to void* here.
typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
const void* ClientId);
template <class Traits>
NTSTATUS NtOpenThread(PHANDLE thread_handle,
ACCESS_MASK desired_access,
POBJECT_ATTRIBUTES object_attributes,
const process_types::CLIENT_ID<Traits>* client_id) {
static NtOpenThreadFunction nt_open_thread =
reinterpret_cast<NtOpenThreadFunction>(
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread"));
DCHECK(nt_open_thread);
return nt_open_thread(thread_handle,
desired_access,
object_attributes,
static_cast<const void*>(client_id));
static const auto nt_open_thread =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtOpenThread);
return nt_open_thread(
thread_handle,
desired_access,
object_attributes,
const_cast<CLIENT_ID*>(reinterpret_cast<const CLIENT_ID*>(client_id)));
}
NTSTATUS NtQueryObject(HANDLE handle,
@ -77,10 +74,8 @@ NTSTATUS NtQueryObject(HANDLE handle,
void* object_information,
ULONG object_information_length,
ULONG* return_length) {
static decltype(::NtQueryObject)* nt_query_object =
reinterpret_cast<decltype(::NtQueryObject)*>(
GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtQueryObject"));
DCHECK(nt_query_object);
static const auto nt_query_object =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryObject);
return nt_query_object(handle,
object_information_class,
object_information,

View File

@ -25,6 +25,7 @@
#include "base/template_util.h"
#include "build/build_config.h"
#include "util/numeric/safe_assignment.h"
#include "util/win/get_function.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_structs.h"
@ -39,10 +40,8 @@ NTSTATUS NtQueryInformationProcess(HANDLE process_handle,
PVOID process_information,
ULONG process_information_length,
PULONG return_length) {
static decltype(::NtQueryInformationProcess)* nt_query_information_process =
reinterpret_cast<decltype(::NtQueryInformationProcess)*>(GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtQueryInformationProcess"));
DCHECK(nt_query_information_process);
static const auto nt_query_information_process =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationProcess);
return nt_query_information_process(process_handle,
process_information_class,
process_information,
@ -51,9 +50,8 @@ NTSTATUS NtQueryInformationProcess(HANDLE process_handle,
}
bool IsProcessWow64(HANDLE process_handle) {
static decltype(IsWow64Process)* is_wow64_process =
reinterpret_cast<decltype(IsWow64Process)*>(
GetProcAddress(LoadLibrary(L"kernel32.dll"), "IsWow64Process"));
static const auto is_wow64_process =
GET_FUNCTION(L"kernel32.dll", ::IsWow64Process);
if (!is_wow64_process)
return false;
BOOL is_wow64;

View File

@ -31,6 +31,7 @@
#include "test/win/child_launcher.h"
#include "util/file/file_io.h"
#include "util/misc/uuid.h"
#include "util/win/get_function.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
@ -40,9 +41,8 @@ namespace {
const wchar_t kNtdllName[] = L"\\ntdll.dll";
bool IsProcessWow64(HANDLE process_handle) {
static decltype(IsWow64Process)* is_wow64_process =
reinterpret_cast<decltype(IsWow64Process)*>(
GetProcAddress(LoadLibrary(L"kernel32.dll"), "IsWow64Process"));
static const auto is_wow64_process =
GET_FUNCTION(L"kernel32.dll", ::IsWow64Process);
if (!is_wow64_process)
return false;
BOOL is_wow64;