"""Resolve the concrete owner behind request+0x08 for the SBC category request.

q_md_sbc_9 proved generic slot +0x88 (0x1801631e0) invokes:
    owner = *(request + 8)
    owner.vtable[+0x18](owner, parsed_response, 0)

Work backwards from the category request constructor and its callers to identify who
supplies request+8, then map candidate owner vtables and their +0x18 consumers.
"""
import traceback

try:
    def show(a, label):
        f = func(a)
        print("\n=== %s %#x %s ===" % (label, a, f.getName() if f else "?"))
        print(dec(a))

    ctor = 0x18017a7c0
    show(ctor, "category request constructor")
    print("\n=== ctor callers ===")
    for ent, name in callers(ctor):
        print("  %#x %s" % (ent, name))
        show(ent, "ctor caller")
    print("\n=== ctor xrefs ===")
    for frm, typ, name, ent in xrefs_to(ctor):
        print("  from=%#x type=%s fn=%s entry=%#x" % (frm, typ, name, ent))

    # The request base constructor is usually visible as the first direct call in
    # the category constructor. Dump every direct callee so request+8 initialization
    # can be distinguished from URI/tag setup.
    print("\n=== constructor direct callees ===")
    for target, name in callees(ctor):
        print("  %#x %s" % (target, name))
        show(target, "ctor callee")

    # Ghidra did not create a function at the traced +0x90 thunk. Print its raw
    # instructions and nearby containing-function identity without assuming a body.
    print("\n=== raw callback thunk at 0x180154830 ===")
    ad = addr(0x180154830)
    for _ in range(48):
        ins = listing.getInstructionAt(ad)
        if ins is None:
            print("  %s <not disassembled>" % ad)
            ad = ad.add(1)
            continue
        print("  %s  %s" % (ad, ins))
        ad = ins.getNext().getAddress() if ins.getNext() else ad.add(ins.getLength())

except Exception:
    traceback.print_exc()
