1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 : //
15 : // A harness for loading integration_tests_dll, and calling a test function
16 : // within it. This is intended for use with instrumented versions of the
17 : // DLL, and is required for certain tests that raise exceptions. The test has
18 : // to be moved to a separate process so as to avoid gtest interference in
19 : // exception handling.
20 :
21 : #include "base/bind.h"
22 : #include "base/command_line.h"
23 : #include "base/logging.h"
24 : #include "base/files/file_path.h"
25 : #include "base/files/file_util.h"
26 : #include "base/strings/string_number_conversions.h"
27 : #include "syzygy/agent/asan/block.h" // Solely for some typedefs.
28 : #include "syzygy/common/com_utils.h"
29 : #include "syzygy/integration_tests/integration_tests_dll.h"
30 :
31 : namespace {
32 :
33 : typedef unsigned int (__stdcall* EndToEndTestFunction)(unsigned int);
34 :
35 : #define _STRINGIFY(s) #s
36 : #define STRINGIFY(s) _STRINGIFY(s)
37 :
38 : // An array of test names. The test integer ID is the position of the name in
39 : // the array.
40 : const char* kTestNames[] = {
41 : #define DEFINE_TEST_NAME(enum_name, function_name) STRINGIFY(enum_name),
42 : END_TO_END_TEST_ID_TABLE(DEFINE_TEST_NAME)
43 : #undef DEFINE_TEST_NAME
44 : };
45 :
46 : // Top level configuration and parameters.
47 : LPTOP_LEVEL_EXCEPTION_FILTER previous_unhandled_exception_filter = NULL;
48 E : base::FilePath dll;
49 : size_t test_id = 0;
50 : bool expect_exception = false;
51 :
52 E : bool ParseTestId(base::CommandLine* cmd_line) {
53 E : DCHECK_NE(reinterpret_cast<base::CommandLine*>(NULL), cmd_line);
54 :
55 E : std::string test = cmd_line->GetSwitchValueASCII("test");
56 E : if (test.empty()) {
57 i : LOG(ERROR) << "Must specify --test.";
58 i : return false;
59 : }
60 :
61 : // Search for the test by name
62 E : for (size_t i = 0; i < arraysize(kTestNames); ++i) {
63 E : if (test == kTestNames[i]) {
64 i : test_id = i;
65 i : return true;
66 : }
67 E : }
68 :
69 : // Try to convert the string to an integer.
70 E : if (!base::StringToSizeT(test, &test_id)) {
71 i : LOG(ERROR) << "Invalid test name or id: " << test;
72 i : return false;
73 : }
74 :
75 : // If integer parsing worked then ensure it's a valid test id.
76 E : if (test_id >= arraysize(kTestNames)) {
77 i : LOG(ERROR) << "Invalid test id: " << test_id;
78 i : return false;
79 : }
80 :
81 E : return true;
82 E : }
83 :
84 E : bool ParseCommandLine(base::CommandLine* cmd_line) {
85 E : DCHECK_NE(reinterpret_cast<base::CommandLine*>(NULL), cmd_line);
86 :
87 : // Parse and validate the path to the DLL.
88 E : dll = cmd_line->GetSwitchValuePath("dll");
89 E : if (dll.empty()) {
90 i : LOG(ERROR) << "Must specify --dll.";
91 i : return false;
92 : }
93 E : if (!base::PathExists(dll)) {
94 i : LOG(ERROR) << "File does not exist: " << dll.value();
95 i : return false;
96 : }
97 :
98 : // Parse the test ID.
99 E : if (!ParseTestId(cmd_line))
100 i : return false;
101 :
102 E : expect_exception = cmd_line->HasSwitch("expect-exception");
103 :
104 E : return true;
105 E : }
106 :
107 : // A utility function for terminating the process with a given return code.
108 E : void Exit(UINT code) {
109 E : if (code != 0) {
110 i : LOG(ERROR) << "Exiting with an error.";
111 i : } else {
112 E : VLOG(1) << "Terminating successfully.";
113 : }
114 E : ::TerminateProcess(::GetCurrentProcess(), code);
115 E : }
116 :
117 : // The base unhandled exception filter. If an exception is raised then this is
118 : // our exit path.
119 E : LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exception) {
120 E : VLOG(1) << "Entering UnhandledExceptionFilter.";
121 :
122 E : if (!expect_exception) {
123 i : LOG(ERROR) << "An exception was raised, but none was expected.";
124 i : Exit(1);
125 : }
126 E : Exit(0);
127 :
128 E : LOG(ERROR) << "Something went terribly wrong.";
129 i : return EXCEPTION_EXECUTE_HANDLER;
130 i : }
131 :
132 i : void AsanOnExceptionCallback(EXCEPTION_POINTERS* exception) {
133 i : LOG(ERROR) << "AsanOnExceptionCallback fired.";
134 i : Exit(1);
135 i : }
136 :
137 E : void SetAsanOnExceptionCallback() {
138 : typedef void (*OnExceptionCallback)(EXCEPTION_POINTERS*);
139 : typedef void (WINAPI *SetOnExceptionCallback)(OnExceptionCallback);
140 :
141 E : HMODULE asan_module = GetModuleHandle(L"syzyasan_rtl.dll");
142 E : if (asan_module == nullptr)
143 E : return;
144 :
145 : SetOnExceptionCallback set_callback =
146 : reinterpret_cast<SetOnExceptionCallback>(
147 i : ::GetProcAddress(asan_module, "asan_SetOnExceptionCallback"));
148 i : DCHECK(set_callback != NULL);
149 i : set_callback(&AsanOnExceptionCallback);
150 E : }
151 :
152 : } // namespace
153 :
154 E : int main(int argc, char** argv) {
155 : // Initialize the command-line.
156 E : base::CommandLine::Init(argc, argv);
157 E : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
158 :
159 : // Initialize logging.
160 E : logging::LoggingSettings settings;
161 E : settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
162 E : settings.lock_log = logging::DONT_LOCK_LOG_FILE;
163 E : settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
164 E : logging::InitLogging(settings);
165 E : logging::SetMinLogLevel(logging::LOG_ERROR);
166 E : if (cmd_line->HasSwitch("verbose"))
167 i : logging::SetMinLogLevel(logging::LOG_VERBOSE);
168 :
169 : // Parse the command-line.
170 E : if (!ParseCommandLine(cmd_line))
171 i : return 1;
172 :
173 : // Prevent dialog boxes from popping up.
174 E : ::SetErrorMode(SEM_FAILCRITICALERRORS);
175 :
176 E : VLOG(1) << "Registering unhandled exception filter and callback.";
177 : previous_unhandled_exception_filter = ::SetUnhandledExceptionFilter(
178 E : &MyUnhandledExceptionFilter);
179 :
180 : // If syzyasan_rtl.dll is in memory then register an OnException handler.
181 : // This gracefully does nothing if SyzyASan is not in memory.
182 E : SetAsanOnExceptionCallback();
183 :
184 : // Load the module.
185 E : LOG(INFO) << "Loading module: " << dll.value();
186 E : HMODULE module = ::LoadLibrary(dll.value().c_str());
187 E : if (module == NULL) {
188 i : DWORD error = ::GetLastError();
189 i : LOG(ERROR) << "LoadLibrary failed: " << common::LogWe(error);
190 i : return 1;
191 : }
192 :
193 : // Get the EndToEndTest function. It is the entry point for calling
194 : // the various tests.
195 E : LOG(INFO) << "Looking up EndToEndTest function.";
196 : EndToEndTestFunction func = reinterpret_cast<EndToEndTestFunction>(
197 E : ::GetProcAddress(module, "EndToEndTest"));
198 E : if (func == NULL) {
199 i : LOG(ERROR) << "Failed to find EndToEndTest function.";
200 i : return 1;
201 : }
202 :
203 : // Invoke the test function.
204 E : LOG(INFO) << "Invoking test " << test_id << ".";
205 E : size_t ret = func(test_id);
206 :
207 E : if (expect_exception) {
208 i : LOG(ERROR) << "Expected an exception, but none was raised.";
209 i : LOG(ERROR) << "Command-line: " << cmd_line->GetCommandLineString();
210 i : Exit(1);
211 : }
212 E : Exit(0);
213 :
214 E : LOG(ERROR) << "Something went terribly wrong.";
215 i : return 1;
216 i : }
|