OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "client/simulate_crash.h" |
| 16 |
| 17 #include <mach/mach.h> |
| 18 #include <string.h> |
| 19 |
| 20 #include "base/basictypes.h" |
| 21 #include "base/strings/stringprintf.h" |
| 22 #include "build/build_config.h" |
| 23 #include "gtest/gtest.h" |
| 24 #include "util/mach/exc_server_variants.h" |
| 25 #include "util/mach/exception_behaviors.h" |
| 26 #include "util/mach/exception_ports.h" |
| 27 #include "util/mach/mach_extensions.h" |
| 28 #include "util/mach/mach_message_server.h" |
| 29 #include "util/mach/symbolic_constants_mach.h" |
| 30 #include "util/test/mac/mach_errors.h" |
| 31 #include "util/test/mac/mach_multiprocess.h" |
| 32 |
| 33 namespace crashpad { |
| 34 namespace test { |
| 35 namespace { |
| 36 |
| 37 class TestSimulateCrashMac final : public MachMultiprocess, |
| 38 public UniversalMachExcServer { |
| 39 public: |
| 40 // Defines which targets the child should set an EXC_CRASH exception handler |
| 41 // for. |
| 42 enum ExceptionPortsTarget { |
| 43 // The child should clear its EXC_CRASH handler for both its task and thread |
| 44 // targets. SimulateCrash() will attempt to deliver the exception to the |
| 45 // host target, which will fail if not running as root. In any case, the |
| 46 // parent should not expect to receive any exception message from the child. |
| 47 kExceptionPortsTargetNone = 0, |
| 48 |
| 49 // The child will set an EXC_CRASH handler for its task target, and clear it |
| 50 // for its thread target. The parent runs an exception server to receive |
| 51 // the child’s simulated crash message. |
| 52 kExceptionPortsTargetTask, |
| 53 |
| 54 // The child will set an EXC_CRASH handler for its thread target, and clear |
| 55 // it for its task target. The parent runs an exception server to receive |
| 56 // the child’s simulated crash message. |
| 57 kExceptionPortsTargetThread, |
| 58 |
| 59 // The child sets an EXC_CRASH handler for both its task and thread targets. |
| 60 // The parent runs an exception server to receive the message expected to be |
| 61 // delivered to the thread target, but returns an error code. The child will |
| 62 // then fall back to trying the server registered for the task target, |
| 63 // sending a second message to the parent. The server in the parent will |
| 64 // handle this one successfully. |
| 65 kExceptionPortsTargetBoth, |
| 66 }; |
| 67 |
| 68 TestSimulateCrashMac(ExceptionPortsTarget target, |
| 69 exception_behavior_t behavior, |
| 70 thread_state_flavor_t flavor) |
| 71 : target_(target), |
| 72 behavior_(behavior), |
| 73 flavor_(flavor), |
| 74 succeed_(true) { |
| 75 } |
| 76 |
| 77 ~TestSimulateCrashMac() {} |
| 78 |
| 79 // UniversalMachExcServer: |
| 80 kern_return_t CatchMachException(exception_behavior_t behavior, |
| 81 exception_handler_t exception_port, |
| 82 thread_t thread, |
| 83 task_t task, |
| 84 exception_type_t exception, |
| 85 const mach_exception_data_type_t* code, |
| 86 mach_msg_type_number_t code_count, |
| 87 thread_state_flavor_t* flavor, |
| 88 const natural_t* old_state, |
| 89 mach_msg_type_number_t old_state_count, |
| 90 thread_state_t new_state, |
| 91 mach_msg_type_number_t* new_state_count, |
| 92 bool* destroy_complex_request) override { |
| 93 *destroy_complex_request = true; |
| 94 |
| 95 // Check the entire exception message, because most or all of it was |
| 96 // generated by SimulateCrash() instead of the kernel. |
| 97 |
| 98 EXPECT_EQ(behavior_, behavior); |
| 99 EXPECT_EQ(LocalPort(), exception_port); |
| 100 if (ExceptionBehaviorHasIdentity(behavior)) { |
| 101 EXPECT_NE(THREAD_NULL, thread); |
| 102 EXPECT_EQ(ChildTask(), task); |
| 103 } else { |
| 104 EXPECT_EQ(THREAD_NULL, thread); |
| 105 EXPECT_EQ(TASK_NULL, task); |
| 106 } |
| 107 EXPECT_EQ(kMachExceptionSimulated, exception); |
| 108 EXPECT_EQ(2u, code_count); |
| 109 if (code_count >= 1) { |
| 110 EXPECT_EQ(0, code[0]); |
| 111 } |
| 112 if (code_count >= 2) { |
| 113 EXPECT_EQ(0, code[1]); |
| 114 } |
| 115 if (!ExceptionBehaviorHasState(behavior)) { |
| 116 EXPECT_EQ(THREAD_STATE_NONE, *flavor); |
| 117 } else { |
| 118 EXPECT_EQ(flavor_, *flavor); |
| 119 switch (*flavor) { |
| 120 #if defined(ARCH_CPU_X86_FAMILY) |
| 121 case x86_THREAD_STATE: { |
| 122 EXPECT_EQ(x86_THREAD_STATE_COUNT, old_state_count); |
| 123 const x86_thread_state* state = |
| 124 reinterpret_cast<const x86_thread_state*>(old_state); |
| 125 switch (state->tsh.flavor) { |
| 126 case x86_THREAD_STATE32: |
| 127 EXPECT_EQ(static_cast<int>(x86_THREAD_STATE32_COUNT), |
| 128 state->tsh.count); |
| 129 break; |
| 130 case x86_THREAD_STATE64: |
| 131 EXPECT_EQ(static_cast<int>(x86_THREAD_STATE64_COUNT), |
| 132 state->tsh.count); |
| 133 break; |
| 134 default: |
| 135 ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor; |
| 136 break; |
| 137 } |
| 138 break; |
| 139 } |
| 140 case x86_FLOAT_STATE: { |
| 141 EXPECT_EQ(x86_FLOAT_STATE_COUNT, old_state_count); |
| 142 const x86_float_state* state = |
| 143 reinterpret_cast<const x86_float_state*>(old_state); |
| 144 switch (state->fsh.flavor) { |
| 145 case x86_FLOAT_STATE32: |
| 146 EXPECT_EQ(static_cast<int>(x86_FLOAT_STATE32_COUNT), |
| 147 state->fsh.count); |
| 148 break; |
| 149 case x86_FLOAT_STATE64: |
| 150 EXPECT_EQ(static_cast<int>(x86_FLOAT_STATE64_COUNT), |
| 151 state->fsh.count); |
| 152 break; |
| 153 default: |
| 154 ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor; |
| 155 break; |
| 156 } |
| 157 break; |
| 158 } |
| 159 case x86_DEBUG_STATE: { |
| 160 EXPECT_EQ(x86_DEBUG_STATE_COUNT, old_state_count); |
| 161 const x86_debug_state* state = |
| 162 reinterpret_cast<const x86_debug_state*>(old_state); |
| 163 switch (state->dsh.flavor) { |
| 164 case x86_DEBUG_STATE32: |
| 165 EXPECT_EQ(static_cast<int>(x86_DEBUG_STATE32_COUNT), |
| 166 state->dsh.count); |
| 167 break; |
| 168 case x86_DEBUG_STATE64: |
| 169 EXPECT_EQ(static_cast<int>(x86_DEBUG_STATE64_COUNT), |
| 170 state->dsh.count); |
| 171 break; |
| 172 default: |
| 173 ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor; |
| 174 break; |
| 175 } |
| 176 break; |
| 177 } |
| 178 case x86_THREAD_STATE32: |
| 179 EXPECT_EQ(x86_THREAD_STATE32_COUNT, old_state_count); |
| 180 break; |
| 181 case x86_FLOAT_STATE32: |
| 182 EXPECT_EQ(x86_FLOAT_STATE32_COUNT, old_state_count); |
| 183 break; |
| 184 case x86_DEBUG_STATE32: |
| 185 EXPECT_EQ(x86_DEBUG_STATE32_COUNT, old_state_count); |
| 186 break; |
| 187 case x86_THREAD_STATE64: |
| 188 EXPECT_EQ(x86_THREAD_STATE64_COUNT, old_state_count); |
| 189 break; |
| 190 case x86_FLOAT_STATE64: |
| 191 EXPECT_EQ(x86_FLOAT_STATE64_COUNT, old_state_count); |
| 192 break; |
| 193 case x86_DEBUG_STATE64: |
| 194 EXPECT_EQ(x86_DEBUG_STATE64_COUNT, old_state_count); |
| 195 break; |
| 196 #else |
| 197 #error Port to your CPU architecture |
| 198 #endif |
| 199 default: |
| 200 ADD_FAILURE() << "unexpected flavor " << *flavor; |
| 201 break; |
| 202 } |
| 203 |
| 204 // Attempt to set a garbage thread state, which would cause the child to |
| 205 // crash inside SimulateCrash() if it actually succeeded. This tests that |
| 206 // SimulateCrash() ignores new_state instead of attempting to set the |
| 207 // state as the kernel would do. This operates in conjunction with the |
| 208 // |true| argument to ExcServerSuccessfulReturnValue() below. |
| 209 *new_state_count = old_state_count; |
| 210 size_t new_state_size = sizeof(natural_t) * old_state_count; |
| 211 memset(new_state, 0xa5, new_state_size); |
| 212 } |
| 213 |
| 214 if (!succeed_) { |
| 215 // The client has registered EXC_CRASH handlers for both its thread and |
| 216 // task targets, and sent a simulated exception message to its |
| 217 // thread-level EXC_CRASH handler. To test that it will fall back to |
| 218 // trying the task-level EXC_CRASH handler, return a failure code, which |
| 219 // should cause SimulateCrash() to try the next target. |
| 220 EXPECT_EQ(kExceptionPortsTargetBoth, target_); |
| 221 return KERN_ABORTED; |
| 222 } |
| 223 |
| 224 return ExcServerSuccessfulReturnValue(behavior, true); |
| 225 } |
| 226 |
| 227 private: |
| 228 // MachMultiprocess: |
| 229 |
| 230 void MachMultiprocessParent() override { |
| 231 if (target_ == kExceptionPortsTargetNone) { |
| 232 // The child does not have any EXC_CRASH handlers registered for its |
| 233 // thread or task targets, so no exception message is expected to be |
| 234 // generated. Don’t run the server at all. |
| 235 return; |
| 236 } |
| 237 |
| 238 mach_msg_return_t mr; |
| 239 if (target_ == kExceptionPortsTargetBoth) { |
| 240 // The client has registered EXC_CRASH handlers for both its thread and |
| 241 // task targets. Run a server that will return a failure code when the |
| 242 // exception message is sent to the thread target, which will cause the |
| 243 // client to fall back to the task target and send another message. |
| 244 succeed_ = false; |
| 245 mr = MachMessageServer::Run(this, |
| 246 LocalPort(), |
| 247 MACH_MSG_OPTION_NONE, |
| 248 MachMessageServer::kOneShot, |
| 249 MachMessageServer::kBlocking, |
| 250 MACH_MSG_TIMEOUT_NONE); |
| 251 EXPECT_EQ(MACH_MSG_SUCCESS, mr) |
| 252 << MachErrorMessage(mr, "MachMessageServer::Run"); |
| 253 } |
| 254 |
| 255 succeed_ = true; |
| 256 mr = MachMessageServer::Run(this, |
| 257 LocalPort(), |
| 258 MACH_MSG_OPTION_NONE, |
| 259 MachMessageServer::kOneShot, |
| 260 MachMessageServer::kBlocking, |
| 261 MACH_MSG_TIMEOUT_NONE); |
| 262 EXPECT_EQ(MACH_MSG_SUCCESS, mr) |
| 263 << MachErrorMessage(mr, "MachMessageServer::Run"); |
| 264 } |
| 265 |
| 266 void MachMultiprocessChild() override { |
| 267 bool task_valid = target_ == kExceptionPortsTargetTask || |
| 268 target_ == kExceptionPortsTargetBoth; |
| 269 ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, |
| 270 TASK_NULL); |
| 271 ASSERT_TRUE(task_exception_ports.SetExceptionPort( |
| 272 EXC_MASK_CRASH, |
| 273 task_valid ? RemotePort() : MACH_PORT_NULL, |
| 274 behavior_, |
| 275 flavor_)); |
| 276 |
| 277 bool thread_valid = target_ == kExceptionPortsTargetThread || |
| 278 target_ == kExceptionPortsTargetBoth; |
| 279 ExceptionPorts thread_exception_ports(ExceptionPorts::kTargetTypeThread, |
| 280 THREAD_NULL); |
| 281 ASSERT_TRUE(thread_exception_ports.SetExceptionPort( |
| 282 EXC_MASK_CRASH, |
| 283 thread_valid ? RemotePort() : MACH_PORT_NULL, |
| 284 behavior_, |
| 285 flavor_)); |
| 286 |
| 287 CRASHPAD_SIMULATE_CRASH(); |
| 288 } |
| 289 |
| 290 ExceptionPortsTarget target_; |
| 291 exception_behavior_t behavior_; |
| 292 thread_state_flavor_t flavor_; |
| 293 bool succeed_; |
| 294 |
| 295 DISALLOW_COPY_AND_ASSIGN(TestSimulateCrashMac); |
| 296 }; |
| 297 |
| 298 TEST(SimulateCrash, SimulateCrash) { |
| 299 const TestSimulateCrashMac::ExceptionPortsTarget kTargets[] = { |
| 300 TestSimulateCrashMac::kExceptionPortsTargetNone, |
| 301 TestSimulateCrashMac::kExceptionPortsTargetTask, |
| 302 TestSimulateCrashMac::kExceptionPortsTargetThread, |
| 303 TestSimulateCrashMac::kExceptionPortsTargetBoth, |
| 304 }; |
| 305 |
| 306 const exception_behavior_t kBehaviors[] = { |
| 307 EXCEPTION_DEFAULT, |
| 308 EXCEPTION_STATE, |
| 309 EXCEPTION_STATE_IDENTITY, |
| 310 EXCEPTION_DEFAULT | kMachExceptionCodes, |
| 311 EXCEPTION_STATE | kMachExceptionCodes, |
| 312 EXCEPTION_STATE_IDENTITY | kMachExceptionCodes, |
| 313 }; |
| 314 |
| 315 const thread_state_flavor_t kFlavors[] = { |
| 316 #if defined(ARCH_CPU_X86_FAMILY) |
| 317 x86_THREAD_STATE, |
| 318 x86_FLOAT_STATE, |
| 319 x86_DEBUG_STATE, |
| 320 #if defined(ARCH_CPU_X86) |
| 321 x86_THREAD_STATE32, |
| 322 x86_FLOAT_STATE32, |
| 323 x86_DEBUG_STATE32, |
| 324 #elif defined(ARCH_CPU_X86_64) |
| 325 x86_THREAD_STATE64, |
| 326 x86_FLOAT_STATE64, |
| 327 x86_DEBUG_STATE64, |
| 328 #endif |
| 329 #else |
| 330 #error Port to your CPU architecture |
| 331 #endif |
| 332 }; |
| 333 |
| 334 for (size_t target_index = 0; |
| 335 target_index < arraysize(kTargets); |
| 336 ++target_index) { |
| 337 TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index]; |
| 338 SCOPED_TRACE(base::StringPrintf( |
| 339 "target_index %zu, target %d", target_index, target)); |
| 340 |
| 341 for (size_t behavior_index = 0; |
| 342 behavior_index < arraysize(kBehaviors); |
| 343 ++behavior_index) { |
| 344 exception_behavior_t behavior = kBehaviors[behavior_index]; |
| 345 SCOPED_TRACE(base::StringPrintf( |
| 346 "behavior_index %zu, behavior %s", |
| 347 behavior_index, |
| 348 ExceptionBehaviorToString(behavior, kUseFullName | kUnknownIsNumeric) |
| 349 .c_str())); |
| 350 |
| 351 if (!ExceptionBehaviorHasState(behavior)) { |
| 352 TestSimulateCrashMac test_simulate_crash_mac( |
| 353 target, behavior, THREAD_STATE_NONE); |
| 354 test_simulate_crash_mac.Run(); |
| 355 } else { |
| 356 for (size_t flavor_index = 0; |
| 357 flavor_index < arraysize(kFlavors); |
| 358 ++flavor_index) { |
| 359 thread_state_flavor_t flavor = kFlavors[flavor_index]; |
| 360 SCOPED_TRACE(base::StringPrintf( |
| 361 "flavor_index %zu, flavor %s", |
| 362 flavor_index, |
| 363 ThreadStateFlavorToString( |
| 364 flavor, kUseFullName | kUnknownIsNumeric).c_str())); |
| 365 |
| 366 TestSimulateCrashMac test_simulate_crash_mac( |
| 367 target, behavior, flavor); |
| 368 test_simulate_crash_mac.Run(); |
| 369 } |
| 370 } |
| 371 } |
| 372 } |
| 373 } |
| 374 |
| 375 } // namespace |
| 376 } // namespace test |
| 377 } // namespace crashpad |
OLD | NEW |