about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md21
-rw-r--r--focaccia/arch/__init__.py11
-rw-r--r--focaccia/arch/aarch64.py55
-rw-r--r--focaccia/arch/arch.py8
-rw-r--r--focaccia/lldb_target.py85
-rw-r--r--focaccia/miasm_util.py55
-rw-r--r--focaccia/snapshot.py6
-rw-r--r--focaccia/symbolic.py47
-rw-r--r--nix.shell12
-rw-r--r--tools/_qemu_tool.py81
10 files changed, 293 insertions, 88 deletions
diff --git a/README.md b/README.md
index 5c14d91..67db62c 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ The `tools/` directory contains additional utility scripts to work with focaccia
 
 ## Project Overview (for developers)
 
-### Snapshot-comparison framework
+### Snapshots and comparison
 
 The following files belong to a rough framework for the snapshot comparison engine:
 
@@ -43,8 +43,7 @@ representation of program snapshots.
 
  - `focaccia/compare.py`: The central algorithms that work on snapshots.
 
- - `focaccia/arch/`: Abstractions over different processor architectures. Will be used to integrate support for more
-architectures later. Currently, we only have X86.
+ - `focaccia/arch/`: Abstractions over different processor architectures. Currently we have x86 and aarch64.
 
 ### Concolic execution
 
@@ -67,4 +66,18 @@ our own log format.
 
  - `focaccia/match.py`: Algorithms for trace matching.
 
- - `miasm_test.py`: A test script that traces a program concolically.
+### Supporting new architectures
+
+To add support for an architecture <arch>, do the following:
+
+ - Add a file `focaccia/arch/<arch>.py`. This module declares the architecture's description, such as register names and
+an architecture class. The convention is to declare state flags (e.g. flags in RFLAGS for x86) as separate registers.
+
+ - Add the class to the `supported_architectures` dict in `focaccia/arch/__init__.py`.
+
+ - Depending on Miasm's support for <arch>, add register name aliases to the `MiasmSymbolResolver.miasm_flag_aliases`
+dict in `focaccia/miasm_util.py`.
+
+ - Depending on the existence of a flags register in <arch>, implement conversion from the flags register's value to
+values of single logical flags (e.g. implement the operation `RFLAGS['OF']`) in the respective concrete targets (LLDB,
+GDB, ...).
diff --git a/focaccia/arch/__init__.py b/focaccia/arch/__init__.py
index 2926d20..1797176 100644
--- a/focaccia/arch/__init__.py
+++ b/focaccia/arch/__init__.py
@@ -1,11 +1,14 @@
 from .arch import Arch
-from . import x86
+from . import x86, aarch64
 
+supported_architectures: dict[str, Arch] = {
+    'x86_64': x86.ArchX86(),
+    'aarch64': aarch64.ArchAArch64('little'),
+    'aarch64l': aarch64.ArchAArch64('little'),
+    'aarch64b': aarch64.ArchAArch64('big'),
+}
 """A dictionary containing all supported architectures at their names.
 
 The arch names (keys) should be compatible with the string returned from
 `platform.machine()`.
 """
