diff --git a/doc/developing.ad b/doc/developing.ad index efb45536..58ca79de 100644 --- a/doc/developing.ad +++ b/doc/developing.ad @@ -64,7 +64,7 @@ need to install them separately. [subs="verbatim,quotes"] ---- -$ *mkdir ~/crashpad* +$ *mkdir \~/crashpad* $ *cd ~/crashpad* $ *fetch crashpad* ---- diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index 4057df89..66504048 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -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( - 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); } diff --git a/snapshot/win/pe_image_reader_test.cc b/snapshot/win/pe_image_reader_test.cc index 4a3bbd22..3928fee9 100644 --- a/snapshot/win/pe_image_reader_test.cc +++ b/snapshot/win/pe_image_reader_test.cc @@ -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( - 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); } diff --git a/util/util.gyp b/util/util.gyp index 26ed3d29..6c2f1eda 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', diff --git a/util/util_test.gyp b/util/util_test.gyp index 7de1ffcb..6517e0b4 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -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', diff --git a/util/win/critical_section_with_debug_info.cc b/util/win/critical_section_with_debug_info.cc index 91387ae9..dc1a2dba 100644 --- a/util/win/critical_section_with_debug_info.cc +++ b/util/win/critical_section_with_debug_info.cc @@ -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(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) { diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 9cc69e67..304319b7 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -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(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) { diff --git a/util/win/get_function.cc b/util/win/get_function.cc new file mode 100644 index 00000000..d498d30b --- /dev/null +++ b/util/win/get_function.cc @@ -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 + // function’s 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 diff --git a/util/win/get_function.h b/util/win/get_function.h new file mode 100644 index 00000000..50e5f150 --- /dev/null +++ b/util/win/get_function.h @@ -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 + +//! \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 can’t 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 +FunctionType* GetFunction( + const wchar_t* library, const char* function, bool required) { + return reinterpret_cast( + 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 can’t 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( \ + 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 can’t 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( \ + library, #function, true) + +#endif // CRASHPAD_UTIL_WIN_GET_FUNCTION_H_ diff --git a/util/win/get_function_test.cc b/util/win/get_function_test.cc new file mode 100644 index 00000000..a2217aef --- /dev/null +++ b/util/win/get_function_test.cc @@ -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 +#include + +#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 that’s declared in the SDK’s 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 doesn’t 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 diff --git a/util/win/nt_internals.cc b/util/win/nt_internals.cc index 3fb5ef20..46192b35 100644 --- a/util/win/nt_internals.cc +++ b/util/win/nt_internals.cc @@ -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 don’t. + +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(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(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 NTSTATUS NtOpenThread(PHANDLE thread_handle, ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, const process_types::CLIENT_ID* client_id) { - static NtOpenThreadFunction nt_open_thread = - reinterpret_cast( - GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread")); - DCHECK(nt_open_thread); - return nt_open_thread(thread_handle, - desired_access, - object_attributes, - static_cast(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(reinterpret_cast(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( - 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, diff --git a/util/win/process_info.cc b/util/win/process_info.cc index ee5668b4..7f3ff286 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -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(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( - 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; diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index 925d0062..075f8eb4 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -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( - 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;