Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/dwarffi/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .backend import LiveMemoryProxy
from .dtyping import FlatFieldsDict, MemoryBuffer, StructLike, TypeAccessor, TypeInfoDict, Vtype
from .types import VtypeBaseType, VtypeEnum, VtypeUserType
from .types import VtypeBaseType, VtypeEnum, VtypeFunction, VtypeParameter, VtypeUserType


def _wrap_integer(value: int, size_bytes: int, signed: bool) -> int:
Expand Down Expand Up @@ -1017,7 +1017,44 @@ def points_to_type_name(self) -> str:
return str(self._subtype_info.name)

# If it's a raw ISF dictionary, use .get()
return str(self._subtype_info.get("name", "void"))
if isinstance(self._subtype_info, dict):
if self._subtype_info.get("kind") == "function":
return "function"
return str(self._subtype_info.get("name", "void"))

return "void"

@property
def signature(self) -> Optional[VtypeFunction]:
"""
Returns the function signature (as a VtypeFunction) if this pointer
points to a function, or None otherwise.
"""
if isinstance(self._subtype_info, dict) and self._subtype_info.get("kind") == "function":
ret_info = self._subtype_info.get("return_type", {"kind": "void"})
params_info = self._subtype_info.get("parameters", [])

params = []
for p in params_info:
if isinstance(p, dict) and "type" in p:
p_name = p.get("name", "")
p_type = p["type"]
else:
p_name = ""
p_type = p

param = VtypeParameter(name=p_name, type_info=p_type, _dffi=self._vtype_accessor)
params.append(param)

func = VtypeFunction(
name=self._subtype_info.get("name", ""),
return_type_info=ret_info,
parameters=params,
_dffi=self._vtype_accessor
)
return func

return None

def deref(self) -> Any:
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_e2e_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,4 @@ def test_e2e_function_returning_function_pointer(compiler):

assert inst.get_handler.address == 0xCAFEBABE
# The string representation should gracefully handle the nested type info
assert "void" in inst.get_handler.points_to_type_name
assert "function" in inst.get_handler.points_to_type_name
65 changes: 64 additions & 1 deletion tests/test_e2e_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,67 @@ def test_e2e_deep_function_signature_resolution(compiler):

# Validate the size calculation recursively handles the inner mac_addr_t arrays
# 6 bytes (src) + 6 bytes (dst) + 2 bytes (short) = 14 bytes
assert ffi.sizeof(pkt_struct) == 14
assert ffi.sizeof(pkt_struct) == 14

@pytest.mark.parametrize("compiler", AVAILABLE_COMPILERS)
def test_e2e_struct_function_pointer_member(compiler):
"""
Tests dynamic extraction of function signatures from members of a struct
using a simulated file_operations jump table.
"""
ffi = DFFI()
ffi.cdef(
"""
typedef long long loff_t;
struct file {
loff_t f_pos;
};

struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
};

void __attribute__((used)) _force_keep(struct file_operations f) {}
""",
compiler=compiler
)

if not ffi.functions and not ffi.types.get("file_operations"):
pytest.skip("System dwarf2json does not support custom function signatures or type resolution failed. Skipping.")

inst = ffi.new("struct file_operations")

# 1. Inspect llseek signature
llseek_ptr = inst.llseek
assert llseek_ptr.points_to_type_name == "function"

sig1 = llseek_ptr.signature
if not sig1:
pytest.skip("dwarf2json version does not output function signatures for pointers. Skipping.")

# Check if this dwarf2json version outputs full signature details
if not sig1.args:
pytest.skip("dwarf2json version does not output function signature parameters for pointers. Skipping.")

assert sig1.return_type_info.get("name") in ("loff_t", "long long", "long long int")
assert len(sig1.args) == 3

# Arg 0: struct file *
assert sig1.args[0].type_info["kind"] == "pointer"
assert sig1.args[0].type_info["subtype"]["name"] == "file"

# Arg 1: loff_t
assert sig1.args[1].type_info["name"] in ("loff_t", "long long", "long long int")

# Arg 2: int
assert sig1.args[2].type_info["name"] == "int"

