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// symupload.mm: Upload a symbol file to a HTTP server. The upload is sent as 30// a multipart/form-data POST request with the following parameters: 31// code_file: the basename of the module, e.g. "app" 32// debug_file: the basename of the debugging file, e.g. "app" 33// debug_identifier: the debug file's identifier, usually consisting of 34// the guid and age embedded in the pdb, e.g. 35// "11111111BBBB3333DDDD555555555555F" 36// os: the operating system that the module was built for 37// cpu: the CPU that the module was built for (x86 or ppc) 38// symbol_file: the contents of the breakpad-format symbol file 39 40#include <fcntl.h> 41#include <sys/stat.h> 42#include <unistd.h> 43 44#include <Foundation/Foundation.h> 45 46#include "HTTPMultipartUpload.h" 47#include "HTTPPutRequest.h" 48#include "SymbolCollectorClient.h" 49#include "common/mac/dump_syms.h" 50 51using google_breakpad::DumpSymbols; 52 53NSString* const kBreakpadSymbolType = @"BREAKPAD"; 54NSString* const kMachOSymbolType = @"MACHO"; 55NSString* const kDSYMSymbolType = @"DSYM"; 56 57typedef enum { kSymUploadProtocolV1, kSymUploadProtocolV2 } SymUploadProtocol; 58 59typedef enum { 60 kResultSuccess = 0, 61 kResultFailure = 1, 62 kResultAlreadyExists = 2 63} Result; 64 65typedef struct { 66 NSString* symbolsPath; 67 NSString* uploadURLStr; 68 SymUploadProtocol symUploadProtocol; 69 NSString* apiKey; 70 BOOL force; 71 Result result; 72 NSString* type; 73 NSString* codeFile; 74 NSString* debugID; 75 NSString* productName; 76} Options; 77 78//============================================================================= 79static NSArray* ModuleDataForSymbolFile(NSString* file) { 80 NSFileHandle* fh = [NSFileHandle fileHandleForReadingAtPath:file]; 81 NSData* data = [fh readDataOfLength:1024]; 82 NSString* str = [[NSString alloc] initWithData:data 83 encoding:NSUTF8StringEncoding]; 84 NSScanner* scanner = [NSScanner scannerWithString:str]; 85 NSString* line; 86 NSMutableArray* parts = nil; 87 const int MODULE_ID_INDEX = 3; 88 89 if ([scanner scanUpToString:@"\n" intoString:&line]) { 90 parts = [[NSMutableArray alloc] init]; 91 NSScanner* moduleInfoScanner = [NSScanner scannerWithString:line]; 92 NSString* moduleInfo; 93 // Get everything BEFORE the module name. None of these properties 94 // can have spaces. 95 for (int i = 0; i <= MODULE_ID_INDEX; i++) { 96 [moduleInfoScanner scanUpToString:@" " intoString:&moduleInfo]; 97 [parts addObject:moduleInfo]; 98 } 99 100 // Now get the module name. This can have a space so we scan to 101 // the end of the line. 102 [moduleInfoScanner scanUpToString:@"\n" intoString:&moduleInfo]; 103 [parts addObject:moduleInfo]; 104 } 105 106 [str release]; 107 108 return parts; 109} 110 111//============================================================================= 112static void StartSymUploadProtocolV1(Options* options, 113 NSString* OS, 114 NSString* CPU, 115 NSString* debugID, 116 NSString* debugFile) { 117 NSURL* url = [NSURL URLWithString:options->uploadURLStr]; 118 HTTPMultipartUpload* ul = [[HTTPMultipartUpload alloc] initWithURL:url]; 119 NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; 120 121 // Add parameters 122 [parameters setObject:debugID forKey:@"debug_identifier"]; 123 [parameters setObject:OS forKey:@"os"]; 124 [parameters setObject:CPU forKey:@"cpu"]; 125 [parameters setObject:debugFile forKey:@"debug_file"]; 126 [parameters setObject:debugFile forKey:@"code_file"]; 127 [ul setParameters:parameters]; 128 129 NSArray* keys = [parameters allKeys]; 130 int count = [keys count]; 131 for (int i = 0; i < count; ++i) { 132 NSString* key = [keys objectAtIndex:i]; 133 NSString* value = [parameters objectForKey:key]; 134 fprintf(stdout, "'%s' = '%s'\n", [key UTF8String], [value UTF8String]); 135 } 136 137 // Add file 138 [ul addFileAtPath:options->symbolsPath name:@"symbol_file"]; 139 140 // Send it 141 NSError* error = nil; 142 NSData* data = [ul send:&error]; 143 NSString* result = [[NSString alloc] initWithData:data 144 encoding:NSUTF8StringEncoding]; 145 int status = [[ul response] statusCode]; 146 147 fprintf(stdout, "Send: %s\n", 148 error ? [[error description] UTF8String] : "No Error"); 149 fprintf(stdout, "Response: %d\n", status); 150 fprintf(stdout, "Result: %lu bytes\n%s\n", (unsigned long)[data length], 151 [result UTF8String]); 152 153 [result release]; 154 [ul release]; 155 options->result = (!error && status == 200) ? kResultSuccess : kResultFailure; 156} 157 158//============================================================================= 159static void StartSymUploadProtocolV2(Options* options, 160 NSString* debugID, 161 NSString* debugFile) { 162 options->result = kResultFailure; 163 164 // Only check status of BREAKPAD symbols, because the v2 protocol doesn't 165 // (yet) have a way to check status of other symbol types. 166 if (!options->force && [options->type isEqualToString:kBreakpadSymbolType]) { 167 SymbolStatus symbolStatus = 168 [SymbolCollectorClient checkSymbolStatusOnServer:options->uploadURLStr 169 withAPIKey:options->apiKey 170 withDebugFile:debugFile 171 withDebugID:debugID]; 172 if (symbolStatus == SymbolStatusFound) { 173 fprintf(stdout, "Symbol file already exists, upload aborted." 174 " Use \"-f\" to overwrite.\n"); 175 options->result = kResultAlreadyExists; 176 return; 177 } else if (symbolStatus == SymbolStatusUnknown) { 178 fprintf(stdout, "Failed to get check for existing symbol.\n"); 179 return; 180 } 181 } 182 183 UploadURLResponse* URLResponse = 184 [SymbolCollectorClient createUploadURLOnServer:options->uploadURLStr 185 withAPIKey:options->apiKey]; 186 if (URLResponse == nil) { 187 return; 188 } 189 190 NSURL* uploadURL = [NSURL URLWithString:[URLResponse uploadURL]]; 191 HTTPPutRequest* putRequest = [[HTTPPutRequest alloc] initWithURL:uploadURL]; 192 [putRequest setFile:options->symbolsPath]; 193 194 NSError* error = nil; 195 NSData* data = [putRequest send:&error]; 196 NSString* result = [[NSString alloc] initWithData:data 197 encoding:NSUTF8StringEncoding]; 198 int responseCode = [[putRequest response] statusCode]; 199 [putRequest release]; 200 201 if (error || responseCode != 200) { 202 fprintf(stdout, "Failed to upload symbol file.\n"); 203 fprintf(stdout, "Response code: %d\n", responseCode); 204 fprintf(stdout, "Response:\n"); 205 fprintf(stdout, "%s\n", [result UTF8String]); 206 return; 207 } 208 209 CompleteUploadResult completeUploadResult = 210 [SymbolCollectorClient completeUploadOnServer:options->uploadURLStr 211 withAPIKey:options->apiKey 212 withUploadKey:[URLResponse uploadKey] 213 withDebugFile:debugFile 214 withDebugID:debugID 215 withType:options->type 216 withProductName:options->productName]; 217 [URLResponse release]; 218 if (completeUploadResult == CompleteUploadResultError) { 219 fprintf(stdout, "Failed to complete upload.\n"); 220 return; 221 } else if (completeUploadResult == CompleteUploadResultDuplicateData) { 222 fprintf(stdout, "Uploaded file checksum matched existing file checksum," 223 " no change necessary.\n"); 224 } else { 225 fprintf(stdout, "Successfully sent the symbol file.\n"); 226 } 227 options->result = kResultSuccess; 228} 229 230//============================================================================= 231static void Start(Options* options) { 232 // If non-BREAKPAD upload special-case. 233 if (![options->type isEqualToString:kBreakpadSymbolType]) { 234 StartSymUploadProtocolV2(options, options->debugID, options->codeFile); 235 return; 236 } 237 238 NSArray* moduleParts = ModuleDataForSymbolFile(options->symbolsPath); 239 // MODULE <os> <cpu> <uuid> <module-name> 240 // 0 1 2 3 4 241 NSString* OS = [moduleParts objectAtIndex:1]; 242 NSString* CPU = [moduleParts objectAtIndex:2]; 243 NSMutableString* debugID = 244 [NSMutableString stringWithString:[moduleParts objectAtIndex:3]]; 245 [debugID replaceOccurrencesOfString:@"-" 246 withString:@"" 247 options:0 248 range:NSMakeRange(0, [debugID length])]; 249 NSString* debugFile = [moduleParts objectAtIndex:4]; 250 251 if (options->symUploadProtocol == kSymUploadProtocolV1) { 252 StartSymUploadProtocolV1(options, OS, CPU, debugID, debugFile); 253 } else if (options->symUploadProtocol == kSymUploadProtocolV2) { 254 StartSymUploadProtocolV2(options, debugID, debugFile); 255 } 256} 257 258//============================================================================= 259static void Usage(int argc, const char* argv[]) { 260 fprintf(stderr, "Submit symbol information.\n"); 261 fprintf(stderr, "Usage: %s [options] <symbol-file> <upload-URL>\n", argv[0]); 262 fprintf(stderr, "<symbol-file> should be created by using the dump_syms " 263 "tool.\n"); 264 fprintf(stderr, "<upload-URL> is the destination for the upload.\n"); 265 fprintf(stderr, "Options:\n"); 266 fprintf(stderr, "\t-p <protocol>: protocol to use for upload, accepts " 267 "[\"sym-upload-v1\", \"sym-upload-v2\"]. Default is " 268 "\"sym-upload-v1\".\n"); 269 fprintf(stderr, "\t-k <api-key>: secret for authentication with upload " 270 "server. [Only in sym-upload-v2 protocol mode]\n"); 271 fprintf(stderr, "\t-f: Overwrite symbol file on server if already present. " 272 "[Only in sym-upload-v2 protocol mode]\n"); 273 fprintf( 274 stderr, 275 "\t-t: <symbol-type> Explicitly set symbol upload type (" 276 "default is 'breakpad').\n" 277 "\t One of ['breakpad', 'elf', 'pe', 'macho', 'debug_only', 'dwp', " 278 "'dsym', 'pdb'].\n" 279 "\t Note: When this flag is set to anything other than 'breakpad', then " 280 "the '-c' and '-i' flags must also be set.\n"); 281 fprintf(stderr, "\t-c: <code-file> Explicitly set 'code_file' for symbol " 282 "upload (basename of executable).\n"); 283 fprintf(stderr, "\t-i: <debug-id> Explicitly set 'debug_id' for symbol " 284 "upload (typically build ID of executable). The debug-id for " 285 "symbol-types 'dsym' and 'macho' will be determined " 286 "automatically. \n"); 287 fprintf(stderr, "\t-n: <product-name> Optionally set 'product_name' for " 288 "symbol upload\n"); 289 fprintf(stderr, "\t-h: Usage\n"); 290 fprintf(stderr, "\t-?: Usage\n"); 291 fprintf(stderr, "\n"); 292 fprintf(stderr, "Exit codes:\n"); 293 fprintf(stderr, "\t%d: Success\n", kResultSuccess); 294 fprintf(stderr, "\t%d: Failure\n", kResultFailure); 295 fprintf(stderr, 296 "\t%d: Symbol file already exists on server (and -f was not " 297 "specified).\n", 298 kResultAlreadyExists); 299 fprintf(stderr, 300 "\t [This exit code will only be returned by the sym-upload-v2 " 301 "protocol.\n"); 302 fprintf(stderr, 303 "\t The sym-upload-v1 protocol can return either Success or " 304 "Failure\n"); 305 fprintf(stderr, "\t in this case, and the action taken by the server is " 306 "unspecified.]\n"); 307 fprintf(stderr, "\n"); 308 fprintf(stderr, "Examples:\n"); 309 fprintf(stderr, " With 'sym-upload-v1':\n"); 310 fprintf(stderr, " %s path/to/symbol_file http://myuploadserver\n", 311 argv[0]); 312 fprintf(stderr, " With 'sym-upload-v2':\n"); 313 fprintf(stderr, " [Defaulting to symbol type 'BREAKPAD']\n"); 314 fprintf(stderr, 315 " %s -p sym-upload-v2 -k mysecret123! " 316 "path/to/symbol_file http://myuploadserver\n", 317 argv[0]); 318 fprintf(stderr, " [Explicitly set symbol type to 'macho']\n"); 319 fprintf(stderr, 320 " %s -p sym-upload-v2 -k mysecret123! -t macho " 321 "-c app -i 11111111BBBB3333DDDD555555555555F " 322 "path/to/symbol_file http://myuploadserver\n", 323 argv[0]); 324} 325 326//============================================================================= 327static void SetupOptions(int argc, const char* argv[], Options* options) { 328 // Set default options values. 329 options->symUploadProtocol = kSymUploadProtocolV1; 330 options->apiKey = nil; 331 options->type = kBreakpadSymbolType; 332 options->codeFile = nil; 333 options->debugID = nil; 334 options->force = NO; 335 options->productName = nil; 336 337 extern int optind; 338 int ch; 339 340 while ((ch = getopt(argc, (char* const*)argv, "p:k:t:c:i:n:hf?")) != -1) { 341 switch (ch) { 342 case 'p': 343 if (strcmp(optarg, "sym-upload-v2") == 0) { 344 options->symUploadProtocol = kSymUploadProtocolV2; 345 break; 346 } else if (strcmp(optarg, "sym-upload-v1") == 0) { 347 // This is already the default but leave in case that changes. 348 options->symUploadProtocol = kSymUploadProtocolV1; 349 break; 350 } 351 Usage(argc, argv); 352 exit(0); 353 break; 354 case 'k': 355 options->apiKey = [NSString stringWithCString:optarg 356 encoding:NSASCIIStringEncoding]; 357 break; 358 case 't': { 359 // This is really an enum, so treat as upper-case for consistency with 360 // enum naming convention on server-side. 361 options->type = [[NSString stringWithCString:optarg 362 encoding:NSASCIIStringEncoding] 363 uppercaseString]; 364 break; 365 } 366 case 'c': 367 options->codeFile = [NSString stringWithCString:optarg 368 encoding:NSASCIIStringEncoding]; 369 break; 370 case 'i': 371 options->debugID = [NSString stringWithCString:optarg 372 encoding:NSASCIIStringEncoding]; 373 break; 374 case 'n': 375 options->productName = 376 [NSString stringWithCString:optarg 377 encoding:NSASCIIStringEncoding]; 378 break; 379 case 'f': 380 options->force = YES; 381 break; 382 default: 383 Usage(argc, argv); 384 exit(0); 385 break; 386 } 387 } 388 389 if ((argc - optind) != 2) { 390 fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]); 391 Usage(argc, argv); 392 exit(1); 393 } 394 395 int fd = open(argv[optind], O_RDONLY); 396 if (fd < 0) { 397 fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); 398 exit(1); 399 } 400 401 struct stat statbuf; 402 if (fstat(fd, &statbuf) < 0) { 403 fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); 404 close(fd); 405 exit(1); 406 } 407 close(fd); 408 409 if (!S_ISREG(statbuf.st_mode)) { 410 fprintf(stderr, "%s: %s: not a regular file\n", argv[0], argv[optind]); 411 exit(1); 412 } 413 414 bool isBreakpadUpload = [options->type isEqualToString:kBreakpadSymbolType]; 415 bool hasCodeFile = options->codeFile != nil; 416 bool hasDebugID = options->debugID != nil; 417 if (isBreakpadUpload && (hasCodeFile || hasDebugID)) { 418 fprintf(stderr, "\n"); 419 fprintf(stderr, 420 "%s: -c and -i should only be specified for non-breakpad " 421 "symbol upload types.\n", 422 argv[0]); 423 fprintf(stderr, "\n"); 424 Usage(argc, argv); 425 exit(1); 426 } 427 428 if (!isBreakpadUpload && hasCodeFile && !hasDebugID && 429 ([options->type isEqualToString:kMachOSymbolType] || 430 [options->type isEqualToString:kDSYMSymbolType])) { 431 DumpSymbols dump_symbols(SYMBOLS_AND_FILES | INLINES, false); 432 if (dump_symbols.Read(argv[optind])) { 433 std::string identifier = dump_symbols.Identifier(); 434 if (identifier.empty()) { 435 fprintf(stderr, "\n"); 436 fprintf(stderr, 437 "%s: Unable to determine debug-id. Please specify with '-i'.\n", 438 argv[0]); 439 fprintf(stderr, "\n"); 440 Usage(argc, argv); 441 exit(1); 442 } 443 options->debugID = [NSString stringWithUTF8String:identifier.c_str()]; 444 hasDebugID = true; 445 } 446 } 447 448 if (!isBreakpadUpload && (!hasCodeFile || !hasDebugID)) { 449 fprintf(stderr, "\n"); 450 fprintf(stderr, 451 "%s: -c and -i must be specified for non-breakpad " 452 "symbol upload types.\n", 453 argv[0]); 454 fprintf(stderr, "\n"); 455 Usage(argc, argv); 456 exit(1); 457 } 458 459 options->symbolsPath = [NSString stringWithUTF8String:argv[optind]]; 460 options->uploadURLStr = [NSString stringWithUTF8String:argv[optind + 1]]; 461} 462 463//============================================================================= 464int main(int argc, const char* argv[]) { 465 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 466 Options options; 467 468 bzero(&options, sizeof(Options)); 469 SetupOptions(argc, argv, &options); 470 Start(&options); 471 472 [pool release]; 473 return options.result; 474} 475