"""
CPU context structures for different architectures.
This module defines the register layouts for NT_PRSTATUS notes
which contain CPU state at the time of the crash.
"""
from __future__ import annotations
import struct
from dataclasses import dataclass, field
from enum import IntEnum
from .elf import ElfData
[docs]
class NoteType(IntEnum):
"""ELF note types for core dumps."""
NT_PRSTATUS = 1
NT_PRFPREG = 2
NT_PRPSINFO = 3
NT_TASKSTRUCT = 4
NT_AUXV = 6
NT_SIGINFO = 0x53494749
NT_FILE = 0x46494C45
NT_PRXFPREG = 0x46E62B7F
# x86_64 register indices in pr_reg array
[docs]
class X86_64Reg(IntEnum):
"""x86_64 register indices in the prstatus pr_reg array."""
R15 = 0
R14 = 1
R13 = 2
R12 = 3
RBP = 4
RBX = 5
R11 = 6
R10 = 7
R9 = 8
R8 = 9
RAX = 10
RCX = 11
RDX = 12
RSI = 13
RDI = 14
ORIG_RAX = 15
RIP = 16
CS = 17
EFLAGS = 18
RSP = 19
SS = 20
FS_BASE = 21
GS_BASE = 22
DS = 23
ES = 24
FS = 25
GS = 26
# AArch64 register indices
[docs]
class AArch64Reg(IntEnum):
"""AArch64 register indices."""
X0 = 0
X1 = 1
X2 = 2
X3 = 3
X4 = 4
X5 = 5
X6 = 6
X7 = 7
X8 = 8
X9 = 9
X10 = 10
X11 = 11
X12 = 12
X13 = 13
X14 = 14
X15 = 15
X16 = 16
X17 = 17
X18 = 18
X19 = 19
X20 = 20
X21 = 21
X22 = 22
X23 = 23
X24 = 24
X25 = 25
X26 = 26
X27 = 27
X28 = 28
X29 = 29 # FP
X30 = 30 # LR
SP = 31
PC = 32
PSTATE = 33
[docs]
@dataclass
class ArchRegisterInfo:
"""Architecture-specific register information."""
num_registers: int
register_size: int # in bytes
reg_enum: type[IntEnum] | None = None
@property
def pr_reg_size(self) -> int:
"""Size of the pr_reg array in bytes."""
return self.num_registers * self.register_size
# Architecture register configurations
ARCH_REGISTER_INFO: dict[str, ArchRegisterInfo] = {
"x86_64": ArchRegisterInfo(27, 8, X86_64Reg),
"aarch64": ArchRegisterInfo(34, 8, AArch64Reg),
"arm64": ArchRegisterInfo(34, 8, AArch64Reg),
# s390x has 16 GPRs + PSW (2 * 8 bytes) + 16 access regs + 16 control regs
"s390x": ArchRegisterInfo(32, 8, None),
# ppc64 has 32 GPRs + special registers
"ppc64le": ArchRegisterInfo(48, 8, None),
"ppc64": ArchRegisterInfo(48, 8, None),
# RISC-V has 32 GPRs + PC
"riscv64": ArchRegisterInfo(33, 8, None),
}
[docs]
@dataclass
class CpuContext:
"""
CPU context for a single processor.
This represents the register state at the time of the crash.
"""
cpu_id: int = 0
pid: int = 0
registers: dict[str, int] = field(default_factory=dict)
# Signal information
si_signo: int = 0
si_code: int = 0
si_errno: int = 0
# Process IDs
pr_pid: int = 0
pr_ppid: int = 0
pr_pgrp: int = 0
pr_sid: int = 0
# Times (in jiffies/clock ticks)
pr_utime_sec: int = 0
pr_utime_usec: int = 0
pr_stime_sec: int = 0
pr_stime_usec: int = 0
pr_cutime_sec: int = 0
pr_cutime_usec: int = 0
pr_cstime_sec: int = 0
pr_cstime_usec: int = 0
[docs]
def pack_prstatus(ctx: CpuContext, arch: str, endianness: ElfData) -> bytes:
"""
Pack a prstatus structure for the given architecture.
The prstatus structure format (for 64-bit):
struct elf_prstatus {
struct elf_siginfo pr_info; // 12 bytes
short pr_cursig; // 2 bytes
unsigned long pr_sigpend; // 8 bytes
unsigned long pr_sighold; // 8 bytes
pid_t pr_pid; // 4 bytes
pid_t pr_ppid; // 4 bytes
pid_t pr_pgrp; // 4 bytes
pid_t pr_sid; // 4 bytes
struct timeval pr_utime; // 16 bytes
struct timeval pr_stime; // 16 bytes
struct timeval pr_cutime; // 16 bytes
struct timeval pr_cstime; // 16 bytes
elf_gregset_t pr_reg; // varies by arch
int pr_fpvalid; // 4 bytes
};
Args:
ctx: CPU context with register values
arch: Architecture name
endianness: Little or big endian
Returns:
Packed prstatus structure as bytes
"""
if arch not in ARCH_REGISTER_INFO:
raise ValueError(f"Unsupported architecture for CPU context: {arch}")
reg_info = ARCH_REGISTER_INFO[arch]
fmt_prefix = "<" if endianness == ElfData.ELFDATA2LSB else ">"
parts = []
# pr_info (elf_siginfo) - 12 bytes: si_signo, si_code, si_errno
parts.append(
struct.pack(f"{fmt_prefix}iii", ctx.si_signo, ctx.si_code, ctx.si_errno)
)
# pr_cursig - 2 bytes + 2 bytes padding (to align pr_sigpend)
parts.append(struct.pack(f"{fmt_prefix}hxx", ctx.si_signo))
# pr_sigpend, pr_sighold - 8 bytes each
parts.append(struct.pack(f"{fmt_prefix}QQ", 0, 0))
# pr_pid, pr_ppid, pr_pgrp, pr_sid - 4 bytes each
pr_pid = ctx.pr_pid if ctx.pr_pid else ctx.pid
parts.append(
struct.pack(f"{fmt_prefix}iiii", pr_pid, ctx.pr_ppid, ctx.pr_pgrp, ctx.pr_sid)
)
# pr_utime, pr_stime, pr_cutime, pr_cstime - struct timeval (16 bytes each on 64-bit)
parts.append(struct.pack(f"{fmt_prefix}qq", ctx.pr_utime_sec, ctx.pr_utime_usec))
parts.append(struct.pack(f"{fmt_prefix}qq", ctx.pr_stime_sec, ctx.pr_stime_usec))
parts.append(struct.pack(f"{fmt_prefix}qq", ctx.pr_cutime_sec, ctx.pr_cutime_usec))
parts.append(struct.pack(f"{fmt_prefix}qq", ctx.pr_cstime_sec, ctx.pr_cstime_usec))
# pr_reg - register array
reg_array = [0] * reg_info.num_registers
# Fill in registers from the context
if reg_info.reg_enum:
for name, value in ctx.registers.items():
name_upper = name.upper()
# Try to find the register in the enum
try:
reg_idx = reg_info.reg_enum[name_upper]
reg_array[reg_idx] = value
except KeyError:
# Register name not found, try without prefix
pass
else:
# For architectures without detailed enum, just use numeric indices
for name, value in ctx.registers.items():
if name.isdigit():
idx = int(name)
if idx < reg_info.num_registers:
reg_array[idx] = value
# Pack registers
reg_format = f"{fmt_prefix}{reg_info.num_registers}Q"
parts.append(struct.pack(reg_format, *reg_array))
# pr_fpvalid - 4 bytes
parts.append(struct.pack(f"{fmt_prefix}i", 0))
return b"".join(parts)
[docs]
def get_prstatus_size(arch: str) -> int:
"""
Get the size of the prstatus structure for an architecture.
Args:
arch: Architecture name
Returns:
Size in bytes
"""
if arch not in ARCH_REGISTER_INFO:
raise ValueError(f"Unsupported architecture: {arch}")
reg_info = ARCH_REGISTER_INFO[arch]
# Fixed parts:
# pr_info: 12 bytes
# pr_cursig + padding: 4 bytes
# pr_sigpend + pr_sighold: 16 bytes
# pr_pid + pr_ppid + pr_pgrp + pr_sid: 16 bytes
# pr_utime + pr_stime + pr_cutime + pr_cstime: 64 bytes
# pr_fpvalid: 4 bytes
fixed_size = 12 + 4 + 16 + 16 + 64 + 4
return fixed_size + reg_info.pr_reg_size