# 2. Inspect unlocked_ioctl signature
ioctl_ptr = inst.unlocked_ioctl
sig2 = ioctl_ptr.signature
assert sig2 is not None
assert sig2.return_type_info.get("name") in ("long", "long int")
assert len(sig2.args) == 3
assert sig2.args[1].type_info["name"] == "unsigned int"
assert sig2.args[2].type_info["name"] in ("unsigned long", "long unsigned int")
155 changes: 155 additions & 0 deletions tests/test_struct_function_pointers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import pytest

from dwarffi import DFFI


@pytest.fixture
def mock_isf_with_function_pointer():
"""A mock ISF dictionary containing a struct with an anonymous function pointer."""
return {
"metadata": {"format": "6.2.0", "producer": {"name": "test", "version": "1.0"}},
"base_types": {
"int": {"size": 4, "signed": True, "kind": "int", "endian": "little"},
"loff_t": {"size": 8, "signed": True, "kind": "int", "endian": "little"},
"pointer": {"size": 8, "signed": False, "kind": "pointer", "endian": "little"}
},
"user_types": {
"file": {
"kind": "struct",
"size": 16,
"fields": {
"f_pos": {"offset": 0, "type": {"kind": "base", "name": "loff_t"}}
}
},
"file_operations": {
"kind": "struct",
"size": 8,
"fields": {
"proc_lseek": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "function",
"return_type": {"kind": "base", "name": "loff_t"},
"parameters": [
{"kind": "pointer", "subtype": {"kind": "struct", "name": "file"}},
{"kind": "base", "name": "loff_t"},
{"name": "whence", "type": {"kind": "base", "name": "int"}}
]
}
}
}
}
}
},
"enums": {},
"symbols": {},
"functions": {}
}

def test_struct_function_pointer_introspection(mock_isf_with_function_pointer):
"""Verify that function pointers inside structs can be correctly introspected."""
ffi = DFFI(mock_isf_with_function_pointer)

# Create an instance
fops = ffi.new("struct file_operations")
ptr = fops.proc_lseek

# Verify points_to_type_name
assert ptr.points_to_type_name == "function"
assert repr(ptr) == "<Ptr to function at 0x0>"

# Verify signature extraction
sig = ptr.signature
assert sig is not None
assert sig.return_type_info["name"] == "loff_t"

# Verify parameters
assert len(sig.args) == 3

# First argument has no name, but valid type
assert sig.args[0].name == ""
assert sig.args[0].type_info["kind"] == "pointer"

# Second argument has no name, but valid type
assert sig.args[1].name == ""
assert sig.args[1].type_info["name"] == "loff_t"

# Third argument has both name and type
assert sig.args[2].name == "whence"
assert sig.args[2].type_info["name"] == "int"

def test_struct_function_pointer_legacy_compat():
"""Verify that an older ISF without return_type or parameters doesn't break."""
isf = {
"metadata": {"format": "6.2.0"},
"base_types": {
"pointer": {"size": 8, "signed": False, "kind": "pointer", "endian": "little"}
},
"user_types": {
"legacy_struct": {
"kind": "struct",
"size": 8,
"fields": {
"old_func": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "function"
}
}
}
}
}
},
"enums": {},
"symbols": {},
}
ffi = DFFI(isf)
inst = ffi.new("struct legacy_struct")

ptr = inst.old_func
assert ptr.points_to_type_name == "function"

sig = ptr.signature
assert sig is not None
assert sig.return_type_info == {"kind": "void"}
assert len(sig.args) == 0

def test_non_function_pointer_signature():
"""Verify that a non-function pointer returns None for signature."""
isf = {
"metadata": {"format": "6.2.0"},
"base_types": {
"int": {"size": 4, "signed": True, "kind": "int", "endian": "little"},
"pointer": {"size": 8, "signed": False, "kind": "pointer", "endian": "little"}
},
"user_types": {
"my_struct": {
"kind": "struct",
"size": 8,
"fields": {
"ptr": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "int"
}
}
}
}
}
},
"enums": {},
"symbols": {},
}
ffi = DFFI(isf)
inst = ffi.new("struct my_struct")

ptr = inst.ptr
assert ptr.points_to_type_name == "int"
assert ptr.signature is None
Loading