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