/thirdparty/breakpad/client/mac/crash_generation/Inspector.mm

http://github.com/tomahawk-player/tomahawk · Objective C++ · 431 lines · 259 code · 76 blank · 96 comment · 52 complexity · c9077187aeaad584668952596498fe6a MD5 · raw file

  1. // Copyright (c) 2007, Google Inc.
  2. // All rights reserved.
  3. //
  4. // Redistribution and use in source and binary forms, with or without
  5. // modification, are permitted provided that the following conditions are
  6. // met:
  7. //
  8. // * Redistributions of source code must retain the above copyright
  9. // notice, this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above
  11. // copyright notice, this list of conditions and the following disclaimer
  12. // in the documentation and/or other materials provided with the
  13. // distribution.
  14. // * Neither the name of Google Inc. nor the names of its
  15. // contributors may be used to endorse or promote products derived from
  16. // this software without specific prior written permission.
  17. //
  18. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. //
  30. // Utility that can inspect another process and write a crash dump
  31. #include <cstdio>
  32. #include <iostream>
  33. #include <servers/bootstrap.h>
  34. #include <stdio.h>
  35. #include <string.h>
  36. #include <string>
  37. #import "client/mac/crash_generation/Inspector.h"
  38. #import "client/mac/Framework/Breakpad.h"
  39. #import "client/mac/handler/minidump_generator.h"
  40. #import "common/mac/SimpleStringDictionary.h"
  41. #import "common/mac/MachIPC.h"
  42. #include "common/mac/bootstrap_compat.h"
  43. #import "GTMDefines.h"
  44. #import <Foundation/Foundation.h>
  45. namespace google_breakpad {
  46. //=============================================================================
  47. void Inspector::Inspect(const char *receive_port_name) {
  48. kern_return_t result = ResetBootstrapPort();
  49. if (result != KERN_SUCCESS) {
  50. return;
  51. }
  52. result = ServiceCheckIn(receive_port_name);
  53. if (result == KERN_SUCCESS) {
  54. result = ReadMessages();
  55. if (result == KERN_SUCCESS) {
  56. // Inspect the task and write a minidump file.
  57. bool wrote_minidump = InspectTask();
  58. // Send acknowledgement to the crashed process that the inspection
  59. // has finished. It will then be able to cleanly exit.
  60. // The return value is ignored because failure isn't fatal. If the process
  61. // didn't get the message there's nothing we can do, and we still want to
  62. // send the report.
  63. SendAcknowledgement();
  64. if (wrote_minidump) {
  65. // Ask the user if he wants to upload the crash report to a server,
  66. // and do so if he agrees.
  67. LaunchReporter(config_file_.GetFilePath());
  68. } else {
  69. fprintf(stderr, "Inspection of crashed process failed\n");
  70. }
  71. // Now that we're done reading messages, cleanup the service, but only
  72. // if there was an actual exception
  73. // Otherwise, it means the dump was generated on demand and the process
  74. // lives on, and we might be needed again in the future.
  75. if (exception_code_) {
  76. ServiceCheckOut(receive_port_name);
  77. }
  78. } else {
  79. PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
  80. }
  81. }
  82. }
  83. //=============================================================================
  84. kern_return_t Inspector::ResetBootstrapPort() {
  85. // A reasonable default, in case anything fails.
  86. bootstrap_subset_port_ = bootstrap_port;
  87. mach_port_t self_task = mach_task_self();
  88. kern_return_t kr = task_get_bootstrap_port(self_task,
  89. &bootstrap_subset_port_);
  90. if (kr != KERN_SUCCESS) {
  91. NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
  92. mach_error_string(kr), kr);
  93. return kr;
  94. }
  95. mach_port_t bootstrap_parent_port;
  96. kr = bootstrap_look_up(bootstrap_subset_port_,
  97. const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
  98. &bootstrap_parent_port);
  99. if (kr != BOOTSTRAP_SUCCESS) {
  100. NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
  101. #if defined(MAC_OS_X_VERSION_10_5) && \
  102. MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
  103. bootstrap_strerror(kr),
  104. #else
  105. mach_error_string(kr),
  106. #endif
  107. kr);
  108. return kr;
  109. }
  110. kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
  111. if (kr != KERN_SUCCESS) {
  112. NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
  113. mach_error_string(kr), kr);
  114. return kr;
  115. }
  116. // Some things access the bootstrap port through this global variable
  117. // instead of calling task_get_bootstrap_port.
  118. bootstrap_port = bootstrap_parent_port;
  119. return KERN_SUCCESS;
  120. }
  121. //=============================================================================
  122. kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
  123. // We need to get the mach port representing this service, so we can
  124. // get information from the crashed process.
  125. kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
  126. (char*)receive_port_name,
  127. &service_rcv_port_);
  128. if (kr != KERN_SUCCESS) {
  129. #if VERBOSE
  130. PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
  131. #endif
  132. }
  133. return kr;
  134. }
  135. //=============================================================================
  136. kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
  137. // We're done receiving mach messages from the crashed process,
  138. // so clean up a bit.
  139. kern_return_t kr;
  140. // DO NOT use mach_port_deallocate() here -- it will fail and the
  141. // following bootstrap_register() will also fail leaving our service
  142. // name hanging around forever (until reboot)
  143. kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
  144. if (kr != KERN_SUCCESS) {
  145. PRINT_MACH_RESULT(kr,
  146. "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
  147. return kr;
  148. }
  149. // Unregister the service associated with the receive port.
  150. kr = breakpad::BootstrapRegister(bootstrap_subset_port_,
  151. (char*)receive_port_name,
  152. MACH_PORT_NULL);
  153. if (kr != KERN_SUCCESS) {
  154. PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
  155. }
  156. return kr;
  157. }
  158. //=============================================================================
  159. kern_return_t Inspector::ReadMessages() {
  160. // Wait for an initial message from the crashed process containing basic
  161. // information about the crash.
  162. ReceivePort receive_port(service_rcv_port_);
  163. MachReceiveMessage message;
  164. kern_return_t result = receive_port.WaitForMessage(&message, 1000);
  165. if (result == KERN_SUCCESS) {
  166. InspectorInfo &info = (InspectorInfo &)*message.GetData();
  167. exception_type_ = info.exception_type;
  168. exception_code_ = info.exception_code;
  169. exception_subcode_ = info.exception_subcode;
  170. #if VERBOSE
  171. printf("message ID = %d\n", message.GetMessageID());
  172. #endif
  173. remote_task_ = message.GetTranslatedPort(0);
  174. crashing_thread_ = message.GetTranslatedPort(1);
  175. handler_thread_ = message.GetTranslatedPort(2);
  176. ack_port_ = message.GetTranslatedPort(3);
  177. #if VERBOSE
  178. printf("exception_type = %d\n", exception_type_);
  179. printf("exception_code = %d\n", exception_code_);
  180. printf("exception_subcode = %d\n", exception_subcode_);
  181. printf("remote_task = %d\n", remote_task_);
  182. printf("crashing_thread = %d\n", crashing_thread_);
  183. printf("handler_thread = %d\n", handler_thread_);
  184. printf("ack_port_ = %d\n", ack_port_);
  185. printf("parameter count = %d\n", info.parameter_count);
  186. #endif
  187. // In certain situations where multiple crash requests come
  188. // through quickly, we can end up with the mach IPC messages not
  189. // coming through correctly. Since we don't know what parameters
  190. // we've missed, we can't do much besides abort the crash dump
  191. // situation in this case.
  192. unsigned int parameters_read = 0;
  193. // The initial message contains the number of key value pairs that
  194. // we are expected to read.
  195. // Read each key/value pair, one mach message per key/value pair.
  196. for (unsigned int i = 0; i < info.parameter_count; ++i) {
  197. MachReceiveMessage parameter_message;
  198. result = receive_port.WaitForMessage(&parameter_message, 1000);
  199. if(result == KERN_SUCCESS) {
  200. KeyValueMessageData &key_value_data =
  201. (KeyValueMessageData&)*parameter_message.GetData();
  202. // If we get a blank key, make sure we don't increment the
  203. // parameter count; in some cases (notably on-demand generation
  204. // many times in a short period of time) caused the Mach IPC
  205. // messages to not come through correctly.
  206. if (strlen(key_value_data.key) == 0) {
  207. continue;
  208. }
  209. parameters_read++;
  210. config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
  211. } else {
  212. PRINT_MACH_RESULT(result, "Inspector: key/value message");
  213. break;
  214. }
  215. }
  216. if (parameters_read != info.parameter_count) {
  217. DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
  218. "dump generation.", parameters_read, info.parameter_count);
  219. return KERN_FAILURE;
  220. }
  221. }
  222. return result;
  223. }
  224. //=============================================================================
  225. bool Inspector::InspectTask() {
  226. // keep the task quiet while we're looking at it
  227. task_suspend(remote_task_);
  228. DEBUGLOG(stderr, "Suspended Remote task\n");
  229. NSString *minidumpDir;
  230. const char *minidumpDirectory =
  231. config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
  232. // If the client app has not specified a minidump directory,
  233. // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
  234. if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
  235. NSArray *libraryDirectories =
  236. NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
  237. NSUserDomainMask,
  238. YES);
  239. NSString *applicationSupportDirectory =
  240. [libraryDirectories objectAtIndex:0];
  241. NSString *library_subdirectory = [NSString
  242. stringWithUTF8String:kDefaultLibrarySubdirectory];
  243. NSString *breakpad_product = [NSString
  244. stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
  245. NSArray *path_components = [NSArray
  246. arrayWithObjects:applicationSupportDirectory,
  247. library_subdirectory,
  248. breakpad_product,
  249. nil];
  250. minidumpDir = [NSString pathWithComponents:path_components];
  251. } else {
  252. minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
  253. stringByExpandingTildeInPath];
  254. }
  255. DEBUGLOG(stderr,
  256. "Writing minidump to directory (%s)\n",
  257. [minidumpDir UTF8String]);
  258. MinidumpLocation minidumpLocation(minidumpDir);
  259. // Obscure bug alert:
  260. // Don't use [NSString stringWithFormat] to build up the path here since it
  261. // assumes system encoding and in RTL locales will prepend an LTR override
  262. // character for paths beginning with '/' which fileSystemRepresentation does
  263. // not remove. Filed as rdar://6889706 .
  264. NSString *path_ns = [NSString
  265. stringWithUTF8String:minidumpLocation.GetPath()];
  266. NSString *pathid_ns = [NSString
  267. stringWithUTF8String:minidumpLocation.GetID()];
  268. NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
  269. minidumpPath = [minidumpPath
  270. stringByAppendingPathExtension:@"dmp"];
  271. DEBUGLOG(stderr,
  272. "minidump path (%s)\n",
  273. [minidumpPath UTF8String]);
  274. config_file_.WriteFile( 0,
  275. &config_params_,
  276. minidumpLocation.GetPath(),
  277. minidumpLocation.GetID());
  278. MinidumpGenerator generator(remote_task_, handler_thread_);
  279. if (exception_type_ && exception_code_) {
  280. generator.SetExceptionInformation(exception_type_,
  281. exception_code_,
  282. exception_subcode_,
  283. crashing_thread_);
  284. }
  285. bool result = generator.Write([minidumpPath fileSystemRepresentation]);
  286. if (result) {
  287. DEBUGLOG(stderr, "Wrote minidump - OK\n");
  288. } else {
  289. DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno));
  290. }
  291. // let the task continue
  292. task_resume(remote_task_);
  293. DEBUGLOG(stderr, "Resumed remote task\n");
  294. return result;
  295. }
  296. //=============================================================================
  297. // The crashed task needs to be told that the inspection has finished.
  298. // It will wait on a mach port (with timeout) until we send acknowledgement.
  299. kern_return_t Inspector::SendAcknowledgement() {
  300. if (ack_port_ != MACH_PORT_DEAD) {
  301. MachPortSender sender(ack_port_);
  302. MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
  303. DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
  304. ack_port_);
  305. kern_return_t result = sender.SendMessage(ack_message, 2000);
  306. #if VERBOSE
  307. PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
  308. #endif
  309. return result;
  310. }
  311. DEBUGLOG(stderr, "Inspector: port translation failure!\n");
  312. return KERN_INVALID_NAME;
  313. }
  314. //=============================================================================
  315. void Inspector::LaunchReporter(const char *inConfigFilePath) {
  316. // Extract the path to the reporter executable.
  317. const char *reporterExecutablePath =
  318. config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
  319. DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
  320. // Setup and launch the crash dump sender.
  321. const char *argv[3];
  322. argv[0] = reporterExecutablePath;
  323. argv[1] = inConfigFilePath;
  324. argv[2] = NULL;
  325. // Launch the reporter
  326. pid_t pid = fork();
  327. // If we're in the child, load in our new executable and run.
  328. // The parent will not wait for the child to complete.
  329. if (pid == 0) {
  330. execv(argv[0], (char * const *)argv);
  331. config_file_.Unlink(); // launch failed - get rid of config file
  332. DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
  333. _exit(1);
  334. }
  335. // Wait until the Reporter child process exits.
  336. //
  337. // We'll use a timeout of one minute.
  338. int timeoutCount = 60; // 60 seconds
  339. while (timeoutCount-- > 0) {
  340. int status;
  341. pid_t result = waitpid(pid, &status, WNOHANG);
  342. if (result == 0) {
  343. // The child has not yet finished.
  344. sleep(1);
  345. } else if (result == -1) {
  346. DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
  347. errno);
  348. break;
  349. } else {
  350. // child has finished
  351. break;
  352. }
  353. }
  354. }
  355. } // namespace google_breakpad