1 // Copyright 2006 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 // Tool to upload an exe/dll and its associated symbols to an HTTP server.
30 // The PDB file is located automatically, using the path embedded in the
31 // executable. The upload is sent as a multipart/form-data POST request,
32 // with the following parameters:
33 // code_file: the basename of the module, e.g. "app.exe"
34 // debug_file: the basename of the debugging file, e.g. "app.pdb"
35 // debug_identifier: the debug file's identifier, usually consisting of
36 // the guid and age embedded in the pdb, e.g.
37 // "11111111BBBB3333DDDD555555555555F"
38 // product: the HTTP-friendly product name, e.g. "MyApp"
39 // version: the file version of the module, e.g. "1.2.3.4"
40 // os: the operating system that the module was built for, always
41 // "windows" in this implementation.
42 // cpu: the CPU that the module was built for, typically "x86".
43 // symbol_file: the contents of the breakpad-format symbol file
44
45 #ifdef HAVE_CONFIG_H
46 #include <config.h> // Must come first
47 #endif
48
49 #include <windows.h>
50 #include <dbghelp.h>
51 #include <wininet.h>
52
53 #include <cstdio>
54 #include <map>
55 #include <string>
56 #include <vector>
57
58 #include "common/windows/string_utils-inl.h"
59
60 #include "common/windows/http_upload.h"
61 #include "common/windows/pdb_source_line_writer.h"
62 #include "common/windows/sym_upload_v2_protocol.h"
63 #include "common/windows/symbol_collector_client.h"
64
65 using google_breakpad::HTTPUpload;
66 using google_breakpad::PDBModuleInfo;
67 using google_breakpad::PDBSourceLineWriter;
68 using google_breakpad::WindowsStringUtils;
69 using std::map;
70 using std::string;
71 using std::vector;
72 using std::wstring;
73
74 const wchar_t* kSymbolUploadTypeBreakpad = L"BREAKPAD";
75
76 // Extracts the file version information for the given filename,
77 // as a string, for example, "1.2.3.4". Returns true on success.
GetFileVersionString(const wchar_t * filename,wstring * version)78 static bool GetFileVersionString(const wchar_t* filename, wstring* version) {
79 DWORD handle;
80 DWORD version_size = GetFileVersionInfoSize(filename, &handle);
81 if (version_size < sizeof(VS_FIXEDFILEINFO)) {
82 return false;
83 }
84
85 vector<char> version_info(version_size);
86 if (!GetFileVersionInfo(filename, handle, version_size, &version_info[0])) {
87 return false;
88 }
89
90 void* file_info_buffer = NULL;
91 unsigned int file_info_length;
92 if (!VerQueryValue(&version_info[0], L"\\",
93 &file_info_buffer, &file_info_length)) {
94 return false;
95 }
96
97 // The maximum value of each version component is 65535 (0xffff),
98 // so the max length is 24, including the terminating null.
99 wchar_t ver_string[24];
100 VS_FIXEDFILEINFO* file_info =
101 reinterpret_cast<VS_FIXEDFILEINFO*>(file_info_buffer);
102 swprintf(ver_string, sizeof(ver_string) / sizeof(ver_string[0]),
103 L"%d.%d.%d.%d",
104 file_info->dwFileVersionMS >> 16,
105 file_info->dwFileVersionMS & 0xffff,
106 file_info->dwFileVersionLS >> 16,
107 file_info->dwFileVersionLS & 0xffff);
108
109 // remove when VC++7.1 is no longer supported
110 ver_string[sizeof(ver_string) / sizeof(ver_string[0]) - 1] = L'\0';
111
112 *version = ver_string;
113 return true;
114 }
115
116 // Creates a new temporary file and writes the symbol data from the given
117 // exe/dll file to it. Returns the path to the temp file in temp_file_path
118 // and information about the pdb in pdb_info.
DumpSymbolsToTempFile(const wchar_t * file,wstring * temp_file_path,PDBModuleInfo * pdb_info,bool handle_inline)119 static bool DumpSymbolsToTempFile(const wchar_t* file,
120 wstring* temp_file_path,
121 PDBModuleInfo* pdb_info,
122 bool handle_inline) {
123 google_breakpad::PDBSourceLineWriter writer(handle_inline);
124 // Use EXE_FILE to get information out of the exe/dll in addition to the
125 // pdb. The name and version number of the exe/dll are of value, and
126 // there's no way to locate an exe/dll given a pdb.
127 if (!writer.Open(file, PDBSourceLineWriter::EXE_FILE)) {
128 return false;
129 }
130
131 wchar_t temp_path[_MAX_PATH];
132 if (GetTempPath(_MAX_PATH, temp_path) == 0) {
133 return false;
134 }
135
136 wchar_t temp_filename[_MAX_PATH];
137 if (GetTempFileName(temp_path, L"sym", 0, temp_filename) == 0) {
138 return false;
139 }
140
141 FILE* temp_file = NULL;
142 #if _MSC_VER >= 1400 // MSVC 2005/8
143 if (_wfopen_s(&temp_file, temp_filename, L"w") != 0)
144 #else // _MSC_VER >= 1400
145 // _wfopen_s was introduced in MSVC8. Use _wfopen for earlier environments.
146 // Don't use it with MSVC8 and later, because it's deprecated.
147 if (!(temp_file = _wfopen(temp_filename, L"w")))
148 #endif // _MSC_VER >= 1400
149 {
150 return false;
151 }
152
153 bool success = writer.WriteSymbols(temp_file);
154 fclose(temp_file);
155 if (!success) {
156 _wunlink(temp_filename);
157 return false;
158 }
159
160 *temp_file_path = temp_filename;
161
162 return writer.GetModuleInfo(pdb_info);
163 }
164
printUsageAndExit()165 __declspec(noreturn) void printUsageAndExit() {
166 wprintf(L"Usage:\n\n"
167 L" symupload [--i] [--timeout NN] [--product product_name] ^\n"
168 L" <file.exe|file.dll> <symbol upload URL> ^\n"
169 L" [...<symbol upload URLs>]\n\n");
170 wprintf(L" - i: Extract inline information from pdb.\n");
171 wprintf(L" - Timeout is in milliseconds, or can be 0 to be unlimited.\n");
172 wprintf(L" - product_name is an HTTP-friendly product name. It must only\n"
173 L" contain an ascii subset: alphanumeric and punctuation.\n"
174 L" This string is case-sensitive.\n\n");
175 wprintf(L"Example:\n\n"
176 L" symupload.exe --timeout 0 --product Chrome ^\n"
177 L" chrome.dll http://no.free.symbol.server.for.you\n");
178 wprintf(L"\n");
179 wprintf(L"sym-upload-v2 usage:\n"
180 L" symupload -p [-f] <file.exe|file.dll> <API-URL> <API-key>\n");
181 wprintf(L"\n");
182 wprintf(L"sym_upload_v2 Options:\n");
183 wprintf(L" <API-URL> is the sym_upload_v2 API URL.\n");
184 wprintf(L" <API-key> is a secret used to authenticate with the API.\n");
185 wprintf(L" -p:\t Use sym_upload_v2 protocol.\n");
186 wprintf(L" -f:\t Force symbol upload if already exists.\n");
187
188 exit(0);
189 }
190
wmain(int argc,wchar_t * argv[])191 int wmain(int argc, wchar_t* argv[]) {
192 const wchar_t* module;
193 const wchar_t* product = nullptr;
194 bool handle_inline = false;
195 int timeout = -1;
196 int currentarg = 1;
197 bool use_sym_upload_v2 = false;
198 bool force = false;
199 const wchar_t* api_url = nullptr;
200 const wchar_t* api_key = nullptr;
201 while (argc > currentarg + 1) {
202 if (!wcscmp(L"--i", argv[currentarg])) {
203 handle_inline = true;
204 ++currentarg;
205 continue;
206 }
207 if (!wcscmp(L"--timeout", argv[currentarg])) {
208 timeout = _wtoi(argv[currentarg + 1]);
209 currentarg += 2;
210 continue;
211 }
212 if (!wcscmp(L"--product", argv[currentarg])) {
213 product = argv[currentarg + 1];
214 currentarg += 2;
215 continue;
216 }
217 if (!wcscmp(L"-p", argv[currentarg])) {
218 use_sym_upload_v2 = true;
219 ++currentarg;
220 continue;
221 }
222 if (!wcscmp(L"-f", argv[currentarg])) {
223 force = true;
224 ++currentarg;
225 continue;
226 }
227 break;
228 }
229
230 if (argc >= currentarg + 2)
231 module = argv[currentarg++];
232 else
233 printUsageAndExit();
234
235 wstring symbol_file;
236 PDBModuleInfo pdb_info;
237 if (!DumpSymbolsToTempFile(module, &symbol_file, &pdb_info, handle_inline)) {
238 fwprintf(stderr, L"Could not get symbol data from %s\n", module);
239 return 1;
240 }
241
242 wstring code_file = WindowsStringUtils::GetBaseName(wstring(module));
243 wstring file_version;
244 // Don't make a missing version a hard error. Issue a warning, and let the
245 // server decide whether to reject files without versions.
246 if (!GetFileVersionString(module, &file_version)) {
247 fwprintf(stderr, L"Warning: Could not get file version for %s\n", module);
248 }
249
250 bool success = true;
251
252 if (use_sym_upload_v2) {
253 if (argc >= currentarg + 2) {
254 api_url = argv[currentarg++];
255 api_key = argv[currentarg++];
256 wstring product_name = product ? wstring(product) : L"";
257
258 success = google_breakpad::SymUploadV2ProtocolSend(
259 api_url, api_key, timeout == -1 ? nullptr : &timeout,
260 pdb_info.debug_file, pdb_info.debug_identifier, symbol_file,
261 kSymbolUploadTypeBreakpad, product_name, force);
262 } else {
263 printUsageAndExit();
264 }
265 } else {
266 map<wstring, wstring> parameters;
267 parameters[L"code_file"] = code_file;
268 parameters[L"debug_file"] = pdb_info.debug_file;
269 parameters[L"debug_identifier"] = pdb_info.debug_identifier;
270 parameters[L"os"] = L"windows"; // This version of symupload is Windows-only
271 parameters[L"cpu"] = pdb_info.cpu;
272
273 map<wstring, wstring> files;
274 files[L"symbol_file"] = symbol_file;
275
276 if (!file_version.empty()) {
277 parameters[L"version"] = file_version;
278 }
279
280 // Don't make a missing product name a hard error. Issue a warning and let
281 // the server decide whether to reject files without product name.
282 if (product) {
283 parameters[L"product"] = product;
284 }
285 else {
286 fwprintf(
287 stderr,
288 L"Warning: No product name (flag --product) was specified for %s\n",
289 module);
290 }
291
292 while (currentarg < argc) {
293 int response_code;
294 if (!HTTPUpload::SendMultipartPostRequest(argv[currentarg], parameters, files,
295 timeout == -1 ? NULL : &timeout,
296 nullptr, &response_code)) {
297 success = false;
298 fwprintf(stderr,
299 L"Symbol file upload to %s failed. Response code = %ld\n",
300 argv[currentarg], response_code);
301 }
302 currentarg++;
303 }
304 }
305
306 _wunlink(symbol_file.c_str());
307
308 if (success) {
309 wprintf(L"Uploaded breakpad symbols for windows-%s/%s/%s (%s %s)\n",
310 pdb_info.cpu.c_str(), pdb_info.debug_file.c_str(),
311 pdb_info.debug_identifier.c_str(), code_file.c_str(),
312 file_version.c_str());
313 }
314
315 return success ? 0 : 1;
316 }
317