diff --git a/util/posix/process_util.h b/util/posix/process_util.h new file mode 100644 index 00000000..0abe46eb --- /dev/null +++ b/util/posix/process_util.h @@ -0,0 +1,42 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_POSIX_PROCESS_UTIL_POSIX_H_ +#define CRASHPAD_UTIL_POSIX_PROCESS_UTIL_POSIX_H_ + +#include + +#include +#include + +namespace crashpad { + +//! \brief Obtains the arguments used to launch a process. +//! +//! \param[in] pid The process ID of the process to examine. +//! \param[out] argv The process’ arguments as passed to its `main()` function +//! as the \a argv parameter, possibly modified by the process. +//! +//! \return `true` on success, with \a argv populated appropriately. Otherwise, +//! `false`. +//! +//! \note This function may spuriously return `false` when used to examine a +//! process that it is calling `exec()`. If examining such a process, call +//! this function in a retry loop with a small (100ns) delay to avoid an +//! erroneous assumption that \a pid is not running. +bool ProcessArgumentsForPID(pid_t pid, std::vector* argv); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_POSIX_PROCESS_UTIL_POSIX_H_ diff --git a/util/posix/process_util_mac.cc b/util/posix/process_util_mac.cc new file mode 100644 index 00000000..07ee60d5 --- /dev/null +++ b/util/posix/process_util_mac.cc @@ -0,0 +1,102 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/posix/process_util.h" + +#include +#include +#include + +#include "base/basictypes.h" + +namespace crashpad { + +bool ProcessArgumentsForPID(pid_t pid, std::vector* argv) { + // The format of KERN_PROCARGS2 is explained in 10.9.2 adv_cmds-153/ps/print.c + // getproclline(). It is an int (argc) followed by the executable’s string + // area. The string area consists of NUL-terminated strings, beginning with + // the executable path, and then starting on an aligned boundary, all of the + // elements of argv, envp, and applev. + + // It is possible for a process to exec() in between the two sysctl() calls + // below. If that happens, and the string area of the new program is larger + // than that of the old one, args_size_estimate will be too small. To detect + // this situation, the second sysctl() attempts to fetch args_size_estimate + + // 1 bytes, expecting to only receive args_size_estimate. If it gets the extra + // byte, it indicates that the string area has grown, and the sysctl() pair + // will be retried a limited number of times. + + size_t args_size_estimate; + size_t args_size; + std::string args; + int tries = 3; + do { + int mib[] = {CTL_KERN, KERN_PROCARGS2, pid}; + int rv = sysctl(mib, arraysize(mib), NULL, &args_size_estimate, NULL, 0); + if (rv != 0) { + return false; + } + + args_size = args_size_estimate + 1; + args.resize(args_size); + rv = sysctl(mib, arraysize(mib), &args[0], &args_size, NULL, 0); + if (rv != 0) { + return false; + } + } while (args_size == args_size_estimate + 1 && tries--); + + if (args_size == args_size_estimate + 1) { + return false; + } + + // KERN_PROCARGS2 needs to at least contain argc. + if (args_size < sizeof(int)) { + return false; + } + args.resize(args_size); + + // Get argc. + int argc; + memcpy(&argc, &args[0], sizeof(argc)); + + // Find the end of the executable path. + size_t start_pos = sizeof(argc); + size_t nul_pos = args.find('\0', start_pos); + if (nul_pos == std::string::npos) { + return false; + } + + // Find the beginning of the string area. + start_pos = args.find_first_not_of('\0', nul_pos); + if (start_pos == std::string::npos) { + return false; + } + + std::vector local_argv; + while (argc-- && nul_pos != std::string::npos) { + nul_pos = args.find('\0', start_pos); + local_argv.push_back(args.substr(start_pos, nul_pos - start_pos)); + start_pos = nul_pos + 1; + } + + if (argc >= 0) { + // Not every argument was recovered. + return false; + } + + argv->swap(local_argv); + return true; +} + +} // namespace crashpad diff --git a/util/posix/process_util_test.cc b/util/posix/process_util_test.cc new file mode 100644 index 00000000..2fb6841e --- /dev/null +++ b/util/posix/process_util_test.cc @@ -0,0 +1,47 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/posix/process_util.h" + +#include +#include + +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(ProcessUtil, ProcessArgumentsForPID) { + std::vector argv; + ASSERT_TRUE(ProcessArgumentsForPID(getpid(), &argv)); + + // gtest argv processing scrambles argv, but it leaves argc and argv[0] + // intact, so test those. + + int argc = static_cast(argv.size()); + int expect_argc = *_NSGetArgc(); + EXPECT_EQ(expect_argc, argc); + + ASSERT_GE(expect_argc, 1); + ASSERT_GE(argc, 1); + + char** expect_argv = *_NSGetArgv(); + EXPECT_EQ(std::string(expect_argv[0]), argv[0]); +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index eafb4922..923f18a2 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -41,6 +41,8 @@ 'misc/initialization_state_dcheck.h', 'misc/uuid.cc', 'misc/uuid.h', + 'posix/process_util.h', + 'posix/process_util_mac.cc', 'stdlib/cxx.h', 'stdlib/objc.h', 'stdlib/strlcpy.cc', @@ -86,6 +88,7 @@ 'misc/initialization_state_dcheck_test.cc', 'misc/initialization_state_test.cc', 'misc/uuid_test.cc', + 'posix/process_util_test.cc', 'stdlib/strlcpy_test.cc', ], },