1 // Copyright 2009 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 // linux_ptrace_dumper_unittest.cc:
30 // Unit tests for google_breakpad::LinuxPtraceDumper.
31 //
32 // This file was renamed from linux_dumper_unittest.cc and modified due
33 // to LinuxDumper being splitted into two classes.
34 
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>  // Must come first
37 #endif
38 
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <poll.h>
43 #include <unistd.h>
44 #include <signal.h>
45 #include <stdint.h>
46 #include <string.h>
47 #include <sys/mman.h>
48 #include <sys/prctl.h>
49 #include <sys/stat.h>
50 #include <sys/types.h>
51 
52 #include <string>
53 
54 #include "breakpad_googletest_includes.h"
55 #include "client/linux/minidump_writer/linux_ptrace_dumper.h"
56 #include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
57 #include "common/linux/eintr_wrapper.h"
58 #include "common/linux/file_id.h"
59 #include "common/linux/ignore_ret.h"
60 #include "common/linux/safe_readlink.h"
61 #include "common/memory_allocator.h"
62 #include "common/using_std_string.h"
63 
64 #ifndef PR_SET_PTRACER
65 #define PR_SET_PTRACER 0x59616d61
66 #endif
67 
68 using namespace google_breakpad;
69 using google_breakpad::elf::FileID;
70 using google_breakpad::elf::kDefaultBuildIdSize;
71 
72 namespace {
73 
SetupChildProcess(int number_of_threads)74 pid_t SetupChildProcess(int number_of_threads) {
75   char kNumberOfThreadsArgument[2];
76   sprintf(kNumberOfThreadsArgument, "%d", number_of_threads);
77 
78   int fds[2];
79   EXPECT_NE(-1, pipe(fds));
80 
81   pid_t child_pid = fork();
82   if (child_pid == 0) {
83     // In child process.
84     close(fds[0]);
85 
86     string helper_path(GetHelperBinary());
87     if (helper_path.empty()) {
88       fprintf(stderr, "Couldn't find helper binary\n");
89       _exit(1);
90     }
91 
92     // Pass the pipe fd and the number of threads as arguments.
93     char pipe_fd_string[8];
94     sprintf(pipe_fd_string, "%d", fds[1]);
95     execl(helper_path.c_str(),
96           "linux_dumper_unittest_helper",
97           pipe_fd_string,
98           kNumberOfThreadsArgument,
99           NULL);
100     // Kill if we get here.
101     printf("Errno from exec: %d", errno);
102     std::string err_str = "Exec of  " + helper_path + " failed";
103     perror(err_str.c_str());
104     _exit(1);
105   }
106   close(fds[1]);
107 
108   // Wait for all child threads to indicate that they have started
109   for (int threads = 0; threads < number_of_threads; threads++) {
110     struct pollfd pfd;
111     memset(&pfd, 0, sizeof(pfd));
112     pfd.fd = fds[0];
113     pfd.events = POLLIN | POLLERR;
114 
115     const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
116     EXPECT_EQ(1, r);
117     EXPECT_TRUE(pfd.revents & POLLIN);
118     uint8_t junk;
119     EXPECT_EQ(read(fds[0], &junk, sizeof(junk)),
120               static_cast<ssize_t>(sizeof(junk)));
121   }
122   close(fds[0]);
123 
124   // There is a race here because we may stop a child thread before
125   // it is actually running the busy loop. Empirically this sleep
126   // is sufficient to avoid the race.
127   usleep(100000);
128   return child_pid;
129 }
130 
131 typedef wasteful_vector<uint8_t> id_vector;
132 typedef testing::Test LinuxPtraceDumperTest;
133 
134 /* Fixture for running tests in a child process. */
135 class LinuxPtraceDumperChildTest : public testing::Test {
136  protected:
SetUp()137   virtual void SetUp() {
138     child_pid_ = fork();
139 #ifndef __ANDROID__
140     prctl(PR_SET_PTRACER, child_pid_);
141 #endif
142   }
143 
144   /* Gtest is calling TestBody from this class, which sets up a child
145    * process in which the RealTestBody virtual member is called.
146    * As such, TestBody is not supposed to be overridden in derived classes.
147    */
TestBody()148   virtual void TestBody() /* final */ {
149     if (child_pid_ == 0) {
150       // child process
151       RealTestBody();
152       _exit(HasFatalFailure() ? kFatalFailure :
153             (HasNonfatalFailure() ? kNonFatalFailure : 0));
154     }
155 
156     ASSERT_TRUE(child_pid_ > 0);
157     int status;
158     waitpid(child_pid_, &status, 0);
159     if (WEXITSTATUS(status) == kFatalFailure) {
160       GTEST_FATAL_FAILURE_("Test failed in child process");
161     } else if (WEXITSTATUS(status) == kNonFatalFailure) {
162       GTEST_NONFATAL_FAILURE_("Test failed in child process");
163     }
164   }
165 
166   /* Gtest defines TestBody functions through its macros, but classes
167    * derived from this one need to define RealTestBody instead.
168    * This is achieved by defining a TestBody macro further below.
169    */
170   virtual void RealTestBody() = 0;
171 
make_vector()172   id_vector make_vector() {
173     return id_vector(&allocator, kDefaultBuildIdSize);
174   }
175 
176  private:
177   static const int kFatalFailure = 1;
178   static const int kNonFatalFailure = 2;
179 
180   pid_t child_pid_;
181   PageAllocator allocator;
182 };
183 
184 }  // namespace
185 
186 /* Replace TestBody declarations within TEST*() with RealTestBody
187  * declarations */
188 #define TestBody RealTestBody
189 
TEST_F(LinuxPtraceDumperChildTest,Setup)190 TEST_F(LinuxPtraceDumperChildTest, Setup) {
191   LinuxPtraceDumper dumper(getppid());
192 }
193 
TEST_F(LinuxPtraceDumperChildTest,FindMappings)194 TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
195   LinuxPtraceDumper dumper(getppid());
196   ASSERT_TRUE(dumper.Init());
197 
198   ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
199   ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
200   ASSERT_FALSE(dumper.FindMapping(NULL));
201 }
202 
TEST_F(LinuxPtraceDumperChildTest,ThreadList)203 TEST_F(LinuxPtraceDumperChildTest, ThreadList) {
204   LinuxPtraceDumper dumper(getppid());
205   ASSERT_TRUE(dumper.Init());
206 
207   ASSERT_GE(dumper.threads().size(), (size_t)1);
208   bool found = false;
209   for (size_t i = 0; i < dumper.threads().size(); ++i) {
210     if (dumper.threads()[i] == getppid()) {
211       ASSERT_FALSE(found);
212       found = true;
213     }
214   }
215   ASSERT_TRUE(found);
216 }
217 
218 // Helper stack class to close a file descriptor and unmap
219 // a mmap'ed mapping.
220 class StackHelper {
221  public:
StackHelper()222   StackHelper()
223     : fd_(-1), mapping_(NULL), size_(0) {}
~StackHelper()224   ~StackHelper() {
225     if (size_)
226       munmap(mapping_, size_);
227     if (fd_ >= 0)
228       close(fd_);
229   }
Init(int fd,char * mapping,size_t size)230   void Init(int fd, char* mapping, size_t size) {
231     fd_ = fd;
232     mapping_ = mapping;
233     size_ = size;
234   }
235 
mapping() const236   char* mapping() const { return mapping_; }
size() const237   size_t size() const { return size_; }
238 
239  private:
240   int fd_;
241   char* mapping_;
242   size_t size_;
243 };
244 
245 class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
246  protected:
247   virtual void SetUp();
248 
249   string helper_path_;
250   size_t page_size_;
251   StackHelper helper_;
252 };
253 
SetUp()254 void LinuxPtraceDumperMappingsTest::SetUp() {
255   helper_path_ = GetHelperBinary();
256   if (helper_path_.empty()) {
257     FAIL() << "Couldn't find helper binary";
258     _exit(1);
259   }
260 
261   // mmap two segments out of the helper binary, one
262   // enclosed in the other, but with different protections.
263   page_size_ = sysconf(_SC_PAGESIZE);
264   const size_t kMappingSize = 3 * page_size_;
265   int fd = open(helper_path_.c_str(), O_RDONLY);
266   ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
267                     << ", Error: " << strerror(errno);
268   char* mapping =
269     reinterpret_cast<char*>(mmap(NULL,
270                                  kMappingSize,
271                                  PROT_READ,
272                                  MAP_SHARED,
273                                  fd,
274                                  0));
275   ASSERT_TRUE(mapping);
276 
277   // Ensure that things get cleaned up.
278   helper_.Init(fd, mapping, kMappingSize);
279 
280   // Carve a page out of the first mapping with different permissions.
281   char* inside_mapping =  reinterpret_cast<char*>(
282       mmap(mapping + 2 * page_size_,
283            page_size_,
284            PROT_NONE,
285            MAP_SHARED | MAP_FIXED,
286            fd,
287            // Map a different offset just to
288            // better test real-world conditions.
289            page_size_));
290   ASSERT_TRUE(inside_mapping);
291 
292   LinuxPtraceDumperChildTest::SetUp();
293 }
294 
TEST_F(LinuxPtraceDumperMappingsTest,MergedMappings)295 TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
296   // Now check that LinuxPtraceDumper interpreted the mappings properly.
297   LinuxPtraceDumper dumper(getppid());
298   ASSERT_TRUE(dumper.Init());
299   int mapping_count = 0;
300   for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
301     const MappingInfo& mapping = *dumper.mappings()[i];
302     if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) {
303       // This mapping should encompass the entire original mapped
304       // range.
305       EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
306                 mapping.start_addr);
307       EXPECT_EQ(this->helper_.size(), mapping.size);
308       EXPECT_EQ(0U, mapping.offset);
309       mapping_count++;
310     }
311   }
312   EXPECT_EQ(1, mapping_count);
313 }
314 
TEST_F(LinuxPtraceDumperChildTest,BuildProcPath)315 TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
316   const pid_t pid = getppid();
317   LinuxPtraceDumper dumper(pid);
318 
319   char maps_path[NAME_MAX] = "";
320   char maps_path_expected[NAME_MAX];
321   snprintf(maps_path_expected, sizeof(maps_path_expected),
322            "/proc/%d/maps", pid);
323   EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
324   EXPECT_STREQ(maps_path_expected, maps_path);
325 
326   EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
327   EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
328   EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
329   EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
330 
331   char long_node[NAME_MAX];
332   size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
333   memset(long_node, 'a', long_node_len);
334   long_node[long_node_len] = '\0';
335   EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
336 }
337 
338 #if !defined(__ARM_EABI__) && !defined(__mips__)
339 // Ensure that the linux-gate VDSO is included in the mapping list.
TEST_F(LinuxPtraceDumperChildTest,MappingsIncludeLinuxGate)340 TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
341   LinuxPtraceDumper dumper(getppid());
342   ASSERT_TRUE(dumper.Init());
343 
344   void* linux_gate_loc =
345     reinterpret_cast<void*>(dumper.auxv()[AT_SYSINFO_EHDR]);
346   ASSERT_TRUE(linux_gate_loc);
347   bool found_linux_gate = false;
348 
349   const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
350   const MappingInfo* mapping;
351   for (unsigned i = 0; i < mappings.size(); ++i) {
352     mapping = mappings[i];
353     if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
354       found_linux_gate = true;
355       break;
356     }
357   }
358   EXPECT_TRUE(found_linux_gate);
359   EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
360   EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
361 }
362 
363 // Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
TEST_F(LinuxPtraceDumperChildTest,LinuxGateMappingID)364 TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
365   LinuxPtraceDumper dumper(getppid());
366   ASSERT_TRUE(dumper.Init());
367 
368   bool found_linux_gate = false;
369   const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
370   unsigned index = 0;
371   for (unsigned i = 0; i < mappings.size(); ++i) {
372     if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
373       found_linux_gate = true;
374       index = i;
375       break;
376     }
377   }
378   ASSERT_TRUE(found_linux_gate);
379 
380   // Need to suspend the child so ptrace actually works.
381   ASSERT_TRUE(dumper.ThreadsSuspend());
382   id_vector identifier(make_vector());
383   ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
384                                                  true,
385                                                  index,
386                                                  identifier));
387 
388   id_vector empty_identifier(make_vector());
389   empty_identifier.resize(kDefaultBuildIdSize, 0);
390   EXPECT_NE(empty_identifier, identifier);
391   EXPECT_TRUE(dumper.ThreadsResume());
392 }
393 #endif
394 
TEST_F(LinuxPtraceDumperChildTest,FileIDsMatch)395 TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
396   // Calculate the File ID of our binary using both
397   // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
398   // and ensure that we get the same result from both.
399   char exe_name[PATH_MAX];
400   ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
401 
402   LinuxPtraceDumper dumper(getppid());
403   ASSERT_TRUE(dumper.Init());
404   const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
405   bool found_exe = false;
406   unsigned i;
407   for (i = 0; i < mappings.size(); ++i) {
408     const MappingInfo* mapping = mappings[i];
409     if (!strcmp(mapping->name, exe_name)) {
410       found_exe = true;
411       break;
412     }
413   }
414   ASSERT_TRUE(found_exe);
415 
416   id_vector identifier1(make_vector());
417   id_vector identifier2(make_vector());
418   EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
419                                                  identifier1));
420   FileID fileid(exe_name);
421   EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
422 
423   string identifier_string1 =
424       FileID::ConvertIdentifierToUUIDString(identifier1);
425   string identifier_string2 =
426       FileID::ConvertIdentifierToUUIDString(identifier2);
427   EXPECT_EQ(identifier_string1, identifier_string2);
428 }
429 
430 /* Get back to normal behavior of TEST*() macros wrt TestBody. */
431 #undef TestBody
432 
TEST(LinuxPtraceDumperTest,VerifyStackReadWithMultipleThreads)433 TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
434   static const size_t kNumberOfThreadsInHelperProgram = 5;
435 
436   pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
437   ASSERT_NE(child_pid, -1);
438 
439   // Children are ready now.
440   LinuxPtraceDumper dumper(child_pid);
441   ASSERT_TRUE(dumper.Init());
442 #if defined(THREAD_SANITIZER)
443   EXPECT_GE(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
444 #else
445   EXPECT_EQ(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
446 #endif
447   EXPECT_TRUE(dumper.ThreadsSuspend());
448 
449   ThreadInfo one_thread;
450   size_t matching_threads = 0;
451   for (size_t i = 0; i < dumper.threads().size(); ++i) {
452     EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
453     const void* stack;
454     size_t stack_len;
455     EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
456         one_thread.stack_pointer));
457     // In the helper program, we stored a pointer to the thread id in a
458     // specific register. Check that we can recover its value.
459 #if defined(__ARM_EABI__)
460     pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]);
461 #elif defined(__aarch64__)
462     pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[3]);
463 #elif defined(__i386)
464     pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx);
465 #elif defined(__x86_64)
466     pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx);
467 #elif defined(__mips__)
468     pid_t* process_tid_location =
469         reinterpret_cast<pid_t*>(one_thread.mcontext.gregs[1]);
470 #elif defined(__riscv)
471     pid_t* process_tid_location =
472         reinterpret_cast<pid_t*>(one_thread.mcontext.__gregs[4]);
473 #else
474 #error This test has not been ported to this platform.
475 #endif
476     pid_t one_thread_id;
477     dumper.CopyFromProcess(&one_thread_id,
478                            dumper.threads()[i],
479                            process_tid_location,
480                            4);
481     matching_threads += (dumper.threads()[i] == one_thread_id) ? 1 : 0;
482   }
483   EXPECT_EQ(matching_threads, kNumberOfThreadsInHelperProgram);
484   EXPECT_TRUE(dumper.ThreadsResume());
485   kill(child_pid, SIGKILL);
486 
487   // Reap child
488   int status;
489   ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
490   ASSERT_TRUE(WIFSIGNALED(status));
491   ASSERT_EQ(SIGKILL, WTERMSIG(status));
492 }
493 
TEST_F(LinuxPtraceDumperTest,SanitizeStackCopy)494 TEST_F(LinuxPtraceDumperTest, SanitizeStackCopy) {
495   static const size_t kNumberOfThreadsInHelperProgram = 1;
496 
497   pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
498   ASSERT_NE(child_pid, -1);
499 
500   LinuxPtraceDumper dumper(child_pid);
501   ASSERT_TRUE(dumper.Init());
502   EXPECT_TRUE(dumper.ThreadsSuspend());
503 
504   ThreadInfo thread_info;
505   EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info));
506 
507   const uintptr_t defaced =
508 #if defined(__LP64__)
509       0x0defaced0defaced;
510 #else
511       0x0defaced;
512 #endif
513 
514   uintptr_t simulated_stack[2];
515 
516   // Pointers into the stack shouldn't be sanitized.
517   memset(simulated_stack, 0xff, sizeof(simulated_stack));
518   simulated_stack[1] = thread_info.stack_pointer;
519   dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
520                            sizeof(simulated_stack), thread_info.stack_pointer,
521                            sizeof(uintptr_t));
522   ASSERT_NE(simulated_stack[1], defaced);
523 
524   // Memory prior to the stack pointer should be cleared.
525   ASSERT_EQ(simulated_stack[0], 0u);
526 
527   // Small integers should not be sanitized.
528   for (int i = -4096; i <= 4096; ++i) {
529     memset(simulated_stack, 0, sizeof(simulated_stack));
530     simulated_stack[0] = static_cast<uintptr_t>(i);
531     dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
532                              sizeof(simulated_stack), thread_info.stack_pointer,
533                              0u);
534     ASSERT_NE(simulated_stack[0], defaced);
535   }
536 
537   // The instruction pointer definitely should point into an executable mapping.
538   const MappingInfo* mapping_info = dumper.FindMappingNoBias(
539       reinterpret_cast<uintptr_t>(thread_info.GetInstructionPointer()));
540   ASSERT_NE(mapping_info, nullptr);
541   ASSERT_TRUE(mapping_info->exec);
542 
543   // Pointers to code shouldn't be sanitized.
544   memset(simulated_stack, 0, sizeof(simulated_stack));
545   simulated_stack[1] = thread_info.GetInstructionPointer();
546   dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
547                            sizeof(simulated_stack), thread_info.stack_pointer,
548                            0u);
549   ASSERT_NE(simulated_stack[0], defaced);
550 
551   // String fragments should be sanitized.
552   memcpy(simulated_stack, "abcdefghijklmnop", sizeof(simulated_stack));
553   dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
554                            sizeof(simulated_stack), thread_info.stack_pointer,
555                            0u);
556   ASSERT_EQ(simulated_stack[0], defaced);
557   ASSERT_EQ(simulated_stack[1], defaced);
558 
559   // Heap pointers should be sanititzed.
560 #if defined(__ARM_EABI__)
561   uintptr_t heap_addr = thread_info.regs.uregs[3];
562 #elif defined(__aarch64__)
563   uintptr_t heap_addr = thread_info.regs.regs[3];
564 #elif defined(__i386)
565   uintptr_t heap_addr = thread_info.regs.ecx;
566 #elif defined(__x86_64)
567   uintptr_t heap_addr = thread_info.regs.rcx;
568 #elif defined(__mips__)
569   uintptr_t heap_addr = thread_info.mcontext.gregs[1];
570 #elif defined(__riscv)
571   uintptr_t heap_addr = thread_info.mcontext.__gregs[4];
572 #else
573 #error This test has not been ported to this platform.
574 #endif
575   memset(simulated_stack, 0, sizeof(simulated_stack));
576   simulated_stack[0] = heap_addr;
577   dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
578                            sizeof(simulated_stack), thread_info.stack_pointer,
579                            0u);
580   ASSERT_EQ(simulated_stack[0], defaced);
581 
582   EXPECT_TRUE(dumper.ThreadsResume());
583   kill(child_pid, SIGKILL);
584 
585   // Reap child.
586   int status;
587   ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
588   ASSERT_TRUE(WIFSIGNALED(status));
589   ASSERT_EQ(SIGKILL, WTERMSIG(status));
590 }
591