# Copyright (c) Meta Platforms, Inc. and affiliates. # SPDX-License-Identifier: LGPL-2.1-or-later import math import operator import struct from drgn import ( FaultError, Object, ObjectAbsentError, OutOfBoundsError, Qualifiers, TypeMember, cast, reinterpret, sizeof, ) from tests import ( MockMemorySegment, MockProgramTestCase, assertReprPrettyEqualsStr, mock_program, ) class TestInit(MockProgramTestCase): def test_type_stays_alive(self): obj = Object(self.prog, self.prog.int_type("int", 4, True), value=0) self.assertIdentical(obj.type_, self.prog.int_type("int", 4, True)) type_ = obj.type_ del obj self.assertIdentical(type_, self.prog.int_type("int", 4, True)) def test_type(self): self.assertRaisesRegex( TypeError, "type must be Type, str, or None", Object, self.prog, 1, value=0 ) self.assertRaisesRegex( ValueError, "reference must have type", Object, self.prog, address=0 ) self.assertRaisesRegex( ValueError, "absent object must have type", Object, self.prog ) def test_address_nand_value(self): self.assertRaisesRegex( ValueError, "object cannot have address and value", Object, self.prog, "int", 0, address=0, ) self.assertRaisesRegex( ValueError, "object cannot have address and value", Object, self.prog, "int", value=0, address=0, ) def test_integer_address(self): self.assertRaises(TypeError, Object, self.prog, "int", address="NULL") def test_bit_field_size(self): self.assertRaises( TypeError, Object, self.prog, "int", address=0, bit_field_size="1" ) self.assertRaisesRegex( ValueError, "bit field size cannot be zero", Object, self.prog, "int", address=0, bit_field_size=0, ) def test_bit_offset(self): self.assertRaisesRegex( ValueError, "value cannot have bit offset", Object, self.prog, "int", value=0, bit_offset=4, ) self.assertRaisesRegex( ValueError, "value cannot have bit offset", Object, self.prog, self.point_type, value={}, bit_offset=4, ) self.assertRaisesRegex( ValueError, "absent object cannot have bit offset", Object, self.prog, "int", bit_offset=4, ) def test_integer_size(self): self.assertRaisesRegex( ValueError, "unsupported integer bit size", Object, self.prog, self.prog.int_type("ZERO", 0, True), ) self.assertRaisesRegex( ValueError, "unsupported integer bit size", Object, self.prog, self.prog.int_type("BIGGEST", 1024**3, True), ) def test_float_size(self): self.assertRaisesRegex( ValueError, "unsupported floating-point bit size", Object, self.prog, self.prog.float_type("ZERO", 0), ) self.assertRaisesRegex( ValueError, "unsupported floating-point bit size", Object, self.prog, self.prog.float_type("BIGGEST", 32 + 1), ) class TestReference(MockProgramTestCase): def test_basic(self): self.add_memory_segment((1000).to_bytes(4, "little"), virt_addr=0xFFFF0000) obj = Object(self.prog, "int", address=0xFFFF0000) self.assertIs(obj.prog_, self.prog) self.assertIdentical(obj.type_, self.prog.type("int")) self.assertFalse(obj.absent_) self.assertEqual(obj.address_, 0xFFFF0000) self.assertEqual(obj.bit_offset_, 0) self.assertIsNone(obj.bit_field_size_) self.assertEqual(obj.value_(), 1000) self.assertEqual(repr(obj), "Object(prog, 'int', address=0xffff0000)") self.assertIdentical(obj.read_(), Object(self.prog, "int", value=1000)) obj = Object( self.prog, self.prog.int_type("sbe32", 4, True, "big"), address=0xFFFF0000 ) self.assertEqual(obj.value_(), -402456576) obj = Object(self.prog, "unsigned int", address=0xFFFF0000, bit_field_size=4) self.assertEqual(obj.bit_offset_, 0) self.assertEqual(obj.bit_field_size_, 4) self.assertEqual(obj.value_(), 8) self.assertEqual( repr(obj), "Object(prog, 'unsigned int', address=0xffff0000, bit_field_size=4)", ) self.assertRaises(TypeError, sizeof, obj) obj = Object( self.prog, "unsigned int", address=0xFFFF0000, bit_field_size=4, bit_offset=4, ) self.assertEqual(obj.bit_offset_, 4) self.assertEqual(obj.bit_field_size_, 4) self.assertEqual(obj.value_(), 14) self.assertEqual( repr(obj), "Object(prog, 'unsigned int', address=0xffff0000, bit_offset=4, bit_field_size=4)", ) def test_overflow(self): Object(self.prog, "char", address=0xFFFFFFFFFFFFFFFF) Object( self.prog, "char", address=0xFFFFFFFFFFFFFFFF, bit_field_size=1, bit_offset=7, ) def test_read_unsigned(self): value = 12345678912345678989 for bit_size in range(1, 65): for bit_offset in range(8): size = (bit_size + bit_offset + 7) // 8 size_mask = (1 << (8 * size)) - 1 for byteorder in ["little", "big"]: if byteorder == "little": tmp = value << bit_offset else: tmp = value << (8 - bit_size - bit_offset) % 8 tmp &= size_mask buf = tmp.to_bytes(size, byteorder) prog = mock_program(segments=[MockMemorySegment(buf, 0)]) obj = Object( prog, prog.int_type("unsigned long long", 8, False, byteorder), address=0, bit_field_size=bit_size, bit_offset=bit_offset, ) self.assertEqual(obj.value_(), value & ((1 << bit_size) - 1)) def test_read_float(self): pi32 = struct.unpack("f", struct.pack("f", math.pi))[0] for bit_size in [32, 64]: for bit_offset in range(8): for byteorder in ["little", "big"]: if bit_size == 64: fmt = "") + "d", math.e), ) def test_float32_value_to_bytes(self): for byteorder in ("little", "big"): with self.subTest(byteorder=byteorder): self.assertEqual( Object( self.prog, self.prog.float_type("float", 4, byteorder), math.e ).to_bytes_(), struct.pack(("<" if byteorder == "little" else ">") + "f", math.e), ) def test_struct_value_to_bytes(self): self.assertEqual( Object(self.prog, self.point_type, {"x": 1, "y": 2}).to_bytes_(), b"\x01\x00\x00\x00\x02\x00\x00\x00", ) def test_int_reference_to_bytes(self): self.add_memory_segment(b"\x78\x56\x34\x12", virt_addr=0xFFFF0000) self.assertEqual( Object(self.prog, "int", address=0xFFFF0000).to_bytes_(), b"\x78\x56\x34\x12", ) def test_int_reference_bit_offset_to_bytes(self): self.add_memory_segment(b"\xe0Y\xd1H\x00", virt_addr=0xFFFF0000) self.assertEqual( Object(self.prog, "int", address=0xFFFF0000, bit_offset=2).to_bytes_(), b"\x78\x56\x34\x12", ) def test_int_reference_big_endian_bit_offset_to_bytes(self): self.add_memory_segment(b"\x04\x8d\x15\x9e\x00", virt_addr=0xFFFF0000) self.assertEqual( Object( self.prog, self.prog.int_type("int", 4, True, "big"), address=0xFFFF0000, bit_offset=2, ).to_bytes_(), b"\x12\x34\x56\x78", ) def test_struct_reference_to_bytes(self): self.add_memory_segment( b"\x01\x00\x00\x00\x02\x00\x00\x00", virt_addr=0xFFFF0000 ) self.assertEqual( Object(self.prog, self.point_type, address=0xFFFF0000).to_bytes_(), b"\x01\x00\x00\x00\x02\x00\x00\x00", ) def test_int_from_bytes(self): for byteorder in ("little", "big"): with self.subTest(byteorder=byteorder): type_ = self.prog.int_type("int", 4, True, byteorder) self.assertIdentical( Object.from_bytes_( self.prog, type_, (0x12345678).to_bytes(4, byteorder) ), Object(self.prog, type_, 0x12345678), ) def test_int_from_bytes_bit_offset(self): self.assertIdentical( Object.from_bytes_(self.prog, "int", b"\xe0Y\xd1H\x00", bit_offset=2), Object(self.prog, "int", 0x12345678), ) def test_int_from_bytes_big_endian_bit_offset(self): self.assertIdentical( Object.from_bytes_( self.prog, self.prog.int_type("int", 4, True, "big"), b"\x04\x8d\x15\x9e\x00", bit_offset=2, ), Object(self.prog, self.prog.int_type("int", 4, True, "big"), 0x12345678), ) def test_int_from_bytes_bit_field(self): self.assertIdentical( Object.from_bytes_(self.prog, "int", b"\xcc", bit_field_size=8), Object(self.prog, "int", 0xCC, bit_field_size=8), ) def test_float64_from_bytes(self): for byteorder in ("little", "big"): with self.subTest(byteorder=byteorder): type_ = self.prog.float_type("double", 8, byteorder) self.assertIdentical( Object.from_bytes_( self.prog, type_, struct.pack( ("<" if byteorder == "little" else ">") + "d", math.e ), ), Object(self.prog, type_, math.e), ) def test_float32_from_bytes(self): for byteorder in ("little", "big"): with self.subTest(byteorder=byteorder): type_ = self.prog.float_type("float", 4, byteorder) self.assertIdentical( Object.from_bytes_( self.prog, type_, struct.pack( ("<" if byteorder == "little" else ">") + "f", math.e ), ), Object(self.prog, type_, math.e), ) def test_struct_from_bytes(self): self.assertIdentical( Object.from_bytes_( self.prog, self.point_type, b"\x01\x00\x00\x00\x02\x00\x00\x00" ), Object(self.prog, self.point_type, {"x": 1, "y": 2}), ) def test_struct_from_bytes_bit_offset(self): self.assertIdentical( Object.from_bytes_( self.prog, self.point_type, b"\xff\x01\x00\x00\x00\x02\x00\x00\x00", bit_offset=8, ), Object(self.prog, self.point_type, {"x": 1, "y": 2}), ) def test_struct_from_bytes_invalid_bit_offset(self): self.assertRaisesRegex( ValueError, "non-scalar must be byte-aligned", Object.from_bytes_, self.prog, self.point_type, b"\xff\x01\x00\x00\x00\x02\x00\x00\x00", bit_offset=2, ) def test_from_bytes_invalid_bit_field_size(self): self.assertRaisesRegex( ValueError, "bit field size cannot be zero", Object.from_bytes_, self.prog, "int", b"", bit_field_size=0, ) def test_from_bytes_buffer_too_small(self): self.assertRaisesRegex( ValueError, "buffer is too small", Object.from_bytes_, self.prog, "int", bytes(3), ) def test_from_bytes_incomplete_type(self): self.assertRaisesRegex( TypeError, "cannot create object with void type", Object.from_bytes_, self.prog, "void", b"", ) def test_from_bytes_bad_type(self): self.assertRaises(TypeError, Object.from_bytes_, self.prog, None, b"") class TestInvalidBitField(MockProgramTestCase): def test_integer(self): self.assertRaisesRegex( ValueError, "bit field size is larger than type size", Object, self.prog, "int", value=0, bit_field_size=64, ) self.assertRaisesRegex( ValueError, "bit field size is larger than type size", Object, self.prog, "int", address=0, bit_field_size=64, ) self.assertRaisesRegex( ValueError, "bit field size is larger than type size", Object, self.prog, "unsigned int", value=0, bit_field_size=64, ) self.assertRaisesRegex( ValueError, "bit field size is larger than type size", Object, self.prog, "unsigned int", address=0, bit_field_size=64, ) def test_float(self): self.assertRaisesRegex( ValueError, "bit field must be integer", Object, self.prog, "float", value=0, bit_field_size=16, ) self.assertRaisesRegex( ValueError, "bit field must be integer", Object, self.prog, "float", address=0, bit_field_size=16, ) def test_reference(self): self.assertRaisesRegex( ValueError, "bit field must be integer", Object, self.prog, self.point_type, address=0, bit_field_size=4, ) self.assertRaisesRegex( ValueError, "bit field must be integer", Object, self.prog, self.point_type, value={}, bit_field_size=4, ) class TestGenericOperators(MockProgramTestCase): def setUp(self): super().setUp() self.add_memory_segment( b"".join(i.to_bytes(4, "little") for i in range(4)), virt_addr=0xFFFF0000 ) def test_len(self): self.assertEqual(len(Object(self.prog, "int [0]", address=0)), 0) self.assertEqual(len(Object(self.prog, "int [10]", address=0)), 10) self.assertRaisesRegex( TypeError, "'int' has no len()", len, Object(self.prog, "int", address=0) ) self.assertRaisesRegex( TypeError, r"'int \[\]' has no len()", len, Object(self.prog, "int []", address=0), ) def test_address_of(self): obj = Object(self.prog, "int", address=0xFFFF0000) self.assertIdentical( obj.address_of_(), Object(self.prog, "int *", value=0xFFFF0000) ) obj = obj.read_() self.assertRaisesRegex( ValueError, "cannot take address of value", obj.address_of_ ) obj = Object(self.prog, "int", address=0xFFFF0000, bit_field_size=4) self.assertRaisesRegex( ValueError, "cannot take address of bit field", obj.address_of_ ) obj = Object(self.prog, "int", address=0xFFFF0000, bit_offset=4) self.assertRaisesRegex( ValueError, "cannot take address of bit field", obj.address_of_ ) def test_subscript(self): arr = Object(self.prog, "int [4]", address=0xFFFF0000) incomplete_arr = Object(self.prog, "int []", address=0xFFFF0000) ptr = Object(self.prog, "int *", value=0xFFFF0000) for obj in [arr, incomplete_arr, ptr]: for i in range(5): self.assertIdentical( obj[i], Object(self.prog, "int", address=0xFFFF0000 + 4 * i) ) if i < 4: self.assertIdentical( obj[i].read_(), Object(self.prog, "int", value=i) ) else: self.assertRaises(FaultError, obj[i].read_) obj = arr.read_() for i in range(4): self.assertIdentical(obj[i], Object(self.prog, "int", value=i)) self.assertRaisesRegex(OutOfBoundsError, "out of bounds", obj.__getitem__, 4) obj = Object(self.prog, "int", value=0) self.assertRaises(TypeError, obj.__getitem__, 0) def test_cast_primitive_value(self): obj = Object(self.prog, "long", value=2**32 + 1) self.assertIdentical(cast("int", obj), Object(self.prog, "int", value=1)) self.assertIdentical( cast("int", obj.read_()), Object(self.prog, "int", value=1) ) self.assertIdentical( cast("const int", Object(self.prog, "int", value=1)), Object(self.prog, "const int", value=1), ) self.assertRaisesRegex( TypeError, "cannot cast to 'struct point'", cast, self.point_type, Object(self.prog, "int", value=1), ) def test_cast_compound_value(self): obj = Object(self.prog, self.point_type, address=0xFFFF0000).read_() self.assertRaisesRegex( TypeError, "cannot cast to 'struct point'", cast, self.point_type, obj, ) self.assertRaisesRegex( TypeError, "cannot convert 'struct point' to 'enum color'", cast, self.color_type, obj, ) def test_cast_invalid(self): obj = Object(self.prog, "int", value=1) self.assertRaisesRegex(TypeError, "cannot cast to void type", cast, "void", obj) def test_reinterpret_reference(self): obj = Object(self.prog, "int", address=0xFFFF0000) self.assertIdentical(reinterpret("int", obj), obj) self.assertIdentical( reinterpret(self.prog.int_type("int", 4, True, "big"), obj), Object( self.prog, self.prog.int_type("int", 4, True, "big"), address=0xFFFF0000 ), ) obj = Object(self.prog, "int []", address=0xFFFF0000) self.assertIdentical( reinterpret("int [4]", obj), Object(self.prog, "int [4]", address=0xFFFF0000), ) def test_reinterpret_value(self): self.types.append(self.point_type) self.types.append( self.prog.struct_type( "foo", 8, (TypeMember(self.prog.int_type("long", 8, True), "counter"),) ), ) obj = Object(self.prog, "struct point", address=0xFFFF0008).read_() self.assertIdentical( reinterpret("struct foo", obj), Object(self.prog, "struct foo", address=0xFFFF0008).read_(), ) self.assertIdentical(reinterpret("int", obj), Object(self.prog, "int", value=2)) self.assertIdentical( reinterpret(self.prog.int_type("int", 4, True, "big"), obj), Object( self.prog, self.prog.int_type("int", 4, True, "big"), value=33554432 ), ) def test_member(self): reference = Object(self.prog, self.point_type, address=0xFFFF0000) unnamed_reference = Object( self.prog, self.prog.struct_type( "point", 8, ( TypeMember( self.prog.struct_type(None, 8, self.point_type.members), None ), ), ), address=0xFFFF0000, ) ptr = Object( self.prog, self.prog.pointer_type(self.point_type), value=0xFFFF0000 ) for obj in [reference, unnamed_reference, ptr]: self.assertIdentical( obj.member_("x"), Object(self.prog, "int", address=0xFFFF0000) ) self.assertIdentical(obj.member_("x"), obj.x) self.assertIdentical( obj.member_("y"), Object(self.prog, "int", address=0xFFFF0004) ) self.assertIdentical(obj.member_("y"), obj.y) self.assertRaisesRegex( LookupError, "'struct point' has no member 'z'", obj.member_, "z" ) self.assertRaisesRegex( AttributeError, "'struct point' has no member 'z'", getattr, obj, "z" ) obj = reference.read_() self.assertIdentical(obj.x, Object(self.prog, "int", value=0)) self.assertIdentical(obj.y, Object(self.prog, "int", value=1)) obj = Object(self.prog, "int", value=1) self.assertRaisesRegex( TypeError, "'int' is not a structure, union, or class", obj.member_, "x" ) self.assertRaisesRegex(AttributeError, "no attribute", getattr, obj, "x") def test_bit_field_member(self): self.add_memory_segment(b"\x07\x10\x5e\x5f\x1f\0\0\0", virt_addr=0xFFFF8000) type_ = self.prog.struct_type( "bits", 8, ( TypeMember( Object( self.prog, self.prog.int_type("int", 4, True), bit_field_size=4 ), "x", 0, ), TypeMember( Object( self.prog, self.prog.int_type("int", 4, True, qualifiers=Qualifiers.CONST), bit_field_size=28, ), "y", 4, ), TypeMember( Object( self.prog, self.prog.int_type("int", 4, True), bit_field_size=5 ), "z", 32, ), ), ) obj = Object(self.prog, type_, address=0xFFFF8000) self.assertIdentical( obj.x, Object( self.prog, self.prog.int_type("int", 4, True), address=0xFFFF8000, bit_field_size=4, ), ) self.assertIdentical( obj.y, Object( self.prog, self.prog.int_type("int", 4, True, qualifiers=Qualifiers.CONST), address=0xFFFF8000, bit_field_size=28, bit_offset=4, ), ) self.assertIdentical( obj.z, Object( self.prog, self.prog.int_type("int", 4, True), address=0xFFFF8004, bit_field_size=5, ), ) def test_member_out_of_bounds(self): obj = Object( self.prog, self.prog.struct_type("foo", 4, self.point_type.members), address=0xFFFF0000, ).read_() self.assertRaisesRegex(OutOfBoundsError, "out of bounds", getattr, obj, "y") def test_string(self): self.add_memory_segment( b"\x00\x00\xff\xff\x00\x00\x00\x00", virt_addr=0xFFFEFFF8 ) self.add_memory_segment(b"hello\0world\0", virt_addr=0xFFFF0000) strings = [ (Object(self.prog, "char *", address=0xFFFEFFF8), b"hello"), (Object(self.prog, "char [2]", address=0xFFFF0000), b"he"), (Object(self.prog, "char [8]", address=0xFFFF0000), b"hello"), ] for obj, expected in strings: with self.subTest(obj=obj): self.assertEqual(obj.string_(), expected) self.assertEqual(obj.read_().string_(), expected) strings = [ Object(self.prog, "char []", address=0xFFFF0000), Object(self.prog, "int []", address=0xFFFF0000), Object(self.prog, "int [2]", address=0xFFFF0000), Object(self.prog, "int *", value=0xFFFF0000), ] for obj in strings: self.assertEqual(obj.string_(), b"hello") self.assertRaisesRegex( TypeError, "must be an array or pointer", Object(self.prog, "int", value=1).string_, ) class TestSpecialMethods(MockProgramTestCase): def test_dir(self): obj = Object(self.prog, "int", value=0) self.assertEqual(dir(obj), sorted(object.__dir__(obj))) obj = Object(self.prog, self.point_type, address=0xFFFF0000) self.assertEqual(dir(obj), sorted(object.__dir__(obj) + ["x", "y"])) self.assertEqual(dir(obj.address_of_()), dir(obj)) def test_round(self): for func in [round, math.trunc, math.floor, math.ceil]: for value in [0.0, -0.0, -0.4, 0.4, 0.5, -0.5, 0.6, -0.6, 1.0, -1.0]: self.assertEqual( func(Object(self.prog, "double", value=value)), func(value) ) self.assertEqual( func(Object(self.prog, "int", value=value)), func(int(value)) ) self.assertIdentical( round(Object(self.prog, "int", value=1), 2), Object(self.prog, "int", value=1), ) self.assertIdentical( round(Object(self.prog, "double", value=0.123), 2), Object(self.prog, "double", value=0.12), ) def test_iter(self): obj = Object(self.prog, "int [4]", value=[0, 1, 2, 3]) for i, element in enumerate(obj): self.assertIdentical(element, Object(self.prog, "int", value=i)) self.assertEqual(operator.length_hint(iter(obj)), 4) self.assertRaisesRegex( TypeError, "'int' is not iterable", iter, Object(self.prog, "int", value=0) ) self.assertRaisesRegex( TypeError, r"'int \[\]' is not iterable", iter, Object(self.prog, "int []", address=0), ) def test__repr_pretty_(self): obj = Object(self.prog, "int", value=0) assertReprPrettyEqualsStr(obj)