blob: f4c9e5a9395091540f0b6367574710f41712244b [file] [log] [blame]
// Copyright 2012 Google Inc. 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 <windows.h> // NOLINT
#include <psapi.h>
#include "base/at_exit.h"
#include "base/atomicops.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/files/file_path.h"
#include "base/win/pe_image.h"
#include "syzygy/agent/asan/asan_rtl_impl.h"
#include "syzygy/agent/asan/asan_runtime.h"
#include "syzygy/agent/common/agent.h"
#include "syzygy/common/com_utils.h"
#include "syzygy/common/logging.h"
// The linker satisfies this symbol. This gets us a pointer to our own module
// when we're loaded.
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace {
using agent::asan::AsanRuntime;
// Our AtExit manager required by base.
base::AtExitManager* at_exit = NULL;
// The asan runtime manager.
AsanRuntime* asan_runtime = NULL;
void SetUpAtExitManager() {
DCHECK(at_exit == NULL);
at_exit = new base::AtExitManager();
CHECK(at_exit != NULL);
}
void TearDownAtExitManager() {
DCHECK(at_exit != NULL);
delete at_exit;
at_exit = NULL;
}
// Returns the name of this module.
bool GetSelfPath(base::FilePath* self_path) {
DCHECK_NE(reinterpret_cast<base::FilePath*>(NULL), self_path);
HMODULE self = reinterpret_cast<HMODULE>(&__ImageBase);
std::vector<wchar_t> name(1024, 0);
while (true) {
size_t n = ::GetModuleFileNameW(self, name.data(), name.size());
if (n == 0) {
DWORD error = ::GetLastError();
LOG(ERROR) << "GetModuleFileNameW failed: "
<< common::LogWe(error) << ".";
return false;
}
// If we read the whole thing we're done.
if (n < name.size())
break;
// Otherwise resize the buffer and try again.
name.resize(2 * name.size(), 0);
}
*self_path = base::FilePath(name.data());
return true;
}
// Used as user data by EnumImportChunksCallback. This is used to look for a
// module matching |basename|. Success or failure is returned via |match|.
struct EnumImportChunksCookie {
const std::string* basename;
bool match;
};
// Examines the imported |module|. If it matches the |basename| specified in
// |cookie|, then aborts the search and indicates success via the |match|
// parameter in |cookie|.
bool EnumImportChunksCallback(const base::win::PEImage& image,
LPCSTR module,
PIMAGE_THUNK_DATA name_table,
PIMAGE_THUNK_DATA iat,
PVOID cookie) {
DCHECK_NE(reinterpret_cast<LPCSTR>(NULL), module);
DCHECK_NE(reinterpret_cast<PVOID>(NULL), cookie);
EnumImportChunksCookie* eicc =
reinterpret_cast<EnumImportChunksCookie*>(cookie);
if (::_stricmp(eicc->basename->c_str(), module) == 0) {
// Indicate that the module was found.
eicc->match = true;
// Stop the enumeration as we're done.
return false;
}
// Continue the iteration.
return true;
}
// Inspects the given module for embedded ASAN parameters. If they are found
// sets a pointer to them in |asan_params|. Returns true on success, false
// otherwise.
bool InspectModuleForEmbeddedAsanParameters(
const std::string& self_basename,
HMODULE module,
const common::AsanParameters** asan_params) {
DCHECK_NE(reinterpret_cast<HMODULE>(NULL), module);
DCHECK_NE(reinterpret_cast<common::AsanParameters**>(NULL), asan_params);
*asan_params = NULL;
base::win::PEImage pe_image(module);
EnumImportChunksCookie eicc = { &self_basename, false };
pe_image.EnumImportChunks(&EnumImportChunksCallback, &eicc);
// If there was no matching import then we can skip this module.
if (!eicc.match)
return true;
// Look for the magic section containing the runtime parameters. If found
// then set the pointer to the parameters.
PIMAGE_SECTION_HEADER section = pe_image.GetImageSectionHeaderByName(
common::kAsanParametersSectionName);
if (section != NULL) {
const uint8* image_base = reinterpret_cast<const uint8*>(module);
*asan_params = reinterpret_cast<const common::AsanParameters*>(
image_base + section->VirtualAddress);
}
return true;
}
// |asan_params| will be populated with a pointer to any found ASAN parameters,
// and will be set to NULL if none are found.
bool LookForEmbeddedAsanParameters(const common::AsanParameters** asan_params) {
DCHECK_NE(reinterpret_cast<common::AsanParameters**>(NULL), asan_params);
*asan_params = NULL;
// Get the path of this module.
base::FilePath self_path;
if (!GetSelfPath(&self_path))
return false;
// Get the base name of this module. We'll be looking for modules that import
// it.
std::string self_basename = self_path.BaseName().AsUTF8Unsafe();
// Determine how much space we need for the module list.
HANDLE process = ::GetCurrentProcess();
DWORD bytes_needed = 0;
if (!::EnumProcessModules(process, NULL, 0, &bytes_needed)) {
DWORD error = ::GetLastError();
LOG(ERROR) << "EnumProcessModules failed: "
<< ::common::LogWe(error) << ".";
return false;
}
// Get the list of module handles.
std::vector<HMODULE> modules(bytes_needed / sizeof(HMODULE));
if (!::EnumProcessModules(process, modules.data(), bytes_needed,
&bytes_needed)) {
DWORD error = ::GetLastError();
LOG(ERROR) << "EnumProcessModules failed: "
<< ::common::LogWe(error) << ".";
return false;
}
// Inspect each module to see if it contains ASAN runtime parameters. The
// first ones found will be used.
for (size_t i = 0; i < modules.size(); ++i) {
if (!InspectModuleForEmbeddedAsanParameters(
self_basename, modules[i], asan_params)) {
return false;
}
// If this module contained parameters then we've finished our search.
if (*asan_params != NULL)
return true;
}
return true;
}
void SetUpAsanRuntime() {
DCHECK(asan_runtime == NULL);
asan_runtime = new AsanRuntime();
CHECK(asan_runtime != NULL);
// Look for any parameters that have been embedded in instrumented modules.
const common::AsanParameters* asan_params = NULL;
if (!LookForEmbeddedAsanParameters(&asan_params )) {
LOG(ERROR) << "Error while trying to find embedded Asan parameters.";
}
// Inflate these and inject them into the runtime library. These will serve
// as the baseline parameters that will then be potentially modified by any
// parameters via the environment.
if (asan_params != NULL &&
!common::InflateAsanParameters(asan_params, &asan_runtime->params())) {
LOG(ERROR) << "Failed to inflate embedded Asan parameters.";
}
// Get the flags string from the environment.
std::wstring asan_flags_str;
if (!AsanRuntime::GetAsanFlagsEnvVar(&asan_flags_str)) {
LOG(ERROR) << "Error while trying to read Asan command line.";
}
// Setup the runtime library with the given options.
asan_runtime->SetUp(asan_flags_str);
agent::asan::SetUpRtl(asan_runtime);
}
void TearDownAsanRuntime() {
DCHECK(asan_runtime != NULL);
asan_runtime->TearDown();
delete asan_runtime;
asan_runtime = NULL;
}
} // namespace
extern "C" {
BOOL WINAPI DllMain(HMODULE instance, DWORD reason, LPVOID reserved) {
agent::common::InitializeCrt();
switch (reason) {
case DLL_PROCESS_ATTACH:
// Create the At-Exit manager.
SetUpAtExitManager();
// Disable logging. In the case of Chrome this is running in a sandboxed
// process where logging to file doesn't help us any. In other cases the
// log output will still go to console.
CommandLine::Init(0, NULL);
common::InitLoggingForDll(L"asan");
SetUpAsanRuntime();
break;
case DLL_THREAD_ATTACH:
// Nothing to do here.
break;
case DLL_THREAD_DETACH:
// Nothing to do here.
break;
case DLL_PROCESS_DETACH:
CommandLine::Reset();
// This should be the last thing called in the agent DLL before it
// gets unloaded. Everything should otherwise have been initialized
// and we're now just cleaning it up again.
agent::asan::TearDownRtl();
TearDownAsanRuntime();
break;
default:
NOTREACHED();
break;
}
return TRUE;
}
} // extern "C"