1*ec63e07aSXin Li // Copyright 2019 Google LLC
2*ec63e07aSXin Li //
3*ec63e07aSXin Li // Licensed under the Apache License, Version 2.0 (the "License");
4*ec63e07aSXin Li // you may not use this file except in compliance with the License.
5*ec63e07aSXin Li // You may obtain a copy of the License at
6*ec63e07aSXin Li //
7*ec63e07aSXin Li // https://www.apache.org/licenses/LICENSE-2.0
8*ec63e07aSXin Li //
9*ec63e07aSXin Li // Unless required by applicable law or agreed to in writing, software
10*ec63e07aSXin Li // distributed under the License is distributed on an "AS IS" BASIS,
11*ec63e07aSXin Li // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*ec63e07aSXin Li // See the License for the specific language governing permissions and
13*ec63e07aSXin Li // limitations under the License.
14*ec63e07aSXin Li
15*ec63e07aSXin Li // Implementation of the sandbox2::StackTrace class.
16*ec63e07aSXin Li
17*ec63e07aSXin Li #include "sandboxed_api/sandbox2/stack_trace.h"
18*ec63e07aSXin Li
19*ec63e07aSXin Li #include <fcntl.h>
20*ec63e07aSXin Li #include <sys/stat.h>
21*ec63e07aSXin Li #include <syscall.h>
22*ec63e07aSXin Li #include <unistd.h>
23*ec63e07aSXin Li
24*ec63e07aSXin Li #include <memory>
25*ec63e07aSXin Li #include <string>
26*ec63e07aSXin Li #include <utility>
27*ec63e07aSXin Li #include <vector>
28*ec63e07aSXin Li
29*ec63e07aSXin Li #include "absl/cleanup/cleanup.h"
30*ec63e07aSXin Li #include "absl/flags/flag.h"
31*ec63e07aSXin Li #include "absl/log/check.h"
32*ec63e07aSXin Li #include "absl/log/log.h"
33*ec63e07aSXin Li #include "absl/memory/memory.h"
34*ec63e07aSXin Li #include "absl/status/status.h"
35*ec63e07aSXin Li #include "absl/status/statusor.h"
36*ec63e07aSXin Li #include "absl/strings/str_cat.h"
37*ec63e07aSXin Li #include "absl/strings/string_view.h"
38*ec63e07aSXin Li #include "absl/strings/strip.h"
39*ec63e07aSXin Li #include "absl/time/time.h"
40*ec63e07aSXin Li #include "sandboxed_api/config.h"
41*ec63e07aSXin Li #include "sandboxed_api/sandbox2/comms.h"
42*ec63e07aSXin Li #include "sandboxed_api/sandbox2/executor.h"
43*ec63e07aSXin Li #include "sandboxed_api/sandbox2/limits.h"
44*ec63e07aSXin Li #include "sandboxed_api/sandbox2/mounts.h"
45*ec63e07aSXin Li #include "sandboxed_api/sandbox2/namespace.h"
46*ec63e07aSXin Li #include "sandboxed_api/sandbox2/policy.h"
47*ec63e07aSXin Li #include "sandboxed_api/sandbox2/policybuilder.h"
48*ec63e07aSXin Li #include "sandboxed_api/sandbox2/regs.h"
49*ec63e07aSXin Li #include "sandboxed_api/sandbox2/result.h"
50*ec63e07aSXin Li #include "sandboxed_api/sandbox2/unwind/unwind.h"
51*ec63e07aSXin Li #include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
52*ec63e07aSXin Li #include "sandboxed_api/util/fileops.h"
53*ec63e07aSXin Li #include "sandboxed_api/util/path.h"
54*ec63e07aSXin Li #include "sandboxed_api/util/raw_logging.h"
55*ec63e07aSXin Li #include "sandboxed_api/util/status_macros.h"
56*ec63e07aSXin Li
57*ec63e07aSXin Li ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false,
58*ec63e07aSXin Li "Completely disable stack trace collection for sandboxees");
59*ec63e07aSXin Li
60*ec63e07aSXin Li ABSL_FLAG(bool, sandbox_libunwind_crash_handler, true,
61*ec63e07aSXin Li "Sandbox libunwind when handling violations (preferred)");
62*ec63e07aSXin Li
63*ec63e07aSXin Li namespace sandbox2 {
64*ec63e07aSXin Li namespace {
65*ec63e07aSXin Li
66*ec63e07aSXin Li namespace file = ::sapi::file;
67*ec63e07aSXin Li namespace file_util = ::sapi::file_util;
68*ec63e07aSXin Li
69*ec63e07aSXin Li // Similar to GetStackTrace() but without using the sandbox to isolate
70*ec63e07aSXin Li // libunwind.
UnsafeGetStackTrace(pid_t pid)71*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> UnsafeGetStackTrace(pid_t pid) {
72*ec63e07aSXin Li LOG(WARNING) << "Using non-sandboxed libunwind";
73*ec63e07aSXin Li return RunLibUnwindAndSymbolizer(pid, kDefaultMaxFrames);
74*ec63e07aSXin Li }
75*ec63e07aSXin Li
IsSameFile(const std::string & path,const std::string & other)76*ec63e07aSXin Li bool IsSameFile(const std::string& path, const std::string& other) {
77*ec63e07aSXin Li struct stat buf, other_buf;
78*ec63e07aSXin Li if (stat(path.c_str(), &buf) != 0 || stat(other.c_str(), &other_buf) != 0) {
79*ec63e07aSXin Li return false;
80*ec63e07aSXin Li }
81*ec63e07aSXin Li return buf.st_dev == other_buf.st_dev && buf.st_ino == other_buf.st_ino &&
82*ec63e07aSXin Li buf.st_mode == other_buf.st_mode &&
83*ec63e07aSXin Li buf.st_nlink == other_buf.st_nlink && buf.st_uid == other_buf.st_uid &&
84*ec63e07aSXin Li buf.st_gid == other_buf.st_gid && buf.st_rdev == other_buf.st_rdev &&
85*ec63e07aSXin Li buf.st_size == other_buf.st_size &&
86*ec63e07aSXin Li buf.st_blksize == other_buf.st_blksize &&
87*ec63e07aSXin Li buf.st_blocks == other_buf.st_blocks;
88*ec63e07aSXin Li }
89*ec63e07aSXin Li
90*ec63e07aSXin Li } // namespace
91*ec63e07aSXin Li
92*ec63e07aSXin Li class StackTracePeer {
93*ec63e07aSXin Li public:
94*ec63e07aSXin Li static absl::StatusOr<std::unique_ptr<Policy>> GetPolicy(
95*ec63e07aSXin Li pid_t target_pid, const std::string& maps_file,
96*ec63e07aSXin Li const std::string& app_path, const std::string& exe_path,
97*ec63e07aSXin Li const Namespace* ns, bool uses_custom_forkserver);
98*ec63e07aSXin Li
99*ec63e07aSXin Li static absl::StatusOr<std::vector<std::string>> LaunchLibunwindSandbox(
100*ec63e07aSXin Li const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
101*ec63e07aSXin Li int recursion_depth);
102*ec63e07aSXin Li };
103*ec63e07aSXin Li
GetPolicy(pid_t target_pid,const std::string & maps_file,const std::string & app_path,const std::string & exe_path,const Namespace * ns,bool uses_custom_forkserver)104*ec63e07aSXin Li absl::StatusOr<std::unique_ptr<Policy>> StackTracePeer::GetPolicy(
105*ec63e07aSXin Li pid_t target_pid, const std::string& maps_file, const std::string& app_path,
106*ec63e07aSXin Li const std::string& exe_path, const Namespace* ns,
107*ec63e07aSXin Li bool uses_custom_forkserver) {
108*ec63e07aSXin Li PolicyBuilder builder;
109*ec63e07aSXin Li if (uses_custom_forkserver) {
110*ec63e07aSXin Li // Custom forkserver just forks, the binary is loaded outside of the
111*ec63e07aSXin Li // sandboxee's mount namespace.
112*ec63e07aSXin Li // Add all possible libraries without the need of parsing the binary
113*ec63e07aSXin Li // or /proc/pid/maps.
114*ec63e07aSXin Li for (const auto& library_path : {
115*ec63e07aSXin Li "/usr/lib64",
116*ec63e07aSXin Li "/usr/lib",
117*ec63e07aSXin Li "/lib64",
118*ec63e07aSXin Li "/lib",
119*ec63e07aSXin Li }) {
120*ec63e07aSXin Li if (access(library_path, F_OK) != -1) {
121*ec63e07aSXin Li VLOG(1) << "Adding library folder '" << library_path << "'";
122*ec63e07aSXin Li builder.AddDirectory(library_path);
123*ec63e07aSXin Li } else {
124*ec63e07aSXin Li VLOG(1) << "Could not add library folder '" << library_path
125*ec63e07aSXin Li << "' as it does not exist";
126*ec63e07aSXin Li }
127*ec63e07aSXin Li }
128*ec63e07aSXin Li } else {
129*ec63e07aSXin Li // Use the mounttree of the original executable.
130*ec63e07aSXin Li CHECK(ns != nullptr);
131*ec63e07aSXin Li Mounts mounts = ns->mounts();
132*ec63e07aSXin Li mounts.Remove("/proc").IgnoreError();
133*ec63e07aSXin Li mounts.Remove(app_path).IgnoreError();
134*ec63e07aSXin Li builder.SetMounts(std::move(mounts));
135*ec63e07aSXin Li }
136*ec63e07aSXin Li builder.AllowOpen()
137*ec63e07aSXin Li .AllowRead()
138*ec63e07aSXin Li .AllowWrite()
139*ec63e07aSXin Li .AllowSyscall(__NR_close)
140*ec63e07aSXin Li .AllowExit()
141*ec63e07aSXin Li .AllowHandleSignals()
142*ec63e07aSXin Li .AllowTcMalloc()
143*ec63e07aSXin Li .AllowSystemMalloc()
144*ec63e07aSXin Li // for Comms:RecvFD
145*ec63e07aSXin Li .AllowSyscall(__NR_recvmsg)
146*ec63e07aSXin Li
147*ec63e07aSXin Li // libunwind
148*ec63e07aSXin Li .AllowMmapWithoutExec()
149*ec63e07aSXin Li .AllowStat()
150*ec63e07aSXin Li .AllowSyscall(__NR_lseek)
151*ec63e07aSXin Li #ifdef __NR__llseek
152*ec63e07aSXin Li .AllowSyscall(__NR__llseek) // Newer glibc on PPC
153*ec63e07aSXin Li #endif
154*ec63e07aSXin Li .AllowSyscall(__NR_mincore)
155*ec63e07aSXin Li .AllowSyscall(__NR_munmap)
156*ec63e07aSXin Li .AllowPipe()
157*ec63e07aSXin Li
158*ec63e07aSXin Li // Symbolizer
159*ec63e07aSXin Li .AllowSyscall(__NR_brk)
160*ec63e07aSXin Li .AllowTime()
161*ec63e07aSXin Li
162*ec63e07aSXin Li // Other
163*ec63e07aSXin Li .AllowDup()
164*ec63e07aSXin Li .AllowSafeFcntl()
165*ec63e07aSXin Li .AllowGetPIDs()
166*ec63e07aSXin Li
167*ec63e07aSXin Li // Required for our ptrace replacement.
168*ec63e07aSXin Li .TrapPtrace()
169*ec63e07aSXin Li
170*ec63e07aSXin Li // Add proc maps.
171*ec63e07aSXin Li .AddFileAt(maps_file,
172*ec63e07aSXin Li file::JoinPath("/proc", absl::StrCat(target_pid), "maps"))
173*ec63e07aSXin Li .AddFileAt(maps_file,
174*ec63e07aSXin Li file::JoinPath("/proc", absl::StrCat(target_pid), "task",
175*ec63e07aSXin Li absl::StrCat(target_pid), "maps"))
176*ec63e07aSXin Li
177*ec63e07aSXin Li // Add the binary itself.
178*ec63e07aSXin Li .AddFileAt(exe_path, app_path)
179*ec63e07aSXin Li .AllowLlvmCoverage();
180*ec63e07aSXin Li
181*ec63e07aSXin Li return builder.TryBuild();
182*ec63e07aSXin Li }
183*ec63e07aSXin Li
184*ec63e07aSXin Li namespace internal {
185*ec63e07aSXin Li SandboxPeer::SpawnFn SandboxPeer::spawn_fn_ = nullptr;
186*ec63e07aSXin Li } // namespace internal
187*ec63e07aSXin Li
LaunchLibunwindSandbox(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)188*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> StackTracePeer::LaunchLibunwindSandbox(
189*ec63e07aSXin Li const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
190*ec63e07aSXin Li int recursion_depth) {
191*ec63e07aSXin Li const pid_t pid = regs->pid();
192*ec63e07aSXin Li
193*ec63e07aSXin Li sapi::file_util::fileops::FDCloser memory_fd(
194*ec63e07aSXin Li open(absl::StrCat("/proc/", pid, "/mem").c_str(), O_RDONLY));
195*ec63e07aSXin Li if (memory_fd.get() == -1) {
196*ec63e07aSXin Li return absl::InternalError("Opening sandboxee process memory failed");
197*ec63e07aSXin Li }
198*ec63e07aSXin Li // Tell executor to use this special internal mode. Using `new` to access a
199*ec63e07aSXin Li // non-public constructor.
200*ec63e07aSXin Li auto executor = absl::WrapUnique(new Executor(pid, recursion_depth));
201*ec63e07aSXin Li
202*ec63e07aSXin Li executor->limits()->set_rlimit_cpu(10).set_walltime_limit(absl::Seconds(5));
203*ec63e07aSXin Li
204*ec63e07aSXin Li // Temporary directory used to provide files from /proc to the unwind sandbox.
205*ec63e07aSXin Li char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX";
206*ec63e07aSXin Li char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template);
207*ec63e07aSXin Li if (!unwind_temp_directory) {
208*ec63e07aSXin Li return absl::InternalError(
209*ec63e07aSXin Li "Could not create temporary directory for unwinding");
210*ec63e07aSXin Li }
211*ec63e07aSXin Li struct UnwindTempDirectoryCleanup {
212*ec63e07aSXin Li ~UnwindTempDirectoryCleanup() {
213*ec63e07aSXin Li file_util::fileops::DeleteRecursively(capture);
214*ec63e07aSXin Li }
215*ec63e07aSXin Li char* capture;
216*ec63e07aSXin Li } cleanup{unwind_temp_directory};
217*ec63e07aSXin Li
218*ec63e07aSXin Li // Copy over important files from the /proc directory as we can't mount them.
219*ec63e07aSXin Li const std::string unwind_temp_maps_path =
220*ec63e07aSXin Li file::JoinPath(unwind_temp_directory, "maps");
221*ec63e07aSXin Li
222*ec63e07aSXin Li if (!file_util::fileops::CopyFile(
223*ec63e07aSXin Li file::JoinPath("/proc", absl::StrCat(pid), "maps"),
224*ec63e07aSXin Li unwind_temp_maps_path, 0400)) {
225*ec63e07aSXin Li return absl::InternalError("Could not copy maps file");
226*ec63e07aSXin Li }
227*ec63e07aSXin Li
228*ec63e07aSXin Li // Get path to the binary.
229*ec63e07aSXin Li // app_path contains the path like it is also in /proc/pid/maps. It is
230*ec63e07aSXin Li // relative to the sandboxee's mount namespace. If it is not existing
231*ec63e07aSXin Li // (anymore) it will have a ' (deleted)' suffix.
232*ec63e07aSXin Li std::string app_path;
233*ec63e07aSXin Li std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe");
234*ec63e07aSXin Li if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) {
235*ec63e07aSXin Li return absl::InternalError("Could not obtain absolute path to the binary");
236*ec63e07aSXin Li }
237*ec63e07aSXin Li
238*ec63e07aSXin Li std::string exe_path;
239*ec63e07aSXin Li if (IsSameFile(app_path, proc_pid_exe)) {
240*ec63e07aSXin Li exe_path = app_path;
241*ec63e07aSXin Li } else {
242*ec63e07aSXin Li // The exe_path will have a mountable path of the application, even if it
243*ec63e07aSXin Li // was removed. Resolve app_path backing file.
244*ec63e07aSXin Li exe_path = ns ? ns->mounts().ResolvePath(app_path).value_or("") : "";
245*ec63e07aSXin Li }
246*ec63e07aSXin Li
247*ec63e07aSXin Li if (exe_path.empty()) {
248*ec63e07aSXin Li // File was probably removed.
249*ec63e07aSXin Li LOG(WARNING) << "File was removed, using /proc/pid/exe.";
250*ec63e07aSXin Li app_path = std::string(absl::StripSuffix(app_path, " (deleted)"));
251*ec63e07aSXin Li // Create a copy of /proc/pid/exe, mount that one.
252*ec63e07aSXin Li exe_path = file::JoinPath(unwind_temp_directory, "exe");
253*ec63e07aSXin Li if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) {
254*ec63e07aSXin Li return absl::InternalError("Could not copy /proc/pid/exe");
255*ec63e07aSXin Li }
256*ec63e07aSXin Li }
257*ec63e07aSXin Li
258*ec63e07aSXin Li VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path;
259*ec63e07aSXin Li
260*ec63e07aSXin Li // Add mappings for the binary (as they might not have been added due to the
261*ec63e07aSXin Li // forkserver).
262*ec63e07aSXin Li SAPI_ASSIGN_OR_RETURN(
263*ec63e07aSXin Li std::unique_ptr<Policy> policy,
264*ec63e07aSXin Li StackTracePeer::GetPolicy(pid, unwind_temp_maps_path, app_path, exe_path,
265*ec63e07aSXin Li ns, uses_custom_forkserver));
266*ec63e07aSXin Li
267*ec63e07aSXin Li VLOG(1) << "Running libunwind sandbox";
268*ec63e07aSXin Li auto sandbox =
269*ec63e07aSXin Li internal::SandboxPeer::Spawn(std::move(executor), std::move(policy));
270*ec63e07aSXin Li Comms* comms = sandbox->comms();
271*ec63e07aSXin Li
272*ec63e07aSXin Li UnwindSetup msg;
273*ec63e07aSXin Li msg.set_pid(pid);
274*ec63e07aSXin Li msg.set_regs(reinterpret_cast<const char*>(®s->user_regs_),
275*ec63e07aSXin Li sizeof(regs->user_regs_));
276*ec63e07aSXin Li msg.set_default_max_frames(kDefaultMaxFrames);
277*ec63e07aSXin Li
278*ec63e07aSXin Li absl::Cleanup kill_sandbox = [&sandbox]() {
279*ec63e07aSXin Li sandbox->Kill();
280*ec63e07aSXin Li sandbox2::Result result = sandbox->AwaitResult();
281*ec63e07aSXin Li LOG(INFO) << "Libunwind execution status: " << result.ToString();
282*ec63e07aSXin Li };
283*ec63e07aSXin Li
284*ec63e07aSXin Li if (!comms->SendProtoBuf(msg)) {
285*ec63e07aSXin Li return absl::InternalError("Sending libunwind setup message failed");
286*ec63e07aSXin Li }
287*ec63e07aSXin Li if (!comms->SendFD(memory_fd.get())) {
288*ec63e07aSXin Li return absl::InternalError("Sending sandboxee's memory fd failed");
289*ec63e07aSXin Li }
290*ec63e07aSXin Li absl::Status status;
291*ec63e07aSXin Li if (!comms->RecvStatus(&status)) {
292*ec63e07aSXin Li return absl::InternalError(
293*ec63e07aSXin Li "Receiving status from libunwind sandbox failed");
294*ec63e07aSXin Li }
295*ec63e07aSXin Li SAPI_RETURN_IF_ERROR(status);
296*ec63e07aSXin Li
297*ec63e07aSXin Li UnwindResult result;
298*ec63e07aSXin Li if (!comms->RecvProtoBuf(&result)) {
299*ec63e07aSXin Li return absl::InternalError("Receiving libunwind result failed");
300*ec63e07aSXin Li }
301*ec63e07aSXin Li
302*ec63e07aSXin Li std::move(kill_sandbox).Cancel();
303*ec63e07aSXin Li
304*ec63e07aSXin Li auto sandbox_result = sandbox->AwaitResult();
305*ec63e07aSXin Li
306*ec63e07aSXin Li LOG(INFO) << "Libunwind execution status: " << sandbox_result.ToString();
307*ec63e07aSXin Li
308*ec63e07aSXin Li if (sandbox_result.final_status() != Result::OK) {
309*ec63e07aSXin Li return absl::InternalError(
310*ec63e07aSXin Li absl::StrCat("libunwind sandbox did not finish properly: ",
311*ec63e07aSXin Li sandbox_result.ToString()));
312*ec63e07aSXin Li }
313*ec63e07aSXin Li
314*ec63e07aSXin Li return std::vector<std::string>(result.stacktrace().begin(),
315*ec63e07aSXin Li result.stacktrace().end());
316*ec63e07aSXin Li }
317*ec63e07aSXin Li
GetStackTrace(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)318*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> GetStackTrace(
319*ec63e07aSXin Li const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
320*ec63e07aSXin Li int recursion_depth) {
321*ec63e07aSXin Li if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) {
322*ec63e07aSXin Li return absl::UnavailableError("Stacktraces disabled");
323*ec63e07aSXin Li }
324*ec63e07aSXin Li if (!regs) {
325*ec63e07aSXin Li return absl::InvalidArgumentError(
326*ec63e07aSXin Li "Could not obtain stacktrace, regs == nullptr");
327*ec63e07aSXin Li }
328*ec63e07aSXin Li
329*ec63e07aSXin Li if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) {
330*ec63e07aSXin Li return UnsafeGetStackTrace(regs->pid());
331*ec63e07aSXin Li }
332*ec63e07aSXin Li
333*ec63e07aSXin Li // Show a warning if sandboxed libunwind is requested but we're running in
334*ec63e07aSXin Li // a sanitizer build (= we can't use sandboxed libunwind).
335*ec63e07aSXin Li if (sapi::sanitizers::IsAny()) {
336*ec63e07aSXin Li LOG(WARNING) << "Sanitizer build, using non-sandboxed libunwind";
337*ec63e07aSXin Li return UnsafeGetStackTrace(regs->pid());
338*ec63e07aSXin Li }
339*ec63e07aSXin Li
340*ec63e07aSXin Li return StackTracePeer::LaunchLibunwindSandbox(
341*ec63e07aSXin Li regs, ns, uses_custom_forkserver, recursion_depth);
342*ec63e07aSXin Li }
343*ec63e07aSXin Li
CompactStackTrace(const std::vector<std::string> & stack_trace)344*ec63e07aSXin Li std::vector<std::string> CompactStackTrace(
345*ec63e07aSXin Li const std::vector<std::string>& stack_trace) {
346*ec63e07aSXin Li std::vector<std::string> compact_trace;
347*ec63e07aSXin Li compact_trace.reserve(stack_trace.size() / 2);
348*ec63e07aSXin Li const std::string* prev = nullptr;
349*ec63e07aSXin Li int seen = 0;
350*ec63e07aSXin Li auto add_repeats = [&compact_trace](int seen) {
351*ec63e07aSXin Li if (seen != 0) {
352*ec63e07aSXin Li compact_trace.push_back(
353*ec63e07aSXin Li absl::StrCat("(previous frame repeated ", seen, " times)"));
354*ec63e07aSXin Li }
355*ec63e07aSXin Li };
356*ec63e07aSXin Li for (const auto& frame : stack_trace) {
357*ec63e07aSXin Li if (prev && frame == *prev) {
358*ec63e07aSXin Li ++seen;
359*ec63e07aSXin Li } else {
360*ec63e07aSXin Li prev = &frame;
361*ec63e07aSXin Li add_repeats(seen);
362*ec63e07aSXin Li seen = 0;
363*ec63e07aSXin Li compact_trace.push_back(frame);
364*ec63e07aSXin Li }
365*ec63e07aSXin Li }
366*ec63e07aSXin Li add_repeats(seen);
367*ec63e07aSXin Li return compact_trace;
368*ec63e07aSXin Li }
369*ec63e07aSXin Li
370*ec63e07aSXin Li } // namespace sandbox2
371