1 // Copyright 2003 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h> // Must come first
31 #endif
32
33 #include <Windows.h>
34 #include <shellapi.h>
35
36 #include <string>
37 #include <utility>
38
39 #include "breakpad_googletest_includes.h"
40
41 namespace tools {
42 namespace windows {
43 namespace dump_syms {
44
45 namespace {
46
47 // Root names of PDB and dumped symbol files to be regression tested. These are
48 // specified in complexity of the resulting dumped symbol files.
49 const wchar_t* kRootNames[] = {
50 // A PDB file with no OMAP data.
51 L"dump_syms_regtest",
52 // A PDB file with OMAP data for an image that has been function-level
53 // reordered.
54 L"omap_reorder_funcs",
55 // A PDB file with OMAP data for an image that had new content injected, all
56 // of it with source data.
57 L"omap_stretched_filled",
58 // A PDB file with OMAP data for an image that had new content injected, but
59 // without source data.
60 L"omap_stretched",
61 // A PDB file with OMAP data for an image that has been basic block reordered.
62 L"omap_reorder_bbs",
63 // A 64bit PDB file with no OMAP data.
64 L"dump_syms_regtest64",
65 };
66
67 const wchar_t* kPEOnlyRootNames[] = {
68 L"pe_only_symbol_test",
69 };
70
TrimLastComponent(const std::wstring & path,std::wstring * trimmed,std::wstring * component)71 void TrimLastComponent(const std::wstring& path,
72 std::wstring* trimmed,
73 std::wstring* component) {
74 size_t len = path.size();
75 while (len > 0 && path[len - 1] != '\\')
76 --len;
77
78 if (component != NULL)
79 component->assign(path.c_str() + len, path.c_str() + path.size());
80
81 while (len > 0 && path[len - 1] == '\\')
82 --len;
83
84 if (trimmed != NULL)
85 trimmed->assign(path.c_str(), len);
86 }
87
88 // Get the directory of the current executable.
GetSelfDirectory(std::wstring * self_dir)89 bool GetSelfDirectory(std::wstring* self_dir) {
90 std::wstring command_line = GetCommandLineW();
91
92 int num_args = 0;
93 wchar_t** args = NULL;
94 args = ::CommandLineToArgvW(command_line.c_str(), &num_args);
95 if (args == NULL)
96 return false;
97
98 *self_dir = args[0];
99 TrimLastComponent(*self_dir, self_dir, NULL);
100
101 return true;
102 }
103
RunCommand(const std::wstring & command_line,std::string * stdout_string)104 void RunCommand(const std::wstring& command_line,
105 std::string* stdout_string) {
106 // Create a PIPE for the child process stdout.
107 HANDLE child_stdout_read = 0;
108 HANDLE child_stdout_write = 0;
109 SECURITY_ATTRIBUTES sec_attr_stdout = {};
110 sec_attr_stdout.nLength = sizeof(sec_attr_stdout);
111 sec_attr_stdout.bInheritHandle = TRUE;
112 ASSERT_TRUE(::CreatePipe(&child_stdout_read, &child_stdout_write,
113 &sec_attr_stdout, 0));
114 ASSERT_TRUE(::SetHandleInformation(child_stdout_read, HANDLE_FLAG_INHERIT,
115 0));
116
117 // Create a PIPE for the child process stdin.
118 HANDLE child_stdin_read = 0;
119 HANDLE child_stdin_write = 0;
120 SECURITY_ATTRIBUTES sec_attr_stdin = {};
121 sec_attr_stdin.nLength = sizeof(sec_attr_stdin);
122 sec_attr_stdin.bInheritHandle = TRUE;
123 ASSERT_TRUE(::CreatePipe(&child_stdin_read, &child_stdin_write,
124 &sec_attr_stdin, 0));
125 ASSERT_TRUE(::SetHandleInformation(child_stdin_write, HANDLE_FLAG_INHERIT,
126 0));
127
128 // Startup the child.
129 STARTUPINFO startup_info = {};
130 PROCESS_INFORMATION process_info = {};
131 startup_info.cb = sizeof(STARTUPINFO);
132 startup_info.hStdError = NULL;
133 startup_info.hStdInput = child_stdin_read;
134 startup_info.hStdOutput = child_stdout_write;
135 startup_info.dwFlags = STARTF_USESTDHANDLES;
136 ASSERT_TRUE(::CreateProcessW(NULL, (LPWSTR)command_line.c_str(), NULL, NULL,
137 TRUE, 0, NULL, NULL,
138 &startup_info, &process_info));
139
140 // Collect the output.
141 ASSERT_TRUE(::CloseHandle(child_stdout_write));
142 char buffer[4096] = {};
143 DWORD bytes_read = 0;
144 while (::ReadFile(child_stdout_read, buffer, sizeof(buffer), &bytes_read,
145 NULL) && bytes_read > 0) {
146 stdout_string->append(buffer, bytes_read);
147 }
148
149 // Wait for the process to finish.
150 ::WaitForSingleObject(process_info.hProcess, INFINITE);
151
152 // Shut down all of our handles.
153 ASSERT_TRUE(::CloseHandle(process_info.hThread));
154 ASSERT_TRUE(::CloseHandle(process_info.hProcess));
155 ASSERT_TRUE(::CloseHandle(child_stdin_write));
156 ASSERT_TRUE(::CloseHandle(child_stdin_read));
157 ASSERT_TRUE(::CloseHandle(child_stdout_read));
158 }
159
GetFileContents(const std::wstring & path,std::string * content)160 void GetFileContents(const std::wstring& path, std::string* content) {
161 FILE* f = ::_wfopen(path.c_str(), L"rb");
162 ASSERT_TRUE(f != NULL);
163
164 char buffer[4096] = {};
165 while (true) {
166 size_t bytes_read = ::fread(buffer, 1, sizeof(buffer), f);
167 if (bytes_read == 0)
168 break;
169 content->append(buffer, bytes_read);
170 }
171 }
172
173 class DumpSymsRegressionTest : public testing::TestWithParam<const wchar_t*> {
174 public:
SetUp()175 virtual void SetUp() {
176 std::wstring self_dir;
177 ASSERT_TRUE(GetSelfDirectory(&self_dir));
178 dump_syms_exe = self_dir + L"\\dump_syms.exe";
179
180 TrimLastComponent(self_dir, &testdata_dir, NULL);
181 testdata_dir += L"\\testdata";
182 }
183
184 std::wstring dump_syms_exe;
185 std::wstring testdata_dir;
186 };
187
188 class DumpSymsPEOnlyRegressionTest : public testing::TestWithParam<const wchar_t*> {
189 public:
SetUp()190 virtual void SetUp() {
191 std::wstring self_dir;
192 ASSERT_TRUE(GetSelfDirectory(&self_dir));
193 dump_syms_exe = self_dir + L"\\dump_syms.exe";
194
195 TrimLastComponent(self_dir, &testdata_dir, NULL);
196 testdata_dir += L"\\testdata";
197 }
198
199 std::wstring dump_syms_exe;
200 std::wstring testdata_dir;
201 };
202
203 } //namespace
204
TEST_P(DumpSymsRegressionTest,EnsureDumpedSymbolsMatch)205 TEST_P(DumpSymsRegressionTest, EnsureDumpedSymbolsMatch) {
206 const wchar_t* root_name = GetParam();
207 std::wstring root_path = testdata_dir + L"\\" + root_name;
208
209 std::wstring sym_path = root_path + L".sym";
210 std::string expected_symbols;
211 ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols));
212
213 std::wstring pdb_path = root_path + L".pdb";
214 std::wstring command_line = L"\"" + dump_syms_exe + L"\" \"" +
215 pdb_path + L"\"";
216 std::string symbols;
217 ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols));
218
219 EXPECT_EQ(expected_symbols, symbols);
220 }
221
222 INSTANTIATE_TEST_SUITE_P(DumpSyms, DumpSymsRegressionTest,
223 testing::ValuesIn(kRootNames));
224
TEST_P(DumpSymsPEOnlyRegressionTest,EnsurePEOnlyDumpedSymbolsMatch)225 TEST_P(DumpSymsPEOnlyRegressionTest, EnsurePEOnlyDumpedSymbolsMatch) {
226 const wchar_t* root_name = GetParam();
227 std::wstring root_path = testdata_dir + L"\\" + root_name;
228
229 std::wstring sym_path = root_path + L".sym";
230 std::string expected_symbols;
231 ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols));
232
233 std::wstring dll_path = root_path + L".dll";
234 std::wstring command_line = L"\"" + dump_syms_exe + L"\" --pe \"" +
235 dll_path + L"\"";
236 std::string symbols;
237 ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols));
238
239 EXPECT_EQ(expected_symbols, symbols);
240 }
241
242 INSTANTIATE_TEST_SUITE_P(PEOnlyDumpSyms, DumpSymsPEOnlyRegressionTest,
243 testing::ValuesIn(kPEOnlyRootNames));
244
245
246 } // namespace dump_syms
247 } // namespace windows
248 } // namespace tools
249