-supported_architectures: dict[str, Arch] = {
-    "x86_64": x86.ArchX86(),
-}
diff --git a/focaccia/arch/aarch64.py b/focaccia/arch/aarch64.py
new file mode 100644
index 0000000..c0efc1a
--- /dev/null
+++ b/focaccia/arch/aarch64.py
@@ -0,0 +1,55 @@
+"""Description of 64-bit ARM."""
+from typing import Literal
+
+from .arch import Arch
+
+archname = 'aarch64'
+
+regnames = [
+    'PC', 'SP', 'LR',
+    'CPSR',
+
+    'X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9',
+    'X10', 'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19',
+    'X20', 'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29',
+    'X30',
+
+    'Q0', 'Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7', 'Q8', 'Q9',
+    'Q10', 'Q11', 'Q12', 'Q13', 'Q14', 'Q15',
+
+    'V0', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9',
+    'V10', 'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19',
+    'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'V29',
+    'V30', 'V31',
+
+    'N', 'Z', 'C', 'V', 'Q',
+    'SSBS', 'PAN', 'DIT', 'GE',
+    'E', 'A', 'I', 'F', 'M',
+]
+
+def decompose_cpsr(cpsr: int) -> dict[str, int]:
+    """Extract individual flag values from the CPSR register."""
+    return {
+        'N':    (cpsr & (1 << 31)) != 0,
+        'Z':    (cpsr & (1 << 30)) != 0,
+        'C':    (cpsr & (1 << 29)) != 0,
+        'V':    (cpsr & (1 << 28)) != 0,
+        'Q':    (cpsr & (1 << 27)) != 0,
+        # Reserved: [26:24]
+        'SSBS': (cpsr & (1 << 23)) != 0,
+        'PAN':  (cpsr & (1 << 22)) != 0,
+        'DIT':  (cpsr & (1 << 21)) != 0,
+        # Reserved: [20]
+        'GE':   (cpsr & (0b1111 << 16)) != 0,
+        # Reserved: [15:10]
+        'E':    (cpsr & (1 << 9)) != 0,
+        'A':    (cpsr & (1 << 8)) != 0,
+        'I':    (cpsr & (1 << 7)) != 0,
+        'F':    (cpsr & (1 << 6)) != 0,
+        # Reserved: [5:4]
+        'M':    (cpsr & 0b1111) != 0,
+    }
+
+class ArchAArch64(Arch):
+    def __init__(self, endianness: Arch.Endianness):
+        super().__init__(archname, regnames, 64, endianness)
diff --git a/focaccia/arch/arch.py b/focaccia/arch/arch.py
index e64f1fa..dee7288 100644
--- a/focaccia/arch/arch.py
+++ b/focaccia/arch/arch.py
@@ -1,13 +1,17 @@
-from typing import Iterable
+from typing import Iterable, Literal
 
 class Arch():
+    Endianness = Literal['little', 'big']
+
     def __init__(self,
                  archname: str,
                  regnames: Iterable[str],
-                 ptr_size: int):
+                 ptr_size: int,
+                 endianness: Endianness = 'little'):
         self.archname = archname
         self.regnames = set(name.upper() for name in regnames)
         self.ptr_size = ptr_size
+        self.endianness: Literal['little', 'big'] = endianness
 
     def to_regname(self, name: str) -> str | None:
         """Transform a string into a standard register name.
diff --git a/focaccia/lldb_target.py b/focaccia/lldb_target.py
index a4e96a8..2ed0757 100644
--- a/focaccia/lldb_target.py
+++ b/focaccia/lldb_target.py
@@ -2,7 +2,7 @@ import os
 
 import lldb
 
-from .arch import supported_architectures, x86
+from .arch import supported_architectures
 from .snapshot import ProgramState
 
 class MemoryMap:
@@ -32,6 +32,18 @@ class ConcreteSectionError(Exception):
     pass
 
 class LLDBConcreteTarget:
+    from focaccia.arch import aarch64, x86
+
+    flag_register_names = {
+        aarch64.archname: 'cpsr',
+        x86.archname: 'rflags',
+    }
+
+    flag_register_decompose = {
+        aarch64.archname: aarch64.decompose_cpsr,
+        x86.archname: x86.decompose_rflags,
+    }
+
     def __init__(self,
                  executable: str,
                  argv: list[str] = [],
@@ -67,7 +79,14 @@ class LLDBConcreteTarget:
             raise RuntimeError(f'[In LLDBConcreteTarget.__init__]: Failed to'
                                f' launch process.')
 
+        # Determine current arch
         self.archname = self.target.GetPlatform().GetTriple().split('-')[0]
+        if self.archname not in supported_architectures:
+            err = f'LLDBConcreteTarget: Architecture {self.archname} is not' \
+                  f' supported by Focaccia.'
+            print(f'[ERROR] {err}')
+            raise NotImplementedError(err)
+        self.arch = supported_architectures[self.archname]
 
     def is_exited(self):
         """Signals whether the concrete process has exited.
@@ -99,28 +118,17 @@ class LLDBConcreteTarget:
 
     def record_snapshot(self) -> ProgramState:
         """Record the concrete target's state in a ProgramState object."""
