linux_ptrace_dumper: Handle ARMEABI binaries on ARM64 kernels

For ARM32 binaries running on ARM64 Linux (kernel) reading of FP
registers fails. It's guarded for Android as well as softap platforms,
but other ARM platforms can suffer from this issue.

The ARMEABI linux_ptrace_dumper_unittest fails on system that runs
under ARM64 kernel.

In order to mitigate the issue, we adding a VFP registers read.
The Breakpad does not support include of VFP registers into a minidump
file, so that read is noop for the backend, but just a fix for broken
systems.

Bug: internal 322205293
Test: Run linux_ptrace_dumper_unittest on following [userland:kernel]
      combinations:
        armeabi-hardfp:armeabi
        armeabi-softfp:armeabi
        armeabi-hardfp:aarch64
        aarch64:aarch64

Signed-off-by: Volodymyr Riazantsev <riazantsevv@google.com>
Change-Id: I0709ae9a7ff913340ebc89de703ab2cb9c823b14
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/5247149
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Volodymyr Riazantsev 2024-01-29 18:24:08 -08:00 committed by Joshua Peraza
parent 6551ac3632
commit 5a0e1e34b0

View file

@ -62,6 +62,20 @@
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
#if defined(__arm__)
/*
* https://elixir.bootlin.com/linux/v6.8-rc2/source/arch/arm/include/asm/user.h#L81
* User specific VFP registers. If only VFPv2 is present, registers 16 to 31
* are ignored by the ptrace system call and the signal handler.
*/
typedef struct user_vfp {
unsigned long long fpregs[32];
unsigned long fpscr;
// Kernel just appends fpscr to the copy of fpregs, so we need to force
// compiler to build the same layout.
} __attribute__((packed, aligned(4))) user_vfp_t;
#endif // defined(__arm__)
// Suspends a thread by attaching to it.
static bool SuspendThread(pid_t pid) {
// This may fail if the thread has just died or debugged.
@ -152,6 +166,24 @@ bool LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child,
return true;
}
// This read VFP registers via either PTRACE_GETREGSET or PTRACE_GETREGS
#if defined(__arm__)
static bool ReadVFPRegistersArm32(pid_t tid, struct iovec* io) {
#ifdef PTRACE_GETREGSET
if (sys_ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_ARM_VFP),
io) == 0 && io->iov_len == sizeof(user_vfp_t)) {
return true;
}
#endif // PTRACE_GETREGSET
#ifdef PTRACE_GETVFPREGS
if (sys_ptrace(PTRACE_GETVFPREGS, tid, nullptr, io->iov_base) == 0) {
return true;
}
#endif // PTRACE_GETVFPREGS
return false;
}
#endif // defined(__arm__)
bool LinuxPtraceDumper::ReadRegisterSet(ThreadInfo* info, pid_t tid)
{
#ifdef PTRACE_GETREGSET
@ -163,7 +195,24 @@ bool LinuxPtraceDumper::ReadRegisterSet(ThreadInfo* info, pid_t tid)
info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len);
if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) {
return false;
// We are going to check if we can read VFP registers on ARM32.
// Currently breakpad does not support VFP registers to be a part of minidump,
// so this is only to confirm that we can actually read FP registers.
// That is needed to prevent a false-positive minidumps failures with ARM32
// binaries running on top of ARM64 Linux kernels.
#if defined(__arm__)
switch (errno) {
case EIO:
case EINVAL:
user_vfp vfp;
struct iovec io;
io.iov_base = &vfp;
io.iov_len = sizeof(vfp);
return ReadVFPRegistersArm32(tid, &io);
default:
return false;
}
#endif // defined(__arm__)
}
return true;
#else
@ -194,7 +243,24 @@ bool LinuxPtraceDumper::ReadRegisters(ThreadInfo* info, pid_t tid) {
void* fp_addr;
info->GetFloatingPointRegisters(&fp_addr, NULL);
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) {
return false;
// We are going to check if we can read VFP registers on ARM32.
// Currently breakpad does not support VFP registers to be a part of minidump,
// so this is only to confirm that we can actually read FP registers.
// That is needed to prevent a false-positive minidumps failures with ARM32
// binaries running on top of ARM64 Linux kernels.
#if defined(__arm__)
switch (errno) {
case EIO:
case EINVAL:
user_vfp vfp;
struct iovec io;
io.iov_base = &vfp;
io.iov_len = sizeof(vfp);
return ReadVFPRegistersArm32(tid, &io);
default:
return false;
}
#endif // defined(__arm__)
}
#endif // !(defined(__ANDROID__) && defined(__ARM_EABI__))
#endif // !defined(__SOFTFP__)