-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libunwind] Replace process_vm_readv with SYS_rt_sigprocmask #74791
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-libunwind Author: Jordan R AW (ajordanr-google) Changesprocess_vm_readv is generally considered dangerous from a syscall perspective, and is frequently blanket banned in seccomp filters such as those in Chromium and ChromiumOS. We can get the same behaviour during the invalid PC address case with pipes and write/read. Testing to ensure that process_vm_readv does not appear, I ran the output of check-unwind on an ARM64 device under strace. Previously, bad_unwind_info in particular would use process_vm_readv, but with this commit, it now uses pipe2:
Full diff: https://github.com/llvm/llvm-project/pull/74791.diff 1 Files Affected:
diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index 647a5a9c9d92d..5e4e376220daa 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -33,6 +33,7 @@
#if defined(_LIBUNWIND_TARGET_LINUX) && \
(defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_RISCV) || \
defined(_LIBUNWIND_TARGET_S390X))
+#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <unistd.h>
@@ -2700,19 +2701,18 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_arm64 &) {
// [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
// The PC might contain an invalid address if the unwind info is bad, so
- // directly accessing it could cause a segfault. Use process_vm_readv to read
- // the memory safely instead. process_vm_readv was added in Linux 3.2, and
- // AArch64 supported was added in Linux 3.7, so the syscall is guaranteed to
- // be present. Unfortunately, there are Linux AArch64 environments where the
- // libc wrapper for the syscall might not be present (e.g. Android 5), so call
- // the syscall directly instead.
+ // directly accessing it could cause a segfault. Use pipe/write/read to read
+ // the memory safely instead.
+ int pipefd[2];
+ if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1)
+ return false;
uint32_t instructions[2];
- struct iovec local_iov = {&instructions, sizeof instructions};
- struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof instructions};
- long bytesRead =
- syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0);
+ const auto bufferSize = sizeof instructions;
+ if (write(pipefd[1], reinterpret_cast<void *>(pc), bufferSize) != bufferSize)
+ return false;
+ const auto bytesRead = read(pipefd[0], instructions, bufferSize);
// Look for instructions: mov x8, #0x8b; svc #0x0
- if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 ||
+ if (bytesRead != bufferSize || instructions[0] != 0xd2801168 ||
instructions[1] != 0xd4000001)
return false;
@@ -2762,17 +2762,20 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) {
template <typename A, typename R>
bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_riscv &) {
const pint_t pc = static_cast<pint_t>(getReg(UNW_REG_IP));
+ int pipefd[2];
+ if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1)
+ return false;
uint32_t instructions[2];
- struct iovec local_iov = {&instructions, sizeof instructions};
- struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof instructions};
- long bytesRead =
- syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0);
+ const auto bufferSize = sizeof instructions;
+ if (write(pipefd[1], reinterpret_cast<void *>(pc), bufferSize) != bufferSize)
+ return false;
+ const auto bytesRead = read(pipefd[0], instructions, bufferSize);
// Look for the two instructions used in the sigreturn trampoline
// __vdso_rt_sigreturn:
//
// 0x08b00893 li a7,0x8b
// 0x00000073 ecall
- if (bytesRead != sizeof instructions || instructions[0] != 0x08b00893 ||
+ if (bytesRead != bufferSize || instructions[0] != 0x08b00893 ||
instructions[1] != 0x00000073)
return false;
@@ -2822,13 +2825,18 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_s390x &) {
// onto the stack.
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
// The PC might contain an invalid address if the unwind info is bad, so
- // directly accessing it could cause a segfault. Use process_vm_readv to
+ // directly accessing it could cause a segfault. Use pipe/write/read to
// read the memory safely instead.
uint16_t inst;
- struct iovec local_iov = {&inst, sizeof inst};
- struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof inst};
- long bytesRead = process_vm_readv(getpid(), &local_iov, 1, &remote_iov, 1, 0);
- if (bytesRead == sizeof inst && (inst == 0x0a77 || inst == 0x0aad)) {
+ int pipefd[2];
+ if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1)
+ return false;
+ uint16_t inst;
+ const auto bufferSize = sizeof inst;
+ if (write(pipefd[1], reinterpret_cast<void *>(pc), bufferSize) != bufferSize)
+ return false;
+ const auto bytesRead = read(pipefd[0], &inst, bufferSize);
+ if (bytesRead == bufferSize && (inst == 0x0a77 || inst == 0x0aad)) {
_info = {};
_info.start_ip = pc;
_info.end_ip = pc + 2;
|
libunwind/src/UnwindCursor.hpp
Outdated
const auto bufferSize = sizeof instructions; | ||
if (write(pipefd[1], reinterpret_cast<void *>(pc), bufferSize) != bufferSize) | ||
return false; | ||
const auto bytesRead = read(pipefd[0], instructions, bufferSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider SYS_rt_sigprocmask like absl/debugging/internal/address_is_readable.cc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting, thanks.
I definitely could, though I'm mildly hesitant for two reasons. The first is because of the comment from address_is_readable.cc
// This strategy depends on Linux implementation details,
// so we rely on the test to alert us if it stops working.
In particular, it seems the EFAULT behaviour is not part of the POSIX.1-2008 standard.
The other is whether the check will span the full set of instructions we want to read. The Abseil implimentation checks 8 bytes, which I think should just about fit everything we need if it weren't for that 8 byte alignment:
// Align address on 8-byte boundary. On aarch64, checking last
// byte before inaccessible page returned unexpected EFAULT.
const uintptr_t u_addr = reinterpret_cast<uintptr_t>(addr) & ~uintptr_t{7};
I'm not familiar with the alignment requirements for a (potentially broken) PC. You'd have more context than I do as to whether we'd need to check for both instructions with rt_sigprocmask
?
I notice in the comments that pipe was a discarded method, but it doesn't really explain why. Performance reasons perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A pipe has the downside of requiring a new file descriptor. The absl migration away from a pipe was partly motivated by performance and partly by avoiding a new file descriptor.
Other OSes use their own implementations if needed, so we do not need to worry about the portability.
Sorry that I am not familiar with SYS_rt_procmask more, so I probably cannot answer some questions, but I suppose that these properties can be straightforwardly tested using a local small test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, that all makes sense. Thanks!
Playing around with SYS_rt_procmask
on Linux x86_64, There's apparently a point in which we can still read a
given address but SYS_rt_procmask
sets errno = 14
(Bad Address)
Looping over the raw syscall with:
syscall(SYS_rt_sigprocmask, /* how= */ -1, Addr, Addr, 8);
until it breaks gives
Addr: 0x56322ab46ff8; Offset: 59704; errno: 22; Value: 0x0
Addr: 0x56322ab46ff9; Offset: 59705; errno: 14; Value: 0x0
Addr: 0x56322ab46ffa; Offset: 59706; errno: 14; Value: 0x0
Addr: 0x56322ab46ffb; Offset: 59707; errno: 14; Value: 0x0
Addr: 0x56322ab46ffc; Offset: 59708; errno: 14; Value: 0x0
Addr: 0x56322ab46ffd; Offset: 59709; errno: 14; Value: 0x0
Addr: 0x56322ab46ffe; Offset: 59710; errno: 14; Value: 0x0
Addr: 0x56322ab46fff; Offset: 59711; errno: 14; Value: 0x0
fish: Job 1, './main' terminated by signal SIGSEGV (Address boundary error)
Seems there's about a 7 to 8 byte boundary where it fails but we can still read, at least on x86_64. So the 8 byte alignment on AARCH64 as mentioned in address_is_readable.cc seems to make sense, though we'd probably want to still check both the beginning and end of the instructions
range. I think that should be sufficient.
f0d8902
to
dceced3
Compare
libunwind/src/UnwindCursor.hpp
Outdated
syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0); | ||
// The PC might contain an invalid address if the unwind info is bad, so | ||
// directly accessing it could cause a SIGSEGV. | ||
if (!isReadableAddr(pc) || !isReadableAddr(pc + 4)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is unfortunate that we have to call isReadableAddr
twice.
linux/kernel/signal.c
rt_sigprocmask
actually supports an unaligned address and it checks 8 bytes at once.
We can remove the alignment code const auto alignedAddr = addr & ~pint_t{7};
and use one isReadableAddr(pc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given Paul's offline comments, I've removed the alignment as it seems to be a bug in QEMU not Linux.
libunwind/src/UnwindCursor.hpp
Outdated
// safe. Additionally, we need to pass the kernel_sigset_size, which is | ||
// different from libc sizeof(sigset_t). Some archs have sigset_t | ||
// defined as unsigned long, so let's use that. | ||
const auto approxKernelSigsetSize = sizeof(unsigned long); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sizeof(unsigned long)
is 4 for 32-bit ELF targets. We should use 8 (tested on i386).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! If you have a better idea on how to make this more robust, I'd love to know. For now I've hardcoded it to 8 then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is important that we get this right since otherwise the syscall returns EINVAL without trying to access the user memory: https://elixir.bootlin.com/linux/v6.6.7/source/kernel/signal.c#L3184
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at linux-6.6.7 source code, we have some minor variety on sigset_t
size...
TL;DR: They are almost all _NSIG
bits long, which is 64 bits or, in the case of MIPS, maybe 128? There are two exceptions to this, where they are not a struct of size _NSIG/_NSIG_BPW * sizeof(unsigned long)
but rather just unsigned long
. I assume _NSIG_BPW == sizeof(long) == sizeof(unsigned long)
, which seems reasonable given the comments (and not _NSIG_BPW == sizeof(int)
).
sigset_t
is almost always defined as:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
Where:
arch/parisc/include/asm/signal.h-7-#define _NSIG 64
arch/parisc/include/asm/signal.h-8-/* bits-per-word, where word apparently means 'long' not 'int' */
arch/parisc/include/asm/signal.h-9-#define _NSIG_BPW BITS_PER_LONG
arch/parisc/include/asm/signal.h:10:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/xtensa/include/uapi/asm/signal.h-18-#define _NSIG 64
arch/xtensa/include/uapi/asm/signal.h-19-#define _NSIG_BPW 32
arch/xtensa/include/uapi/asm/signal.h:20:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/ia64/include/asm/signal.h-15-#define _NSIG 64
arch/ia64/include/asm/signal.h-16-#define _NSIG_BPW 64
arch/ia64/include/asm/signal.h:17:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/alpha/include/asm/signal.h-10-#define _NSIG 64
arch/alpha/include/asm/signal.h-11-#define _NSIG_BPW 64
arch/alpha/include/asm/signal.h:12:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/arm/include/asm/signal.h-10-#define _NSIG 64
arch/arm/include/asm/signal.h-11-#define _NSIG_BPW 32
arch/arm/include/asm/signal.h:12:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/s390/include/asm/signal.h-12-/* Most things should be clean enough to redefine this at will, if care
arch/s390/include/asm/signal.h-13- is taken to make libc match. */
arch/s390/include/asm/signal.h-14-#include <asm/sigcontext.h>
arch/s390/include/asm/signal.h-15-#define _NSIG _SIGCONTEXT_NSIG
arch/s390/include/asm/signal.h-16-#define _NSIG_BPW _SIGCONTEXT_NSIG_BPW
arch/s390/include/asm/signal.h:17:#define _NSIG_WORDS _SIGCONTEXT_NSIG_WORDS
arch/s390/include/uapi/asm/sigcontext.h:30:/* Has to be at least _NSIG_WORDS from asm/signal.h */
arch/s390/include/uapi/asm/sigcontext.h-31-#define _SIGCONTEXT_NSIG 64
arch/s390/include/uapi/asm/sigcontext.h-32-#define _SIGCONTEXT_NSIG_BPW 64
// or
arch/x86/include/asm/signal.h-11-#define _NSIG 64
arch/x86/include/asm/signal.h-12-
arch/x86/include/asm/signal.h-13-#ifdef __i386__
arch/x86/include/asm/signal.h-14-# define _NSIG_BPW 32
arch/x86/include/asm/signal.h-15-#else
arch/x86/include/asm/signal.h-16-# define _NSIG_BPW 64
arch/x86/include/asm/signal.h-17-#endif
arch/x86/include/asm/signal.h-18-
arch/x86/include/asm/signal.h:19:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/m68k/include/asm/signal.h-10-#define _NSIG 64
arch/m68k/include/asm/signal.h-11-#define _NSIG_BPW 32
arch/m68k/include/asm/signal.h:12:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or (quite weirdly)
arch/mips/include/uapi/asm/signal.h-15-#define _NSIG 128
arch/mips/include/uapi/asm/signal.h-16-#define _NSIG_BPW (sizeof(unsigned long) * 8)
arch/mips/include/uapi/asm/signal.h:17:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or
arch/sparc/include/uapi/asm/signal.h-81-#define __NEW_NSIG 64
arch/sparc/include/uapi/asm/signal.h-82-#ifdef __arch64__
arch/sparc/include/uapi/asm/signal.h-83-#define _NSIG_BPW 64
arch/sparc/include/uapi/asm/signal.h-84-#else
arch/sparc/include/uapi/asm/signal.h-85-#define _NSIG_BPW 32
arch/sparc/include/uapi/asm/signal.h-86-#endif
arch/sparc/include/uapi/asm/signal.h:87:#define _NSIG_WORDS (__NEW_NSIG / _NSIG_BPW)
// or
arch/powerpc/include/uapi/asm/signal.h-7-#define _NSIG 64
arch/powerpc/include/uapi/asm/signal.h-8-#ifdef __powerpc64__
arch/powerpc/include/uapi/asm/signal.h-9-#define _NSIG_BPW 64
arch/powerpc/include/uapi/asm/signal.h-10-#else
arch/powerpc/include/uapi/asm/signal.h-11-#define _NSIG_BPW 32
arch/powerpc/include/uapi/asm/signal.h-12-#endif
arch/powerpc/include/uapi/asm/signal.h:13:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
// or (finally)
include/uapi/asm-generic/signal.h-7-#define _NSIG 64
include/uapi/asm-generic/signal.h-8-#define _NSIG_BPW __BITS_PER_LONG
include/uapi/asm-generic/signal.h:9:#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
Here are the two exceptions to that (and their line numbers):
arch/x86/include/uapi/asm/signal.h:17:typedef unsigned long sigset_t;
arch/alpha/include/uapi/asm/signal.h:14:typedef unsigned long sigset_t;
There were also various old_sigset_t
and __kernel_sigset_t
, which were defined like:
arch/ia64/include/asm/signal.h:24:typedef unsigned long old_sigset_t;
arch/alpha/include/asm/signal.h:14:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/m68k/include/asm/signal.h:14:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/s390/include/asm/signal.h:19:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/x86/include/asm/signal.h:21:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/parisc/include/asm/signal.h:17:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/mips/include/uapi/asm/signal.h:23:typedef unsigned long old_sigset_t; /* at least 32 bits */
arch/powerpc/include/uapi/asm/signal.h:15:typedef unsigned long old_sigset_t; /* at least 32 bits */
and
arch/ia64/include/uapi/asm/posix_types.h:5:typedef unsigned long __kernel_sigset_t; /* at least 32 bits */
arch/s390/include/uapi/asm/posix_types.h:46:typedef unsigned long __kernel_sigset_t; /* at least 32 bits */
arch/alpha/include/uapi/asm/posix_types.h:14:typedef unsigned long __kernel_sigset_t; /* at least 32 bits */
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about (NSIG-1)/8
? https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html. Just let libc handle it. It should work cleanly for everything that's not MIPS. I don't have a MIPS system to test if this works for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should not worry about mips. mips uses are being phased out, and the remaining use cases (embedded?) seems to not care about this involved sigreturn trampoline anyway...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I ended up using NSIG/8
since it rounds down anyways, and I checked the glibc code and it has 128 instead of 127 for mips, and I think we would actually want 128/8 instead if I'm reading the mips case right. It's not that important if we don't care about mips.
NSIG is 65 for everything in glibc except hurd (33) and mips (128)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one question otherwise this looks good to me.
libunwind/src/UnwindCursor.hpp
Outdated
// invalid. | ||
assert(Result == -1); | ||
assert(errno == EFAULT || errno == EINVAL); | ||
return errno != EFAULT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to save+restore errno? Or is libunwind free to modify errno?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably save and restore, as we do this above in getInfoFromTBTable
. Good point. Done.
libunwind/src/UnwindCursor.hpp
Outdated
(void)Result; | ||
// Because our "how" is invalid, this syscall should always fail, and our | ||
// errno should always be EINVAL or an EFAULT. EFAULT is not guaranteed | ||
// by the POSIX standard. Additionally, this relies on the Linux kernel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"EFAULT is not guaranteed by the POSIX standard." should be removed since the whole function is Linux specific
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
libunwind/src/UnwindCursor.hpp
Outdated
template <typename A, typename R> | ||
bool UnwindCursor<A, R>::isReadableAddr(const pint_t addr) const { | ||
// This code is heavily based on Abseil's 'address_is_readable.cc', | ||
// which is Copyright Abseil Authors (2017). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine to just say without more quote about the license.
We use SYS_rt_sigprocmask, inspired by Abseil's AddressIsReadable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
I don't have Commit Access (I so rarely actually commit to upstream), and I don't think I have yet the "track record of submitting high quality patches" :) Can someone else merge this once the checks are green? |
Hmm. It looks like |
|
process_vm_readv is generally considered dangerous from a syscall perspective, and is frequently blanket banned in seccomp filters such as those in Chromium and ChromiumOS. We can get the same behaviour during the invalid PC address case with pipes and write/read. Testing to ensure that process_vm_readv does not appear, I ran the output of check-unwind on an ARM64 device under strace. Previously, bad_unwind_info in particular would use process_vm_readv, but with this commit, it now uses pipe2: ``` strace test/Output/bad_unwind_info.pass.cpp.dir/t.tmp.exe \ |& grep process_vm_readv strace test/Output/bad_unwind_info.pass.cpp.dir/t.tmp.exe \ |& grep pipe2 ```
3651ac9
to
70ce62e
Compare
Rebased to include 47413bb (a merge commit seemed silly?). No other changes have been made. |
All CI runs are failing on unrelated tests... can we re-run the CI again in a week or so? |
) process_vm_readv is generally considered dangerous from a syscall perspective, and is frequently blanket banned in seccomp filters such as those in Chromium and ChromiumOS. We can get the same behaviour during the invalid PC address case with the raw SYS_rt_sigprocmask syscall. Testing to ensure that process_vm_readv does not appear, I ran the output of check-unwind on an ARM64 device under strace. Previously, bad_unwind_info in particular would use process_vm_readv, but with this commit, it now no longer uses it: ``` strace test/Output/bad_unwind_info.pass.cpp.dir/t.tmp.exe \ |& grep process_vm_readv ``` The libunwind unittests were also tested on ARM64 ChromeOS (Gentoo Linux) devices.
process_vm_readv is generally considered dangerous from a syscall perspective, and is frequently blanket banned in seccomp filters such as those in Chromium and ChromiumOS. We can get the same behaviour during the invalid PC address case with the raw SYS_rt_sigprocmask syscall.
Testing to ensure that process_vm_readv does not appear, I ran the output of check-unwind on an ARM64 device under strace. Previously, bad_unwind_info in particular would use process_vm_readv, but with this commit, it now no longer uses it:
The libunwind unittests were also tested on ARM64 ChromeOS (Gentoo Linux) devices.