-        # Determine current arch
-        if self.archname not in supported_architectures:
-            print(f'[ERROR] LLDBConcreteTarget: Recording snapshots is not'
-                  f' supported for architecture {self.archname}!')
-            raise NotImplementedError()
-        arch = supported_architectures[self.archname]
-
-        state = ProgramState(arch)
+        state = ProgramState(self.arch)
 
         # Query and store register state
-        for regname in arch.regnames:
+        for regname in self.arch.regnames:
             try:
                 conc_val = self.read_register(regname)
                 state.set_register(regname, conc_val)
             except KeyError:
                 pass
             except ConcreteRegisterError:
-                # Special rule for flags on X86
-                if arch.archname == x86.archname:
-                    rflags = x86.decompose_rflags(self.read_register('rflags'))
-                    if regname in rflags:
-                        state.set_register(regname, rflags[regname])
+                pass
 
         # Query and store memory state
         for mapping in self.get_mappings():
@@ -142,12 +150,35 @@ class LLDBConcreteTarget:
         """
         frame = self.process.GetThreadAtIndex(0).GetFrameAtIndex(0)
         reg = frame.FindRegister(regname)
-        if reg is None:
+        if not reg.IsValid():
             raise ConcreteRegisterError(
                 f'[In LLDBConcreteTarget._get_register]: Register {regname}'
                 f' not found.')
         return reg
 
+    def read_flags(self) -> dict[str, int | bool]:
+        """Read the current state flags.
+
+        If the concrete target's architecture has state flags, read and return
+        their current values.
+
+        This handles the conversion from implementation details like flags
+        registers to the logical flag values. For example: On X86, this reads
+        the RFLAGS register and extracts the flag bits from its value.
+
+        :return: Dictionary mapping flag names to values. The values may be
+                 booleans in the case of true binary flags or integers in the
+                 case of multi-byte flags. Is empty if the current architecture
+                 does not have state flags of the access is not implemented for
+                 it.
+        """
+        if self.archname not in self.flag_register_names:
+            return {}
+
+        flags_reg = self.flag_register_names[self.archname]
+        flags_val = self._get_register(flags_reg).GetValueAsUnsigned()
+        return self.flag_register_decompose[self.archname](flags_val)
+
     def read_register(self, regname: str) -> int:
         """Read the value of a register.
 
@@ -155,14 +186,17 @@ class LLDBConcreteTarget:
                                       or the target is otherwise unable to read
                                       the register's value.
         """
-        reg = self._get_register(regname)
-        val = reg.GetValue()
-        if val is None:
+        try:
+            reg = self._get_register(regname)
+            assert(reg.IsValid())
+            return reg.GetValueAsUnsigned()
+        except ConcreteRegisterError as err:
+            flags = self.read_flags()
+            if regname in flags:
+                return flags[regname]
             raise ConcreteRegisterError(
-                f'[In LLDBConcreteTarget.read_register]: Register has an'
-                f' invalid value of {val}.')
-
-        return int(val, 16)
+                f'[In LLDBConcreteTarget.read_register]: Unable to read'
+                f' register {regname}: {err}')
 
     def write_register(self, regname: str, value: int):
         """Read the value of a register.
@@ -189,7 +223,10 @@ class LLDBConcreteTarget:
         if not err.success:
             raise ConcreteMemoryError(f'Error when reading {size} bytes at'
                                       f' address {hex(addr)}: {err}')
