/ src / tools / windows / dump_syms / dump_syms_unittest.cc
dump_syms_unittest.cc
  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  
 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.
 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  
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  
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:
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:
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  
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  
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