import gdb


def reg(name):
    return int(gdb.parse_and_eval("$" + name))


def read_hex(address, size):
    if address == 0 or size == 0:
        return ""
    try:
        data = bytes(gdb.selected_inferior().read_memory(address, min(size, 64)))
        return data.hex()
    except gdb.error:
        return "<unreadable>"


class Tracepoint(gdb.Breakpoint):
    def __init__(self, address, callback):
        super().__init__("*0x%x" % address, internal=False)
        self.callback = callback

    def stop(self):
        self.callback()
        return False


def libpoplar_base():
    pid = gdb.selected_inferior().pid
    with open("/proc/%d/maps" % pid) as mappings:
        for line in mappings:
            if "libpoplar.so" in line and "r-xp" in line:
                start = int(line.split("-", 1)[0], 16)
                offset = int(line.split()[2], 16)
                return start - offset
    raise gdb.GdbError("libpoplar.so is not mapped")


def copy_to():
    pointer, size = reg("rsi"), reg("rdx")
    gdb.write("HOST copy-to bytes=%d resource=%#x offset=%#x data=%s\n" %
              (size, reg("rcx"), reg("r8"), read_hex(pointer, size)))


def copy_from():
    pointer, size = reg("rcx"), reg("r8")
    gdb.write("HOST copy-from bytes=%d resource=%#x offset=%#x pointer=%#x\n" %
              (size, reg("rsi"), reg("rdx"), pointer))


def mirror_to():
    pointer, size = reg("r8"), reg("r9")
    gdb.write("HOST mirror-to ipu=%#x resource=%#x offset=%#x bytes=%d data=%s\n" %
              (reg("rsi"), reg("rdx"), reg("rcx"), size,
               read_hex(pointer, size)))


def mirror_from():
    gdb.write("HOST mirror-from ipu=%#x resource=%#x offset=%#x bytes=%d pointer=%#x\n" %
              (reg("rsi"), reg("rdx"), reg("rcx"), reg("r9"), reg("r8")))


def increment_hsp():
    pages = []
    for index in range(2):
        try:
            address = int(gdb.parse_and_eval(
                "ipuIoctlTraceBufferAddresses[%d]" % index))
            size = int(gdb.parse_and_eval(
                "ipuIoctlTraceBufferSizes[%d]" % index))
            pages.append("page%d=%s" %
                         (index, read_hex(address, min(size, 128))))
        except gdb.error:
            pass
    gdb.write("HSP increment ipu=%u amount=%u %s\n" %
              (reg("rsi") & 0xffffffff, reg("rdx") & 0xffffffff,
               " ".join(pages)))


def process_hsp():
    event = reg("rcx")
    gdb.write("HSP process event=%s upcoming=%#x replica=%u\n" %
              (read_hex(event, 64), reg("r8"), reg("r9") & 0xffffffff))


def mirror_host_buffers():
    gdb.write("HOST mirror-host-buffers sync=%#x replica=%u to-host=%u bytes=%u\n" %
              (reg("rsi"), reg("rdx") & 0xffffffff,
               reg("rcx") & 0xff, reg("r8")))


def target_mirror_buffer():
    pointer, size = reg("rsi"), reg("rcx")
    gdb.write("HOST target-mirror buffer=%#x offset=%#x bytes=%u ops=%#x flags=%#x data=%s\n" %
              (pointer, reg("rdx"), size, reg("r8"), reg("r9"),
               read_hex(pointer, size)))


base = libpoplar_base()
gdb.write("libpoplar base=%#x\n" % base)
Tracepoint(base + 0x121A880, copy_to)
Tracepoint(base + 0x121C810, copy_from)
Tracepoint(base + 0x3B6BD60, mirror_to)
Tracepoint(base + 0x3B6C190, mirror_from)
Tracepoint(base + 0x1222CB0, increment_hsp)
Tracepoint(base + 0x124A3C0, process_hsp)
Tracepoint(base + 0x12265A0, mirror_host_buffers)
Tracepoint(base + 0x3B5F4C0, target_mirror_buffer)