-        return bytes(reversed(content))  # Convert to big endian
+        if self.arch.endianness == 'little':
+            return content
+        else:
+            return bytes(reversed(content))
 
     def write_memory(self, addr, value: bytes):
         """Write bytes to memory.
diff --git a/focaccia/miasm_util.py b/focaccia/miasm_util.py
index 83a8778..a2cd025 100644
--- a/focaccia/miasm_util.py
+++ b/focaccia/miasm_util.py
@@ -1,14 +1,26 @@
 from typing import Callable
 
+from miasm.analysis.machine import Machine
 from miasm.core.locationdb import LocationDB, LocKey
 from miasm.expression.expression import Expr, ExprOp, ExprId, ExprLoc, \
                                         ExprInt, ExprMem, ExprCompose, \
                                         ExprSlice, ExprCond
 from miasm.expression.simplifications import expr_simp_explicit
 
+from . import arch
+from .arch import Arch
 from .snapshot import ReadableProgramState, \
                       RegisterAccessError, MemoryAccessError
 
+def make_machine(_arch: Arch) -> Machine:
+    """Create a Miasm `Machine` object corresponding to an `Arch`."""
+    machines = {
+        arch.x86.archname: lambda _: Machine('x86_64'),
+        # Miasm only has ARM machine names with the l/b suffix:
+        arch.aarch64.archname: lambda a: Machine(f'aarch64{a.endianness[0]}'),
+    }
+    return machines[_arch.archname](_arch)
+
 def simp_segm(expr_simp, expr: ExprOp):
     """Simplify a segmentation expression to an addition of the segment
     register's base value and the address argument.
@@ -60,27 +72,50 @@ class MiasmSymbolResolver:
     """Resolves atomic symbols to some state."""
 
     miasm_flag_aliases = {
-        'NF':     'SF',
-        'I_F':    'IF',
-        'IOPL_F': 'IOPL',
-        'I_D':    'ID',
+        arch.x86.archname: {
+            'NF':     'SF',
+            'I_F':    'IF',
+            'IOPL_F': 'IOPL',
+            'I_D':    'ID',
+        },
+        arch.aarch64.archname: {
+            'NF': 'N',
+            'SF': 'N',
+            'ZF': 'Z',
+            'CF': 'C',
+            'VF': 'V',
+            'OF': 'V',
+            'QF': 'Q',
+
+            'AF': 'A',
+            'EF': 'E',
+            'IF': 'I',
+            'FF': 'F',
+        }
     }
 
-    def __init__(self, state: ReadableProgramState, loc_db: LocationDB):
+    def __init__(self,
+                 state: ReadableProgramState,
+                 loc_db: LocationDB):
         self._state = state
         self._loc_db = loc_db
+        self._arch = state.arch
+        self.endianness: Arch.Endianness = self._arch.endianness
 
-    @staticmethod
-    def _miasm_to_regname(regname: str) -> str:
+    def _miasm_to_regname(self, regname: str) -> str:
         """Convert a register name as used by Miasm to one that follows
         Focaccia's naming conventions."""
         regname = regname.upper()
-        return MiasmSymbolResolver.miasm_flag_aliases.get(regname, regname)
+        if self._arch.archname in self.miasm_flag_aliases:
+            aliases = self.miasm_flag_aliases[self._arch.archname]
+            return aliases.get(regname, regname)
+        return regname
 
     def resolve_register(self, regname: str) -> int | None:
         try:
             return self._state.read_register(self._miasm_to_regname(regname))
-        except RegisterAccessError:
+        except RegisterAccessError as err:
+            print(f'Not a register: {regname} ({err})')
             return None
 
     def resolve_memory(self, addr: int, size: int) -> bytes | None:
@@ -164,7 +199,7 @@ def _eval_exprmem(expr: ExprMem, state: MiasmSymbolResolver):
         return expr
 
     assert(len(mem) * 8 == expr.size)
-    return ExprInt(int.from_bytes(mem, byteorder='big'), expr.size)
+    return ExprInt(int.from_bytes(mem, byteorder=state.endianness), expr.size)
 
 def _eval_exprcond(expr, state: MiasmSymbolResolver):
     """Evaluate an ExprCond using the current state"""
diff --git a/focaccia/snapshot.py b/focaccia/snapshot.py
index 104ca0a..506cf27 100644
--- a/focaccia/snapshot.py
+++ b/focaccia/snapshot.py
@@ -80,7 +80,9 @@ class SparseMemory:
         assert(len(data) == offset)  # Exactly all data was written
 
 class ReadableProgramState:
