1 // Copyright 2003 Google Inc. All rights reserved.
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 Inc. 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 #include <Windows.h>
30 #include <shellapi.h>
31 
32 #include <string>
33 #include <utility>
34 
35 #include "breakpad_googletest_includes.h"
36 
37 namespace tools {
38 namespace windows {
39 namespace dump_syms {
40 
41 namespace {
42 
43 // Root names of PDB and dumped symbol files to be regression tested. These are
44 // specified in complexity of the resulting dumped symbol files.
45 const wchar_t* kRootNames[] = {
46   // A PDB file with no OMAP data.
47   L"dump_syms_regtest",
48   // A PDB file with OMAP data for an image that has been function-level
49   // reordered.
50   L"omap_reorder_funcs",
51   // A PDB file with OMAP data for an image that had new content injected, all
52   // of it with source data.
53   L"omap_stretched_filled",
54   // A PDB file with OMAP data for an image that had new content injected, but
55   // without source data.
56   L"omap_stretched",
57   // A PDB file with OMAP data for an image that has been basic block reordered.
58   L"omap_reorder_bbs",
59   // A 64bit PDB file with no OMAP data.
60   L"dump_syms_regtest64",
61 };
62 
63 const wchar_t* kPEOnlyRootNames[] = {
64   L"pe_only_symbol_test",
65 };
66 
TrimLastComponent(const std::wstring & path,std::wstring * trimmed,std::wstring * component)67 void TrimLastComponent(const std::wstring& path,
68                        std::wstring* trimmed,
69                        std::wstring* component) {
70   size_t len = path.size();
71   while (len > 0 && path[len - 1] != '\\')
72     --len;
73 
74   if (component != NULL)
75     component->assign(path.c_str() + len, path.c_str() + path.size());
76 
77   while (len > 0 && path[len - 1] == '\\')
78     --len;
79 
80   if (trimmed != NULL)
81     trimmed->assign(path.c_str(), len);
82 }
83 
84 // Get the directory of the current executable.
GetSelfDirectory(std::wstring * self_dir)85 bool GetSelfDirectory(std::wstring* self_dir) {
86   std::wstring command_line = GetCommandLineW();
87 
88   int num_args = 0;
89   wchar_t** args = NULL;
90   args = ::CommandLineToArgvW(command_line.c_str(), &num_args);
91   if (args == NULL)
92     return false;
93 
94   *self_dir = args[0];
95   TrimLastComponent(*self_dir, self_dir, NULL);
96 
97   return true;
98 }
99 
RunCommand(const std::wstring & command_line,std::string * stdout_string)100 void RunCommand(const std::wstring& command_line,
101                 std::string* stdout_string) {
102   // Create a PIPE for the child process stdout.
103   HANDLE child_stdout_read = 0;
104   HANDLE child_stdout_write = 0;
105   SECURITY_ATTRIBUTES sec_attr_stdout = {};
106   sec_attr_stdout.nLength = sizeof(sec_attr_stdout);
107   sec_attr_stdout.bInheritHandle = TRUE;
108   ASSERT_TRUE(::CreatePipe(&child_stdout_read, &child_stdout_write,
109                            &sec_attr_stdout, 0));
110   ASSERT_TRUE(::SetHandleInformation(child_stdout_read, HANDLE_FLAG_INHERIT,
111                                      0));
112 
113   // Create a PIPE for the child process stdin.
114   HANDLE child_stdin_read = 0;
115   HANDLE child_stdin_write = 0;
116   SECURITY_ATTRIBUTES sec_attr_stdin = {};
117   sec_attr_stdin.nLength = sizeof(sec_attr_stdin);
118   sec_attr_stdin.bInheritHandle = TRUE;
119   ASSERT_TRUE(::CreatePipe(&child_stdin_read, &child_stdin_write,
120                            &sec_attr_stdin, 0));
121   ASSERT_TRUE(::SetHandleInformation(child_stdin_write, HANDLE_FLAG_INHERIT,
122                                      0));
123 
124   // Startup the child.
125   STARTUPINFO startup_info = {};
126   PROCESS_INFORMATION process_info = {};
127   startup_info.cb = sizeof(STARTUPINFO);
128   startup_info.hStdError = NULL;
129   startup_info.hStdInput = child_stdin_read;
130   startup_info.hStdOutput = child_stdout_write;
131   startup_info.dwFlags = STARTF_USESTDHANDLES;
132   ASSERT_TRUE(::CreateProcessW(NULL, (LPWSTR)command_line.c_str(), NULL, NULL,
133                                TRUE, 0, NULL, NULL,
134                                &startup_info, &process_info));
135 
136   // Collect the output.
137   ASSERT_TRUE(::CloseHandle(child_stdout_write));
138   char buffer[4096] = {};
139   DWORD bytes_read = 0;
140   while (::ReadFile(child_stdout_read, buffer, sizeof(buffer), &bytes_read,
141                     NULL) && bytes_read > 0) {
142     stdout_string->append(buffer, bytes_read);
143   }
144 
145   // Wait for the process to finish.
146   ::WaitForSingleObject(process_info.hProcess, INFINITE);
147 
148   // Shut down all of our handles.
149   ASSERT_TRUE(::CloseHandle(process_info.hThread));
150   ASSERT_TRUE(::CloseHandle(process_info.hProcess));
151   ASSERT_TRUE(::CloseHandle(child_stdin_write));
152   ASSERT_TRUE(::CloseHandle(child_stdin_read));
153   ASSERT_TRUE(::CloseHandle(child_stdout_read));
154 }
155 
GetFileContents(const std::wstring & path,std::string * content)156 void GetFileContents(const std::wstring& path, std::string* content) {
157   FILE* f = ::_wfopen(path.c_str(), L"rb");
158   ASSERT_TRUE(f != NULL);
159 
160   char buffer[4096] = {};
161   while (true) {
162     size_t bytes_read = ::fread(buffer, 1, sizeof(buffer), f);
163     if (bytes_read == 0)
164       break;
165     content->append(buffer, bytes_read);
166   }
167 }
168 
169 class DumpSymsRegressionTest : public testing::TestWithParam<const wchar_t *> {
170  public:
SetUp()171   virtual void SetUp() {
172     std::wstring self_dir;
173     ASSERT_TRUE(GetSelfDirectory(&self_dir));
174     dump_syms_exe = self_dir + L"\\dump_syms.exe";
175 
176     TrimLastComponent(self_dir, &testdata_dir, NULL);
177     testdata_dir += L"\\testdata";
178   }
179 
180   std::wstring dump_syms_exe;
181   std::wstring testdata_dir;
182 };
183 
184 class DumpSymsPEOnlyRegressionTest : public testing::TestWithParam<const wchar_t *> {
185 public:
SetUp()186   virtual void SetUp() {
187     std::wstring self_dir;
188     ASSERT_TRUE(GetSelfDirectory(&self_dir));
189     dump_syms_exe = self_dir + L"\\dump_syms.exe";
190 
191     TrimLastComponent(self_dir, &testdata_dir, NULL);
192     testdata_dir += L"\\testdata";
193   }
194 
195   std::wstring dump_syms_exe;
196   std::wstring testdata_dir;
197 };
198 
199 }  //namespace
200 
TEST_P(DumpSymsRegressionTest,EnsureDumpedSymbolsMatch)201 TEST_P(DumpSymsRegressionTest, EnsureDumpedSymbolsMatch) {
202   const wchar_t* root_name = GetParam();
203   std::wstring root_path = testdata_dir + L"\\" + root_name;
204 
205   std::wstring sym_path = root_path + L".sym";
206   std::string expected_symbols;
207   ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols));
208 
209   std::wstring pdb_path = root_path + L".pdb";
210   std::wstring command_line = L"\"" + dump_syms_exe + L"\" \"" +
211     pdb_path + L"\"";
212   std::string symbols;
213   ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols));
214 
215   EXPECT_EQ(expected_symbols, symbols);
216 }
217 
218 INSTANTIATE_TEST_CASE_P(DumpSyms, DumpSymsRegressionTest,
219   testing::ValuesIn(kRootNames));
220 
TEST_P(DumpSymsPEOnlyRegressionTest,EnsurePEOnlyDumpedSymbolsMatch)221 TEST_P(DumpSymsPEOnlyRegressionTest, EnsurePEOnlyDumpedSymbolsMatch) {
222   const wchar_t* root_name = GetParam();
223   std::wstring root_path = testdata_dir + L"\\" + root_name;
224 
225   std::wstring sym_path = root_path + L".sym";
226   std::string expected_symbols;
227   ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols));
228 
229   std::wstring dll_path = root_path + L".dll";
230   std::wstring command_line = L"\"" + dump_syms_exe + L"\" --pe \"" +
231     dll_path + L"\"";
232   std::string symbols;
233   ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols));
234 
235   EXPECT_EQ(expected_symbols, symbols);
236 }
237 
238 INSTANTIATE_TEST_CASE_P(PEOnlyDumpSyms, DumpSymsPEOnlyRegressionTest,
239   testing::ValuesIn(kPEOnlyRootNames));
240 
241 
242 }  // namespace dump_syms
243 }  // namespace windows
244 }  // namespace tools
245