xref: /aosp_15_r20/external/google-breakpad/src/tools/windows/dump_syms/dump_syms_unittest.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
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