-    """Interface for read-only program states. Used for typing purposes."""
+    """Interface for read-only program states."""
+    def __init__(self, arch: Arch):
+        self.arch = arch
 
     def read_register(self, reg: str) -> int:
         """Read a register's value.
@@ -106,7 +108,7 @@ class ReadableProgramState:
 class ProgramState(ReadableProgramState):
     """A snapshot of the program's state."""
     def __init__(self, arch: Arch):
-        self.arch = arch
+        super().__init__(arch=arch)
 
         self.regs: dict[str, int | None] = {reg: None for reg in arch.regnames}
         self.mem = SparseMemory()
diff --git a/focaccia/symbolic.py b/focaccia/symbolic.py
index dfc2966..d5475a6 100644
--- a/focaccia/symbolic.py
+++ b/focaccia/symbolic.py
@@ -17,7 +17,7 @@ from .arch import Arch, supported_architectures
 from .lldb_target import LLDBConcreteTarget, \
                          ConcreteRegisterError, \
                          ConcreteMemoryError
-from .miasm_util import MiasmSymbolResolver, eval_expr
+from .miasm_util import MiasmSymbolResolver, eval_expr, make_machine
 from .snapshot import ProgramState, ReadableProgramState, \
                       RegisterAccessError, MemoryAccessError
 from .trace import Trace, TraceEnvironment
@@ -80,7 +80,7 @@ class Instruction:
     @staticmethod
     def from_bytecode(asm: bytes, arch: Arch) -> Instruction:
         """Disassemble an instruction."""
-        machine = Machine(arch.archname)
+        machine = make_machine(arch)
         assert(machine.mn is not None)
         _instr = machine.mn.dis(asm, arch.ptr_size)
         return Instruction(_instr, machine, arch, None)
@@ -244,14 +244,15 @@ class SymbolicTransform:
         accessed_regs = set[str]()
 
         class RegisterCollector(MiasmSymbolResolver):
-            def __init__(self): pass
+            def __init__(self, arch: Arch):
+                self._arch = arch  # MiasmSymbolResolver needs this
             def resolve_register(self, regname: str) -> int | None:
                 accessed_regs.add(self._miasm_to_regname(regname))
                 return None
             def resolve_memory(self, addr: int, size: int): pass
             def resolve_location(self, loc): assert(False)
 
-        resolver = RegisterCollector()
+        resolver = RegisterCollector(self.arch)
         for expr in self.changed_regs.values():
             eval_expr(expr, resolver)
         for addr_expr, mem_expr in self.changed_mem.items():
@@ -340,7 +341,8 @@ class SymbolicTransform:
         for addr, expr in self.changed_mem.items():
             addr = eval_symbol(addr, conc_state)
             length = int(expr.size / 8)
-            res[addr] = eval_symbol(expr, conc_state).to_bytes(length, byteorder='big')
+            res[addr] = eval_symbol(expr, conc_state) \
+                        .to_bytes(length, byteorder=self.arch.endianness)
         return res
 
     @classmethod
@@ -352,8 +354,13 @@ class SymbolicTransform:
         from miasm.expression.parser import str_to_expr as parse
 
         def decode_inst(obj: Iterable[int], arch: Arch):
-            b = b''.join(i.to_bytes(1, byteorder='big') for i in obj)
-            return Instruction.from_bytecode(b, arch)
+            b = b''.join(i.to_bytes(1) for i in obj)
+            try:
+                return Instruction.from_bytecode(b, arch)
+            except Exception as err:
+                warn(f'[In SymbolicTransform.from_json] Unable to disassemble'
+                     f' bytes {obj}: {err}.')
+                return None
 
         arch = supported_architectures[data['arch']]
         start_addr = int(data['from_addr'])
@@ -362,7 +369,8 @@ class SymbolicTransform:
         t = SymbolicTransform({}, [], arch, start_addr, end_addr)
         t.changed_regs = { name: parse(val) for name, val in data['regs'].items() }
         t.changed_mem = { parse(addr): parse(val) for addr, val in data['mem'].items() }
-        t.instructions = [decode_inst(b, arch) for b in data['instructions']]
+        instrs = [decode_inst(b, arch) for b in data['instructions']]
+        t.instructions = [inst for inst in instrs if inst is not None]
 
         # Recover the instructions' address information
         addr = t.addr
