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 "util/mach/scoped_task_suspend.h" | |
16 | |
17 #include <mach/mach.h> | |
18 #include <stdint.h> | |
19 | |
20 #include <algorithm> | |
21 | |
22 #include "gtest/gtest.h" | |
23 #include "util/file/fd_io.h" | |
24 #include "util/mach/task_memory.h" | |
25 #include "util/misc/clock.h" | |
26 #include "util/test/mac/mach_multiprocess.h" | |
27 | |
28 namespace crashpad { | |
29 namespace test { | |
30 namespace { | |
31 | |
32 class ScopedTaskSuspendTest final : public MachMultiprocess { | |
33 public: | |
34 ScopedTaskSuspendTest() : MachMultiprocess() {} | |
35 ~ScopedTaskSuspendTest() {} | |
36 | |
37 private: | |
38 // Reads the int at |address| from the child process’ memory at |task_memory|. | |
39 static int CounterValue(TaskMemory* task_memory, mach_vm_address_t address) { | |
Robert Sesek
2014/10/13 15:01:37
Does this test really need to use TaskMemory? Coul
| |
40 int counter = 0; | |
41 EXPECT_TRUE(task_memory->Read(address, sizeof(counter), &counter)); | |
42 return counter; | |
43 } | |
44 | |
45 // Determines whether the child process is updating its counter. The counter | |
46 // is expected to be updated while the child process is running, but should | |
47 // appear frozen while the child process is suspended. | |
48 static bool CounterIsRunning( | |
49 TaskMemory* task_memory, mach_vm_address_t address) { | |
50 int initial_counter = CounterValue(task_memory, address); | |
51 | |
52 // Look for any change in the counter. If there is any change, the counter | |
53 // is running. If there is no change, it doesn’t necessarily mean that the | |
54 // child process is suspended, because it may not have been scheduled for | |
55 // another reason. Retry a few times in a loop with a delay. | |
56 for (size_t tries = 10; tries > 0; --tries) { | |
57 SleepNanoseconds(1E5); // 100 microseconds | |
58 | |
59 int counter = CounterValue(task_memory, address); | |
60 if (counter != initial_counter) { | |
61 return true; | |
62 } | |
63 } | |
64 | |
65 return false; | |
66 } | |
67 | |
68 // MachMultiprocess: | |
69 | |
70 virtual void MachMultiprocessParent() override { | |
71 int read_fd = ReadPipeFD(); | |
72 | |
73 // Get the address of the counter in the child process. | |
74 mach_vm_address_t address; | |
75 CheckedReadFD(read_fd, &address, sizeof(address)); | |
76 | |
77 task_t child_task = ChildTask(); | |
78 TaskMemory task_memory(child_task); | |
79 | |
80 // In the initial state, the child process should be scheduled, and it | |
81 // should be updating its counter. | |
82 EXPECT_TRUE(CounterIsRunning(&task_memory, address)); | |
83 | |
84 { | |
85 // Suspend the child process. | |
86 ScopedTaskSuspend suspend(child_task); | |
87 | |
88 // CounterIsRunning() just makes sure that the child process is not | |
89 // running within a short duration. Checking the counter value before and | |
90 // after the sleep ensures that the child process has not run during a | |
91 // larger period that it was expected to be suspended. | |
92 int initial_value = CounterValue(&task_memory, address); | |
93 | |
94 EXPECT_FALSE(CounterIsRunning(&task_memory, address)); | |
95 | |
96 // Sleep for a little while, giving the child process a chance to measure | |
97 // that one of its loop iterations took a long time. | |
98 SleepNanoseconds(1E6); // 1 millisecond | |
99 | |
100 EXPECT_FALSE(CounterIsRunning(&task_memory, address)); | |
101 | |
102 // Make sure that the child process hasn’t had a chance to run at all. | |
103 int final_value = CounterValue(&task_memory, address); | |
104 EXPECT_EQ(initial_value, final_value); | |
105 } | |
106 | |
107 // The suspension should no longer be placed, and the child process should | |
108 // be running again. This makes sure that when the counter was seen as | |
109 // stopped before, it’s because the child process was really suspended, and | |
110 // not because it had exited its loop. | |
111 EXPECT_TRUE(CounterIsRunning(&task_memory, address)); | |
112 | |
113 // Get the child’s idea of its final counter value, and compare that against | |
114 // this process’ idea of the same value. This tests that CounterValue() was | |
115 // in fact reading the right value from the child process. | |
116 int expect_counter; | |
117 CheckedReadFD(read_fd, &expect_counter, sizeof(expect_counter)); | |
118 | |
119 int counter = CounterValue(&task_memory, address); | |
120 EXPECT_EQ(expect_counter, counter); | |
121 } | |
122 | |
123 virtual void MachMultiprocessChild() override { | |
124 int write_fd = WritePipeFD(); | |
125 | |
126 // Tell the parent process where to find the counter in this process’ | |
127 // address space. | |
128 volatile int counter = 0; | |
129 mach_vm_address_t address = reinterpret_cast<mach_vm_address_t>(&counter); | |
130 CheckedWriteFD(write_fd, &address, sizeof(address)); | |
131 | |
132 // Loop for a while, computing the longest duration for an iteration of the | |
133 // loop. Inside each loop, update the counter, which the parent process can | |
134 // examine to see whether the loop is running from its perspective. | |
135 uint64_t start = ClockMonotonicNanoseconds(); | |
136 uint64_t deadline = start + 1E8; // 100 milliseconds | |
137 uint64_t now = start; | |
138 uint64_t last = now; | |
139 uint64_t max_delay = 0; | |
140 do { | |
141 ++counter; | |
142 uint64_t delay = now - last; | |
143 max_delay = std::max(max_delay, delay); | |
144 last = now; | |
145 } while ((now = ClockMonotonicNanoseconds()) < deadline); | |
146 | |
147 // The longest iteration of the loop should be at least as long as the time | |
148 // that the parent process had this process suspended, but because of the | |
149 // tricky nature of scheduling, just make sure that the longest iteration | |
150 // took a perceptible amount of time. | |
151 EXPECT_GE(max_delay, 1E5); // 100 microseconds | |
152 | |
153 // Tell the parent process where the counter ended up. | |
154 CheckedWriteFD(write_fd, const_cast<int*>(&counter), sizeof(counter)); | |
155 | |
156 // Wait for the parent process to finish reading this process’ memory. | |
157 CheckedReadFDAtEOF(ReadPipeFD()); | |
158 } | |
159 | |
160 DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspendTest); | |
161 }; | |
162 | |
163 TEST(ScopedTaskSuspend, ScopedTaskSuspend) { | |
164 ScopedTaskSuspendTest scoped_task_suspend_test; | |
165 scoped_task_suspend_test.Run(); | |
166 } | |
167 | |
168 } // namespace | |
169 } // namespace test | |
170 } // namespace crashpad | |
OLD | NEW |