Source code for libdebug.architectures.amd64.amd64_stack_unwinder
#
# This file is part of libdebug Python library (https://github.com/libdebug/libdebug).
# Copyright (c) 2023-2024 Gabriele Digregorio, Roberto Alessandro Bertolini, Francesco Panebianco. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#
from __future__ import annotations
from typing import TYPE_CHECKING
from libdebug.architectures.stack_unwinding_manager import StackUnwindingManager
if TYPE_CHECKING:
from libdebug.state.thread_context import ThreadContext
[docs]
class Amd64StackUnwinder(StackUnwindingManager):
"""Class that provides stack unwinding for the x86_64 architecture."""
[docs]
def unwind(self: Amd64StackUnwinder, target: ThreadContext) -> list:
"""Unwind the stack of a process.
Args:
target (ThreadContext): The target ThreadContext.
Returns:
list: A list of return addresses.
"""
assert hasattr(target.regs, "rip")
assert hasattr(target.regs, "rbp")
current_rbp = target.regs.rbp
stack_trace = [target.regs.rip]
vmaps = target._internal_debugger.debugging_interface.maps()
while current_rbp:
try:
# Read the return address
return_address = int.from_bytes(target.memory[current_rbp + 8, 8], byteorder="little")
if not any(vmap.start <= return_address < vmap.end for vmap in vmaps):
break
# Read the previous rbp and set it as the current one
current_rbp = int.from_bytes(target.memory[current_rbp, 8], byteorder="little")
stack_trace.append(return_address)
except (OSError, ValueError):
break
return stack_trace
[docs]
def get_return_address(self: Amd64StackUnwinder, target: ThreadContext) -> int:
"""Get the return address of the current function.
Args:
target (ThreadContext): The target ThreadContext.
Returns:
int: The return address.
"""
instruction_window = target.memory[target.regs.rip, 4]
# Check if the instruction window is a function preamble and handle each case
return_address = None
if self._preamble_state(instruction_window) == 0:
return_address = target.memory[target.regs.rbp + 8, 8]
elif self._preamble_state(instruction_window) == 1:
return_address = target.memory[target.regs.rsp, 8]
else:
return_address = target.memory[target.regs.rsp + 8, 8]
return int.from_bytes(return_address, byteorder="little")
def _preamble_state(self: Amd64StackUnwinder, instruction_window: bytes) -> int:
"""Check if the instruction window is a function preamble and if so at what stage.
Args:
instruction_window (bytes): The instruction window.
Returns:
int: 0 if not a preamble, 1 if rbp has not been pushed yet, 2 otherwise
"""
preamble_state = 0
# endbr64 and push rbp
if b"\xf3\x0f\x1e\xfa" in instruction_window or b"\x55" in instruction_window:
preamble_state = 1
# mov rbp, rsp
elif b"\x48\x89\xe5" in instruction_window:
preamble_state = 2
return preamble_state