diff --git a/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc index e335413b..1fb26ede 100644 --- a/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc +++ b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc @@ -63,7 +63,7 @@ TEST(LinuxCoreDumperTest, BuildProcPath) { TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) { CrashGenerator crash_generator; if (!crash_generator.HasDefaultCorePattern()) { - fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test" + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test " "is skipped due to non-default core pattern\n"); return; } @@ -76,23 +76,15 @@ TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) { // CrashGenerator is identified and fixed. if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread, kCrashSignal, &child_pid)) { - fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test" + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test " "is skipped due to no core dump generated\n"); return; } pid_t pid = getpid(); - const char* core_file = crash_generator.GetCoreFilePath().c_str(); - - // Since CrashGenerator::CreateChildCrash() simply crashed a fork of - // this process, we expect that those proc files, which are used by - // LinuxCoreDumper, of crashed child process have the same content of - // this process. So we simply pass the proc files of this process to - // LinuxCoreDumper. - char procfs_path[NAME_MAX]; - snprintf(procfs_path, NAME_MAX, "/proc/%d", pid); - - LinuxCoreDumper dumper(child_pid, core_file, procfs_path); + const string core_file = crash_generator.GetCoreFilePath(); + const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy(); + LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str()); dumper.Init(); EXPECT_TRUE(dumper.IsPostMortem()); diff --git a/src/common/linux/tests/crash_generator.cc b/src/common/linux/tests/crash_generator.cc index 7274acf9..dff2bca2 100644 --- a/src/common/linux/tests/crash_generator.cc +++ b/src/common/linux/tests/crash_generator.cc @@ -55,6 +55,12 @@ struct ThreadData { pid_t* thread_id_ptr; }; +const char* const kProcFilesToCopy[] = { + "auxv", "cmdline", "environ", "maps", "status" +}; +const size_t kNumProcFilesToCopy = + sizeof(kProcFilesToCopy) / sizeof(kProcFilesToCopy[0]); + // Core file size limit set to 1 MB, which is big enough for test purposes. const rlim_t kCoreSizeLimit = 1024 * 1024; @@ -95,6 +101,10 @@ std::string CrashGenerator::GetCoreFilePath() const { return temp_dir_.path() + "/core"; } +std::string CrashGenerator::GetDirectoryOfProcFilesCopy() const { + return temp_dir_.path() + "/proc"; +} + pid_t CrashGenerator::GetThreadId(unsigned index) const { return reinterpret_cast(shared_memory_)[index]; } @@ -160,6 +170,15 @@ bool CrashGenerator::CreateChildCrash( } if (SetCoreFileSizeLimit(kCoreSizeLimit)) { CreateThreadsInChildProcess(num_threads); + std::string proc_dir = GetDirectoryOfProcFilesCopy(); + if (mkdir(proc_dir.c_str(), 0755) == -1) { + perror("CrashGenerator: Failed to create proc directory"); + exit(1); + } + if (!CopyProcFiles(getpid(), proc_dir.c_str())) { + fprintf(stderr, "CrashGenerator: Failed to copy proc files\n"); + exit(1); + } if (kill(*GetThreadIdPointer(crash_thread), crash_signal) == -1) { perror("CrashGenerator: Failed to kill thread by signal"); } @@ -185,6 +204,25 @@ bool CrashGenerator::CreateChildCrash( return true; } +bool CrashGenerator::CopyProcFiles(pid_t pid, const char* path) const { + char from_path[PATH_MAX], to_path[PATH_MAX]; + for (size_t i = 0; i < kNumProcFilesToCopy; ++i) { + int num_chars = snprintf(from_path, PATH_MAX, "/proc/%d/%s", + pid, kProcFilesToCopy[i]); + if (num_chars < 0 || num_chars >= PATH_MAX) + return false; + + num_chars = snprintf(to_path, PATH_MAX, "%s/%s", + path, kProcFilesToCopy[i]); + if (num_chars < 0 || num_chars >= PATH_MAX) + return false; + + if (!CopyFile(from_path, to_path)) + return false; + } + return true; +} + void CrashGenerator::CreateThreadsInChildProcess(unsigned num_threads) { *GetThreadIdPointer(0) = getpid(); diff --git a/src/common/linux/tests/crash_generator.h b/src/common/linux/tests/crash_generator.h index 874b15cd..d05ce73d 100644 --- a/src/common/linux/tests/crash_generator.h +++ b/src/common/linux/tests/crash_generator.h @@ -58,13 +58,12 @@ class CrashGenerator { // if /proc/sys/kernel/core_pattern has the default value 'core'. bool HasDefaultCorePattern() const; - // Sets the maximum size of core dump file (both the soft and hard limit) - // to |limit| bytes. Returns true on success. - bool SetCoreFileSizeLimit(rlim_t limit) const; - // Returns the expected path of the core dump file. std::string GetCoreFilePath() const; + // Returns the directory of a copy of proc files of the child process. + std::string GetDirectoryOfProcFilesCopy() const; + // Creates a crash (and a core dump file) by creating a child process with // |num_threads| threads, and the terminating the child process by sending // a signal with number |crash_signal| to the |crash_thread|-th thread. @@ -72,14 +71,23 @@ class CrashGenerator { bool CreateChildCrash(unsigned num_threads, unsigned crash_thread, int crash_signal, pid_t* child_pid); - // Creates |num_threads| threads in the child process. - void CreateThreadsInChildProcess(unsigned num_threads); - // Returns the thread ID of the |index|-th thread in the child process. // This method does not validate |index|. pid_t GetThreadId(unsigned index) const; private: + // Copies the following proc files of the process with |pid| to the directory + // at |path|: auxv, cmdline, environ, maps, status + // The directory must have been created. Returns true on success. + bool CopyProcFiles(pid_t pid, const char* path) const; + + // Creates |num_threads| threads in the child process. + void CreateThreadsInChildProcess(unsigned num_threads); + + // Sets the maximum size of core dump file (both the soft and hard limit) + // to |limit| bytes. Returns true on success. + bool SetCoreFileSizeLimit(rlim_t limit) const; + // Creates a shared memory of |memory_size| bytes for communicating thread // IDs between the parent and child process. Returns true on success. bool MapSharedMemory(size_t memory_size); diff --git a/src/common/tests/file_utils.cc b/src/common/tests/file_utils.cc index 0d9b04ad..1264b078 100644 --- a/src/common/tests/file_utils.cc +++ b/src/common/tests/file_utils.cc @@ -40,6 +40,61 @@ namespace google_breakpad { +bool CopyFile(const char* from_path, const char* to_path) { + int infile = HANDLE_EINTR(open(from_path, O_RDONLY)); + if (infile < 0) { + perror("open"); + return false; + } + + int outfile = HANDLE_EINTR(creat(to_path, 0666)); + if (outfile < 0) { + perror("creat"); + if (HANDLE_EINTR(close(infile)) < 0) { + perror("close"); + } + return false; + } + + char buffer[1024]; + bool result = true; + + while (result) { + ssize_t bytes_read = HANDLE_EINTR(read(infile, buffer, sizeof(buffer))); + if (bytes_read < 0) { + perror("read"); + result = false; + break; + } + if (bytes_read == 0) + break; + ssize_t bytes_written_per_read = 0; + do { + ssize_t bytes_written_partial = HANDLE_EINTR(write( + outfile, + &buffer[bytes_written_per_read], + bytes_read - bytes_written_per_read)); + if (bytes_written_partial < 0) { + perror("write"); + result = false; + break; + } + bytes_written_per_read += bytes_written_partial; + } while (bytes_written_per_read < bytes_read); + } + + if (HANDLE_EINTR(close(infile)) == -1) { + perror("close"); + result = false; + } + if (HANDLE_EINTR(close(outfile)) == -1) { + perror("close"); + result = false; + } + + return result; +} + bool ReadFile(const char* path, void* buffer, ssize_t* buffer_size) { int fd = HANDLE_EINTR(open(path, O_RDONLY)); if (fd == -1) { diff --git a/src/common/tests/file_utils.h b/src/common/tests/file_utils.h index 52e8b009..c98a9bfa 100644 --- a/src/common/tests/file_utils.h +++ b/src/common/tests/file_utils.h @@ -35,6 +35,9 @@ namespace google_breakpad { +// Copies a file from |from_path| to |to_path|. Returns true on success. +bool CopyFile(const char* from_path, const char* to_path); + // Reads the content of a file at |path| into |buffer|. |buffer_size| specifies // the size of |buffer| in bytes and returns the number of bytes read from the // file on success. Returns true on success.