@@ -375,13 +383,21 @@ class SymbolicTransform:
     def to_json(self) -> dict:
         """Serialize a symbolic transformation as a JSON object."""
         def encode_inst(inst: Instruction):
-            return [int(b) for b in inst.to_bytecode()]
+            try:
+                return [int(b) for b in inst.to_bytecode()]
+            except Exception as err:
+                warn(f'[In SymbolicTransform.to_json] Unable to assemble'
+                     f' "{inst}" to bytecode: {err}. This instruction will not'
+                     f' be serialized.')
+                return None
 
+        instrs = [encode_inst(inst) for inst in self.instructions]
+        instrs = [inst for inst in instrs if inst is not None]
         return {
             'arch': self.arch.archname,
             'from_addr': self.range[0],
             'to_addr': self.range[1],
-            'instructions': [encode_inst(inst) for inst in self.instructions],
+            'instructions': instrs,
             'regs': { name: repr(expr) for name, expr in self.changed_regs.items() },
             'mem': { repr(addr): repr(val) for addr, val in self.changed_mem.items() },
         }
@@ -554,24 +570,17 @@ class _LLDBConcreteState(ReadableProgramState):
     allows us instead to read values from LLDB on demand.
     """
     def __init__(self, target: LLDBConcreteTarget, arch: Arch):
+        super().__init__(arch)
         self._target = target
-        self._arch = arch
 
     def read_register(self, reg: str) -> int:
-        from focaccia.arch import x86
-
-        regname = self._arch.to_regname(reg)
+        regname = self.arch.to_regname(reg)
         if regname is None:
             raise RegisterAccessError(reg, f'Not a register name: {reg}')
 
         try:
             return self._target.read_register(regname)
         except ConcreteRegisterError:
-            # Special case for X86
-            if self._arch.archname == x86.archname:
-                rflags = x86.decompose_rflags(self._target.read_register('rflags'))
-                if regname in rflags:
-                    return rflags[regname]
             raise RegisterAccessError(regname, '')
 
     def read_memory(self, addr: int, size: int) -> bytes:
diff --git a/nix.shell b/nix.shell
new file mode 100644
index 0000000..00fef51
--- /dev/null
+++ b/nix.shell
@@ -0,0 +1,12 @@
+{ pkgs ? import <nixpkgs> {} }:
+pkgs.mkShell {
+    nativeBuildInputs = with pkgs; [
+        python311
+        python311Packages.pip
+        virtualenv
+
+        gcc gnumake binutils cmake ninja pkg-config
+        musl qemu swig4
+        gdb
+    ];
+}
diff --git a/tools/_qemu_tool.py b/tools/_qemu_tool.py
index 38715dc..e3341ad 100644
--- a/tools/_qemu_tool.py
+++ b/tools/_qemu_tool.py
@@ -10,51 +10,86 @@ import gdb
 from typing import Iterable
 
 import focaccia.parser as parser
-from focaccia.arch import supported_architectures
+from focaccia.arch import supported_architectures, Arch
 from focaccia.compare import compare_symbolic
 from focaccia.snapshot import ProgramState, ReadableProgramState, \
                               RegisterAccessError, MemoryAccessError
 from focaccia.symbolic import SymbolicTransform, eval_symbol, ExprMem
 from focaccia.trace import Trace, TraceEnvironment
-from focaccia.utils import get_envp, print_result
+from focaccia.utils import print_result
 
 from verify_qemu import make_argparser, verbosity
 
 class GDBProgramState(ReadableProgramState):
-    def __init__(self, process: gdb.Inferior, frame: gdb.Frame):
+    from focaccia.arch import aarch64, x86
+
+    flag_register_names = {
+        aarch64.archname: 'cpsr',
+        x86.archname: 'eflags',
+    }
+
+    flag_register_decompose = {
+        aarch64.archname: aarch64.decompose_cpsr,
+        x86.archname: x86.decompose_rflags,
+    }
+
+    def __init__(self, process: gdb.Inferior, frame: gdb.Frame, arch: Arch):
+        super().__init__(arch)
         self._proc = process
         self._frame = frame
 
+    @staticmethod
+    def _read_vector_reg_aarch64(val, size) -> int:
+        return int(str(val['u']), 10)
+
+    @staticmethod
+    def _read_vector_reg_x86(val, size) -> int:
+        num_longs = size // 64
+        vals = val[f'v{num_longs}_int64']
+        res = 0
+        for i in range(num_longs):
+            val = int(vals[i].cast(gdb.lookup_type('unsigned long')))
+            res += val << i * 64
+        return res
+
+    read_vector_reg = {
+        aarch64.archname: _read_vector_reg_aarch64,
+        x86.archname: _read_vector_reg_x86,
+    }
+
     def read_register(self, reg: str) -> int:
         try:
             val = self._frame.read_register(reg.lower())
             size = val.type.sizeof * 8
 
-            # For vector registers, we have to assemble Python's
-            # arbitrary-length integers from GDB's fixed-size integers
-            # ourselves:
+            # For vector registers, we need to apply architecture-specific
+            # logic because GDB's interface is not consistent.
             if size > 64:  # Value is a vector
-                num_longs = size // 64
-                vals = val[f'v{num_longs}_int64']
-                res = 0
-                for i in range(num_longs):
-                    val = int(vals[i].cast(gdb.lookup_type('unsigned long')))
-                    res += val << i * 64
-                return res
+                if self.arch.archname not in self.read_vector_reg:
+                    raise NotImplementedError(
+                        f'Reading vector registers is not implemented for'
+                        f' architecture {self.arch.archname}.')
+                return self.read_vector_reg[self.arch.archname](val, size)
             # For non-vector values, just return the 64-bit value
             return int(val.cast(gdb.lookup_type('unsigned long')))
         except ValueError as err:
-            from focaccia.arch import x86
-            rflags = int(self._frame.read_register('eflags'))
-            rflags = x86.decompose_rflags(rflags)
-            if reg in rflags:
-                return rflags[reg]
-            raise RegisterAccessError(reg, str(err))
+            # Try to access the flags register with `reg` as a logical flag name
+            if self.arch.archname in self.flag_register_names:
+                flags_reg = self.flag_register_names[self.arch.archname]
+                flags = int(self._frame.read_register(flags_reg))
+                flags = self.flag_register_decompose[self.arch.archname](flags)
+                if reg in flags:
+                    return flags[reg]
+            raise RegisterAccessError(reg,
+                                      f'[GDB] Unable to access {reg}: {err}')
 
     def read_memory(self, addr: int, size: int) -> bytes:
         try:
             mem = self._proc.read_memory(addr, size).tobytes()
-            return bytes(reversed(mem))  # Convert to big endian
+            if self.arch.endianness == 'little':
+                return mem
+            else:
+                return bytes(reversed(mem))  # Convert to big endian
         except gdb.MemoryError as err:
             raise MemoryAccessError(addr, size, str(err))
 
@@ -87,7 +122,7 @@ class GDBServerStateIterator:
         # i.e. before stepping the first time
         if self._first_next:
             self._first_next = False
-            return GDBProgramState(self._process, gdb.selected_frame())
+            return GDBProgramState(self._process, gdb.selected_frame(), self.arch)
 
         # Step
         pc = gdb.selected_frame().read_register('pc')
@@ -98,7 +133,7 @@ class GDBServerStateIterator:
                 raise StopIteration
             new_pc = gdb.selected_frame().read_register('pc')
 
-        return GDBProgramState(self._process, gdb.selected_frame())
+        return GDBProgramState(self._process, gdb.selected_frame(), self.arch)
 
 def record_minimal_snapshot(prev_state: ReadableProgramState,
                             cur_state: ReadableProgramState,
@@ -140,8 +175,8 @@ def record_minimal_snapshot(prev_state: ReadableProgramState,
                            resolved relative to this state.
         """
         for regname in regs:
-            regval = cur_state.read_register(regname)
             try:
+                regval = cur_state.read_register(regname)
                 out_state.set_register(regname, regval)
             except RegisterAccessError:
                 pass