from __future__ import print_function
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import gdb
import subprocess
import re
import copy
import struct
import os
# main_arena
main_arena = 0
main_arena_off = 0 

# thread
thread_arena = 0
enable_thread = False
tcache_enable = False
tcache = None
tcache_max_bin = 0
tcache_counts_size = 1

# chunks
top = {}
fastbinsize = 13
fastbin = []
fastchunk = [] #save fastchunk address for chunkinfo check
tcache_entry = []
tcache_count = []
all_tcache_entry = [] #save tcache address for chunkinfo check
last_remainder = {}
unsortbin = []
smallbin = {}  #{size:bin}
largebin = {}
system_mem = 0x21000

# chunk recording
freememoryarea = {} #using in parse
allocmemoryarea = {}
freerecord = {} # using in trace

# setting for tracing memory allocation
tracelargebin = True
inmemalign = False
inrealloc = False
print_overlap = True
DEBUG = True  #debug msg (free and malloc) if you want

# breakpoints for tracing 
mallocbp = None
freebp = None
memalignbp = None
reallocbp = None

# architecture setting
capsize = 0
word = ""
arch = ""

#condition
corruptbin = False

def u32(data,fmt="<I"):
    return struct.unpack(fmt,data)[0]


def u64(data,fmt="<Q"):
    return struct.unpack(fmt,data)[0]

def init_angelheap():
    global allocmemoryarea
    global freerecord
    
    dis_trace_malloc()
    allocmemoryarea = {}
    freerecord = {} 

class Malloc_bp_ret(gdb.FinishBreakpoint):
    global allocmemoryarea
    global freerecord
    
    def __init__(self,arg):
        gdb.FinishBreakpoint.__init__(self,gdb.newest_frame(),internal=True)
        self.silent = True
        self.arg = arg
    
    def stop(self):
        chunk = {}
        if len(arch) == 0 :
            getarch()
        if arch == "x86-64" :
            value = int(self.return_value)
            chunk["addr"] = value - capsize*2
        else :
            cmd = "info register $eax"
            value = int(gdb.execute(cmd,to_string=True).split()[1].strip(),16)
            chunk["addr"] = value - capsize*2
        if value == 0 :
            return False
    
        cmd = "x/" + word + hex(chunk["addr"] + capsize)
        chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
        overlap,status = check_overlap(chunk["addr"],chunk["size"],allocmemoryarea)
        if overlap and status == "error" :
            if DEBUG :
                print("\033[34m>--------------------------------------------------------------------------------------<\033[37m")
                msg = "\033[33mmalloc(0x%x)\033[37m" % self.arg
                print("%-40s = 0x%x \033[31m overlap detected !! (0x%x)\033[37m" % (msg,chunk["addr"]+capsize*2,overlap["addr"]))
                print("\033[34m>--------------------------------------------------------------------------------------<\033[37m")
            else :
                print("\033[31moverlap detected !! (0x%x)\033[37m" % overlap["addr"])
            del allocmemoryarea[hex(overlap["addr"])]
        else :
            if DEBUG:
                msg = "\033[33mmalloc(0x%x)\033[37m" % self.arg
                print("%-40s = 0x%x" % (msg,chunk["addr"] + capsize*2))
        allocmemoryarea[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"]+chunk["size"],chunk))
        if hex(chunk["addr"]) in freerecord :
            freechunktuple = freerecord[hex(chunk["addr"])]
            freechunk = freechunktuple[2]
            splitchunk = {}
            del freerecord[hex(chunk["addr"])]
            if chunk["size"] != freechunk["size"] :
                splitchunk["addr"] = chunk["addr"] + chunk["size"]
                splitchunk["size"] = freechunk["size"] - chunk["size"]
                freerecord[hex(splitchunk["addr"])] = copy.deepcopy((splitchunk["addr"],splitchunk["addr"]+splitchunk["size"],splitchunk))
        if self.arg >= 128*capsize :
            Malloc_consolidate()

class Malloc_Bp_handler(gdb.Breakpoint):
    def stop(self):
        if len(arch) == 0 :
            getarch()
        if arch == "x86-64":
            reg = "$rsi"
            arg = int(gdb.execute("info register " + reg,to_string=True).split()[1].strip(),16)
        else :
            # for _int_malloc in x86's glibc (unbuntu 14.04 & 16.04), size is stored in edx
            reg = "$edx"
            arg = int(gdb.execute("info register " + reg,to_string=True).split()[1].strip(),16)
        Malloc_bp_ret(arg)
        return False

class Free_bp_ret(gdb.FinishBreakpoint):
    def __init__(self):
        gdb.FinishBreakpoint.__init__(self,gdb.newest_frame(),internal=True)
        self.silent = True
    
    def stop(self):
        Malloc_consolidate()
        return False

class Free_Bp_handler(gdb.Breakpoint):

    def stop(self):
        global allocmemoryarea
        global freerecord
        global inmemalign
        global inrealloc
        get_top_lastremainder()

        if len(arch) == 0 :
            getarch()
        if arch == "x86-64":
            reg = "$rsi"
            result = int(gdb.execute("info register " + reg,to_string=True).split()[1].strip(),16) + 0x10
        else :
            # for _int_free in x86's glibc (unbuntu 14.04 & 16.04), chunk address is stored in edx
            reg = "$edx"
            result = int(gdb.execute("info register " + reg,to_string=True).split()[1].strip(),16) + 0x8
        chunk = {}
        if inmemalign or inrealloc:
            Update_alloca()
            inmemalign = False
            inrealloc = False
        prevfreed = False
        chunk["addr"] = result - capsize*2

        cmd = "x/" +word + hex(chunk["addr"] + capsize)
        size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        chunk["size"] = size & 0xfffffffffffffff8
        if (size & 1) == 0 :
            prevfreed = True
#        overlap,status = check_overlap(chunk["addr"],chunk["size"],freememoryarea)
        overlap,status = check_overlap(chunk["addr"],chunk["size"],freerecord)
        if overlap and status == "error" :
            if DEBUG :
                msg = "\033[32mfree(0x%x)\033[37m (size = 0x%x)" % (result,chunk["size"])
                print("\033[34m>--------------------------------------------------------------------------------------<\033[37m")
                print("%-25s \033[31m double free detected !! (0x%x(size:0x%x))\033[37m" % (msg,overlap["addr"],overlap["size"]))
                print("\033[34m>--------------------------------------------------------------------------------------<\033[37m",end="")
            else :
                print("\033[31mdouble free detected !! (0x%x)\033[37m" % overlap["addr"])
            del freerecord[hex(overlap["addr"])]
        else :
            if DEBUG :
                msg = "\033[32mfree(0x%x)\033[37m" % result
                print("%-40s (size = 0x%x)" % (msg,chunk["size"]),end="")

        if chunk["size"] <= 0x80 :
            freerecord[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"]+chunk["size"],chunk))
            if DEBUG :
                print("")
            if hex(chunk["addr"]) in allocmemoryarea :
                del allocmemoryarea[hex(chunk["addr"])]
            return False

        prevchunk = {}
        if prevfreed :
            cmd = "x/" +word + hex(chunk["addr"])
            prevchunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
            prevchunk["addr"] = chunk["addr"] - prevchunk["size"]
            if hex(prevchunk["addr"]) not in freerecord :
                print("\033[31m confuse in prevchunk 0x%x" % prevchunk["addr"])
            else :
                prevchunk["size"] += chunk["size"]
                del freerecord[hex(prevchunk["addr"])]

        nextchunk = {}
        nextchunk["addr"] = chunk["addr"] + chunk["size"]
        
        if nextchunk["addr"] == top["addr"] :
            if hex(chunk["addr"]) in allocmemoryarea :
                del allocmemoryarea[hex(chunk["addr"])]
                Free_bp_ret()
            if DEBUG :
                print("")
            return False

        cmd = "x/" + word + hex(nextchunk["addr"] + capsize)
        nextchunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
        cmd = "x/" + word + hex(nextchunk["addr"] + nextchunk["size"] + capsize)
        nextinused = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 1
        
        if nextinused == 0 and prevfreed: #next chunk is freed                       
            if hex(nextchunk["addr"]) not in freerecord :
                print("\033[31m confuse in nextchunk 0x%x" % nextchunk["addr"])
            else :
                prevchunk["size"] += nextchunk["size"]
                del freerecord[hex(nextchunk["addr"])]
        if nextinused == 0 and not prevfreed:
            if hex(nextchunk["addr"]) not in freerecord :
                print("\033[31m confuse in nextchunk 0x%x" % nextchunk["addr"])
            else :
                chunk["size"] += nextchunk["size"]
                del freerecord[hex(nextchunk["addr"])]
        if prevfreed :
            if hex(chunk["addr"]) in allocmemoryarea :
                del allocmemoryarea[hex(chunk["addr"])]
            chunk = prevchunk

        if DEBUG :
            print("")
        freerecord[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"]+chunk["size"],chunk))
        if hex(chunk["addr"]) in allocmemoryarea :
            del allocmemoryarea[hex(chunk["addr"])]
        if chunk["size"] > 65536 :
            Malloc_consolidate()
        return False

class Memalign_Bp_handler(gdb.Breakpoint):
    def stop(self):
        global inmemalign
        inmemalign = True
        return False

class Realloc_Bp_handler(gdb.Breakpoint):
    def stop(self):
        global inrealloc
        inrealloc = True
        return False

def Update_alloca():
    global allocmemoryarea
    if capsize == 0:
        getarch()
    for addr,(start,end,chunk) in allocmemoryarea.items():
        cmd = "x/" + word + hex(chunk["addr"] + capsize*1)
        cursize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8

        if cursize != chunk["size"]:
            chunk["size"] = cursize
            allocmemoryarea[hex(chunk["addr"])]= copy.deepcopy((start,start + cursize,chunk))
            

def Malloc_consolidate(): #merge fastbin when malloc a large chunk or free a very large chunk
    global fastbin
    global freerecord

    if capsize == 0 :
        getarch()
    freerecord = {}
    if not get_heap_info():
        print("Can't find heap info")
        return
    freerecord = copy.deepcopy(freememoryarea) 

def getarch():
    global capsize
    global word
    global arch

    data = gdb.execute('show arch',to_string = True)
    tmp =  re.search("currently.*",data)
    if tmp :
        info = tmp.group()
        if "x86-64" in info:
            capsize = 8
            word = "gx "
            arch = "x86-64"
            return "x86-64"
        elif "aarch64" in info :
            capsize = 8
            word = "gx "
            arch = "aarch64"
            return "aarch64"
        elif "arm" in info :
            capsize = 4
            word = "wx "
            arch = "arm"
            return "arm"
        else :
            word = "wx "
            capsize = 4
            arch = "i386"
            return  "i386"
    else :
        return "error"

def infoprocmap():
    """ Use gdb command 'info proc map' to get the memory mapping """
    """ Notice: No permission info """
    resp = gdb.execute("info proc map", to_string=True).split("\n")
    resp = '\n'.join(resp[i] for i  in range(4, len(resp))).strip().split("\n")
    infomap = ""
    for l in resp:
        line = ""
        first = True
        for sep in l.split(" "):
            if len(sep) != 0:
                if first: # start address
                    line += sep + "-"
                    first = False
                else:
                    line += sep + " "
        line = line.strip() + "\n"
        infomap += line
    return infomap

def procmap():
    data = gdb.execute('info proc exe',to_string = True)
    pid = re.search('process.*',data)
    if pid :
        pid = pid.group()
        pid = pid.split()[1]
        fpath = "/proc/" + pid + "/maps"
        if os.path.isfile(fpath): # if file exist, read memory mapping directly from file
            maps = open(fpath)
            infomap = maps.read()
            maps.close()
            return infomap
        else: # if file doesn't exist, use 'info proc map' to get the memory mapping
            return infoprocmap()
    else :
        return "error"

def libcbase():
    infomap = procmap()
    data = re.search(".*libc.*\.so",infomap)
    if data :
        libcaddr = data.group().split("-")[0]
        return int(libcaddr,16)
    else :
        return 0

def getoff(sym):
    libc = libcbase()
    if type(sym) is int :
        return sym-libc
    else :
        try :
            data = gdb.execute("x/x " + sym ,to_string=True)
            if "No symbol" in data:
                return 0
            else :
                data = re.search("0x.*[0-9a-f] ",data)
                data = data.group()
                symaddr = int(data[:-1] ,16)
                return symaddr-libc
        except :
            return 0


def set_thread_arena():
    global thread_arena
    global main_arena
    global enable_thread
    if capsize == 0 :
        arch = getarch()
    try :
        data = gdb.execute("x/" + word +"&thread_arena",to_string=True)
    except :
        return
    enable_thread = True
    if "main_arena" in data :
        thread_arena = main_arena
        return 
    thread_arena = int(data.split(":")[1].strip(),16)

def set_main_arena():
    global main_arena
    global main_arena_off

    offset = getoff("&main_arena")
    if offset == 0: # no main_arena symbol
        print("Cannot get main_arena's symbol address. Make sure you install libc debug file (libc6-dbg & libc6-dbg:i386 for debian package).")
        return
    libc = libcbase()
    arch = getarch()
    main_arena_off = offset
    main_arena = libc + main_arena_off

def check_overlap(addr,size,data = None):
    if data :
        for key,(start,end,chunk) in data.items() :
            if (addr >= start and addr < end) or ((addr+size) > start and (addr+size) < end ) or ((addr < start) and  ((addr + size) >= end)):
                return chunk,"error"
    else :
        for key,(start,end,chunk) in freememoryarea.items() :
            if (addr >= start and addr < end) or ((addr+size) > start and (addr+size) < end ) or ((addr < start) and  ((addr + size) >= end)):
                return chunk,"freed"
        for key,(start,end,chunk) in allocmemoryarea.items() :
            if (addr >= start and addr < end) or ((addr+size) > start and (addr+size) < end ) or ((addr < start) and  ((addr + size) >= end)) :
                return chunk,"inused" 
    return None,None

def get_top_lastremainder(arena=None):
    global fastbinsize
    global top
    global last_remainder
    if not arena :
        arena = main_arena
    chunk = {}
    if capsize == 0 :
        arch = getarch()
    #get top
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").top"
    chunk["addr"] =  int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
    chunk["size"] = 0
    if chunk["addr"] :
        cmd = "x/" + word + hex(chunk["addr"]+capsize*1)
        try :
            chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
            if chunk["size"] > system_mem :
                chunk["memerror"] = "top is broken ?"
        except :
            chunk["memerror"] = "invaild memory"
    top = copy.deepcopy(chunk)
    #get last_remainder
    chunk = {}
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").last_remainder"
    chunk["addr"] =  int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
    chunk["size"] = 0
    if chunk["addr"] :
        cmd = "x/" + word + hex(chunk["addr"]+capsize*1)
        try :
            chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
        except :
            chunk["memerror"] = "invaild memory"
    last_remainder = copy.deepcopy(chunk)

def get_fast_bin(arena=None):
    global fastbin
    global fastchunk
    global fastbinsize
    global freememoryarea
    if not arena :
        arena = main_arena
    fastbin = []
    fastchunk = []
    #freememoryarea = []
    if capsize == 0 :
        arch = getarch()
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").fastbinsY"
    fastbinsY = int(gdb.execute(cmd,to_string=True).split(":")[0].split()[0].strip(),16)
    for i in range(fastbinsize-3):
        fastbin.append([])
        chunk = {}
        is_overlap = (None,None)
        cmd = "x/" + word  + hex(fastbinsY + i*capsize)
        chunk["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)

        while chunk["addr"] and not is_overlap[0]:
            cmd = "x/" + word + hex(chunk["addr"]+capsize*1)
            try :
                chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
            except :
                chunk["memerror"] = "invaild memory"
                break
            is_overlap = check_overlap(chunk["addr"], (capsize*2)*(i+2))
            chunk["overlap"] = is_overlap
            freememoryarea[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"] + (capsize*2)*(i+2) ,chunk))
            fastbin[i].append(copy.deepcopy(chunk))
            fastchunk.append(chunk["addr"])
            cmd = "x/" + word + hex(chunk["addr"]+capsize*2)
            chunk = {}
            chunk["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        if not is_overlap[0]:
            chunk["size"] = 0
            chunk["overlap"] = None
            fastbin[i].append(copy.deepcopy(chunk))

def get_curthread():
    cmd = "thread"
    thread_id = int(gdb.execute(cmd,to_string=True).split("thread is")[1].split()[0].strip())
    return thread_id

def get_all_threads():
    cmd = "info threads"
    all_threads = [int(line.split()[0].strip()) for line in gdb.execute(cmd, to_string=True).replace("*", "").split("\n")[1:-1]]
    return all_threads

def thread_cmd_execute(thread_id,thread_cmd):
    cmd = "thread apply %d %s" % (thread_id,thread_cmd)
    result = gdb.execute(cmd,to_string=True)
    return result

def get_tcache():
    global tcache
    global tcache_enable
    global tcache_max_bin
    global tcache_counts_size
    if capsize == 0 :
        arch = getarch()
    try :
        tcache_max_bin = int(gdb.execute("x/" + word + " &mp_.tcache_bins",to_string=True).split(":")[1].strip(),16)
        try :
            tcache_enable = True
            tcache = int(gdb.execute("x/" + word + "&tcache",to_string=True).split(":")[1].strip(),16)
            tps_size = int(gdb.execute("p sizeof(*tcache)",to_string=True).split("=")[1].strip())
            if tps_size > 0x240:
                tcache_counts_size = 2
        except :
            heapbase = get_heapbase()
            if heapbase != 0 :
                cmd = "x/" + word + hex(heapbase + capsize*1)
                f_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                while(f_size == 0):
                    heapbase += capsize*2
                    cmd = "x/" + word + hex(heapbase + capsize*1)
                    f_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                tcache = heapbase + capsize*2
                if (f_size & ~7) - 0x10 > 0x240:
                    tcache_counts_size = 2
            else :
                tcache = 0
    except :
        tcache_enable = False
        tcache = 0

def get_tcache_count() :
    global tcache_count
    tcache_count = []
    if not tcache_enable :
        return
    if capsize == 0 :
        arch = getarch()
    count_size = int(tcache_max_bin * tcache_counts_size / capsize)
    for i in range(count_size):
        cmd = "x/" + word + hex(tcache + i*capsize)
        c = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        for j in range(int(capsize / tcache_counts_size)):
            tcache_count.append((c >> j * 8*tcache_counts_size) & 0xff)

def get_tcache_entry():
    global tcache_entry
    get_tcache()
    if not tcache_enable :
        return
    tcache_entry = []
    get_tcache_count()
    if capsize == 0 :
        arch = getarch()
    if tcache and tcache_max_bin :
        entry_start = tcache + tcache_max_bin * tcache_counts_size
        for i in range(tcache_max_bin):
            tcache_entry.append([])
            chunk = {}
            is_overlap = (None,None)
            cmd = "x/" + word + hex(entry_start + i*capsize)
            entry = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            while entry and not is_overlap[0] :
                chunk["addr"] = entry - capsize*2
                cmd = "x/" + word + hex(chunk["addr"] + capsize)
                try :
                    chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
                except :
                    chunk["memerror"] = "invaild memory"
                    tcache_entry[i].append(copy.deepcopy(chunk))
                    break
                is_overlap = check_overlap(chunk["addr"],capsize*2*(i+2))
                chunk["overlap"] = is_overlap
                freememoryarea[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"] + (capsize*2)*(i+2) ,chunk))
                tcache_entry[i].append(copy.deepcopy(chunk))
                all_tcache_entry.append(chunk["addr"])
                cmd = "x/" + word + hex(chunk["addr"]+capsize*2)
                chunk = {}
                entry = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)

def trace_normal_bin(chunkhead,arena=None):
    global freememoryarea 
    if not arena :
        arena = main_arena
    libc = libcbase()
    bins = []
    if capsize == 0 :
        arch = getarch()
    if chunkhead["addr"] == 0 : # main_arena not initial
        return None
    chunk = {}
    cmd = "x/" + word  + hex(chunkhead["addr"] + capsize*2) #fd
    chunk["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) #get fd chunk
    if (chunk["addr"] == chunkhead["addr"]) :  #no chunk in the bin
        if (chunkhead["addr"] > arena) :
            return bins
        else :
            try :
                cmd = "x/" + word + hex(chunk["addr"]+capsize*1)
                chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
                is_overlap = check_overlap(chunk["addr"],chunk["size"])
                chunk["overlap"] = is_overlap
                chunk["memerror"] = "\033[31mbad fd (" + hex(chunk["addr"]) + ")\033[37m"
            except :
                chunk["memerror"] = "invaild memory"
            bins.append(copy.deepcopy(chunk)) 
            return bins
    else :
        try :
            cmd = "x/" + word + hex(chunkhead["addr"]+capsize*3)
            bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(bk+capsize*2)
            bk_fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            if bk_fd != chunkhead["addr"]:
                chunkhead["memerror"] = "\033[31mdoubly linked list corruption {0} != {1} and \033[36m{2}\033[31m is broken".format(hex(chunkhead["addr"]),hex(bk_fd),hex(chunkhead["addr"]))
                bins.append(copy.deepcopy(chunkhead))
                return bins
            fd = chunkhead["addr"]
            chunkhead = {}
            chunkhead["addr"] = bk #bins addr
            chunk["addr"] = fd #first chunk
        except :
            chunkhead["memerror"] = "invaild memory" 
            bins.append(copy.deepcopy(chunkhead))
            return bins
        while chunk["addr"] != chunkhead["addr"] :
            try :
                cmd = "x/" + word + hex(chunk["addr"])
                chunk["prev_size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
                cmd = "x/" + word + hex(chunk["addr"]+capsize*1)
                chunk["size"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
            except :
                chunk["memerror"] = "invaild memory"
                break
            try :
                cmd = "x/" + word + hex(chunk["addr"]+capsize*2)
                fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                if fd == chunk["addr"] :
                    chunk["memerror"] = "\033[31mbad fd (" + hex(fd) + ")\033[37m"
                    bins.append(copy.deepcopy(chunk))
                    break
                cmd = "x/" + word + hex(fd + capsize*3)
                fd_bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                if chunk["addr"] != fd_bk :
                    chunk["memerror"] = "\033[31mdoubly linked list corruption {0} != {1} and \033[36m{2}\033[31m or \033[36m{3}\033[31m is broken".format(hex(chunk["addr"]),hex(fd_bk),hex(fd),hex(chunk["addr"]))
                    bins.append(copy.deepcopy(chunk))
                    break
            except :
                chunk["memerror"] = "invaild memory"
                bins.append(copy.deepcopy(chunk))
                break
            is_overlap = check_overlap(chunk["addr"],chunk["size"])
            chunk["overlap"] = is_overlap
            freememoryarea[hex(chunk["addr"])] = copy.deepcopy((chunk["addr"],chunk["addr"] + chunk["size"] ,chunk))
            bins.append(copy.deepcopy(chunk))
            cmd = "x/" + word + hex(chunk["addr"]+capsize*2) #find next
            chunk = {}
            chunk["addr"] = fd
    return bins

def get_unsortbin(arena=None):
    global unsortbin
    if not arena :
        arena = main_arena
    unsortbin = []
    if capsize == 0 :
        arch = getarch()
    chunkhead = {}
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").bins"
    chunkhead["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
    unsortbin = trace_normal_bin(chunkhead,arena)

def get_smallbin(arena=None):
    global smallbin
    if not arena :
        arena = main_arena
    smallbin = {}
    if capsize == 0 :
        arch = getarch()
    max_smallbin_size = 512*int(capsize/4)
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").bins"
    bins_addr = int(gdb.execute(cmd,to_string=True).split(":")[0].split()[0].strip(),16)
    for size in range(capsize*4,max_smallbin_size,capsize*2):
        chunkhead = {}
        idx = int((size/(capsize*2)))-1 
        cmd = "x/" + word + hex(bins_addr + idx*capsize*2)  # calc the smallbin index
        chunkhead["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        try :
            bins = trace_normal_bin(chunkhead,arena)
        except:
            corruptbin = True
            bins = None
        if bins and len(bins) > 0 :
            smallbin[hex(size)] = copy.deepcopy(bins)

def largbin_index(size):
    if capsize == 0 :
        arch = getarch()
    if capsize == 8 :
        if (size >> 6) <= 48 :
            idx = 48 + (size >> 6)
        elif (size >> 9) <= 20 :
            idx = 91 + (size >> 9)
        elif (size >> 12) <= 10:
            idx = 110 + (size >> 12)
        elif (size >> 15) <= 4 :
            idx = 119 + (size >> 15)
        elif (size >> 18) <= 2:
            idx = 124 + (size >> 18)
        else :
            idx = 126
    else :
        if (size >> 6) <= 38 :
            idx = 56 + (size >> 6)
        elif (size >> 9) <= 20 :
            idx = 91 + (size >> 9)
        elif (size >> 12) <= 10:
            idx = 110 + (size >> 12)
        elif (size >> 15) <= 4 :
            idx = 119 + (size >> 15)
        elif (size >> 18) <= 2:
            idx = 124 + (size >> 18)
        else :
            idx = 126
    return idx 

def get_largebin(arena=None):
    global largebin
    global corruptbin
    if not arena :
        arena = main_arena
    largebin = {}
    if capsize == 0 :
        arch = getarch()
    min_largebin = 512*int(capsize/4)
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").bins"
    bins_addr = int(gdb.execute(cmd,to_string=True).split(":")[0].split()[0].strip(),16)
    for idx in range(64,128):
        chunkhead = {}
        cmd = "x/" + word + hex(bins_addr + idx*capsize*2 - 2*capsize)  # calc the largbin index
        chunkhead["addr"] = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        try :
            bins = trace_normal_bin(chunkhead,arena)
        except :
            corruptbin = True
            bins = None
        if bins and len(bins) > 0 :
            largebin[idx] = copy.deepcopy(bins)

def get_system_mem(arena=None):
    global system_mem
    if not arena :
        arena = main_arena
    if capsize == 0 :
        arch = getarch()
    cmd = "x/" + word + "&((struct malloc_state *)" + hex(arena) + ").system_mem" 
    system_mem = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)


def get_heap_info(arena=None):
    global main_arena
    global thread_arena
    global freememoryarea
    global top
    global tcache_enable
    global tcache

    top = {}
    freememoryarea = {}
    corruptbin = False

    if arena :
        get_system_mem(arena)
        get_unsortbin(arena)
        get_smallbin(arena)
        if tracelargebin :
            get_largebin(arena)
        get_fast_bin(arena)
        get_top_lastremainder(arena)
        get_tcache_entry()
        return True


    set_main_arena()
    set_thread_arena()
    if thread_arena and enable_thread :
        get_system_mem(thread_arena)
        get_unsortbin(thread_arena)
        get_smallbin(thread_arena)
        if tracelargebin :
            get_largebin(thread_arena)
        get_fast_bin(thread_arena)
        get_top_lastremainder(thread_arena)
        get_tcache_entry()
        return True

    elif main_arena and not enable_thread:
        get_system_mem()
        get_unsortbin()
        get_smallbin()
        if tracelargebin :
            get_largebin()
        get_fast_bin()
        get_top_lastremainder()
        get_tcache_entry()
        return True
    return False
    

def get_reg(reg):
    cmd = "info register " + reg
    result = int(gdb.execute(cmd,to_string=True).split()[1].strip(),16)
    return result

def trace_malloc():
    global mallocbp
    global freebp
    global memalignbp
    global reallocbp
    
    mallocbp = Malloc_Bp_handler("*" + "_int_malloc")
    freebp = Free_Bp_handler("*" + "_int_free")
    memalignbp = Memalign_Bp_handler("*" + "_int_memalign")
    reallocbp = Realloc_Bp_handler("*" + "_int_realloc")
    if not get_heap_info() :
        print("Can't find heap info")
        return

def dis_trace_malloc():
    global mallocbp
    global freebp
    global memalignbp
    global reallocbp

    if mallocbp :
        mallocbp.delete()
        mallocbp = None
    if freebp :   
        freebp.delete()
        freebp = None
    if memalignbp :
        memalignbp.delete()
        memalignbp = None
    if reallocbp :
        reallocbp.delete()
        reallocbp = None
 
def find_overlap(chunk,bins):
    is_overlap = False
    count = 0
    for current in bins :
        if chunk["addr"] == current["addr"] :
            count += 1
    if count > 1 :
        is_overlap = True
    return is_overlap

def unlinkable(chunkaddr,fd = None ,bk = None):
    if capsize == 0 :
        arch = getarch()
    try :
        cmd = "x/" + word + hex(chunkaddr + capsize)
        chunk_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
        cmd = "x/" + word + hex(chunkaddr + chunk_size)
        next_prev_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        if not fd :
            cmd = "x/" + word + hex(chunkaddr + capsize*2)
            fd = int(gdb.execute(cmd,to_string=true).split(":")[1].strip(),16)
        if not bk :
            cmd = "x/" + word + hex(chunkaddr + capsize*3)
            bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(fd + capsize*3)
        fd_bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(bk + capsize*2)
        bk_fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        if chunk_size != next_prev_size :
            print("\033[32mUnlinkable :\033[1;31m False (corrupted size chunksize(0x%x) != prev_size(0x%x)) ) \033[37m " % (chunk_size,next_prev_size))
        elif (chunkaddr == fd_bk ) and (chunkaddr == bk_fd) :
            print("\033[32mUnlinkable :\033[1;33m True\033[37m")
            print("\033[32mResult of unlink :\033[37m")
            print("\033[32m      \033[1;34m FD->bk (\033[1;33m*0x%x\033[1;34m) = BK (\033[1;37m0x%x ->\033[1;33m 0x%x\033[1;34m)\033[37m " % (fd+capsize*3,fd_bk,bk))
            print("\033[32m      \033[1;34m BK->fd (\033[1;33m*0x%x\033[1;34m) = FD (\033[1;37m0x%x ->\033[1;33m 0x%x\033[1;34m)\033[37m " % (bk+capsize*2,bk_fd,fd))
        else :
            if chunkaddr != fd_bk :
                print("\033[32mUnlinkable :\033[1;31m False (FD->bk(0x%x) != (0x%x)) \033[37m " % (fd_bk,chunkaddr))
            else :
                print("\033[32mUnlinkable :\033[1;31m False (BK->fd(0x%x) != (0x%x)) \033[37m " % (bk_fd,chunkaddr))
    except :
        print("\033[32mUnlinkable :\033[1;31m False (FD or BK is corruption) \033[37m ")

def freeable(victim):
    global fastchunk
    global system_mem
    if capsize == 0 :
        arch = getarch()
    chunkaddr = victim
    try :
        if not get_heap_info() :
            print("Can't find heap info")
            return
        cmd = "x/" + word + hex(chunkaddr)
        prev_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*1)
        size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*2)
        fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*3)
        bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        prev_inuse = size & 1
        is_mmapd = (size >> 1) & 1
        non_main_arena = (size >> 2) & 1
        size = size & 0xfffffffffffffff8
        if is_mmapd :
            block = chunkaddr - prev_size
            total_size = prev_size + size
            if ((block | total_size) & (0xfff)) != 0 :
                print("\033[32mFreeable :\033[1;31m False -> Invalid pointer (((chunkaddr(0x%x) - prev_size(0x%x))|(prev_size(0x%x) + size(0x%x)))) & 0xfff != 0 \033[37m" % (chunkaddr,prev_size,prev_size,size))
                return 
        else :
            if chunkaddr > (2**(capsize*8) - (size & 0xfffffffffffffff8)):
                print("\033[32mFreeable :\033[1;31m False -> Invalid pointer chunkaddr (0x%x) > -size (0x%x)\033[37m" % (chunkaddr,(2**(capsize*8) - (size & 0xfffffffffffffff8)))) 
                return
            if (chunkaddr & (capsize*2 - 1)) != 0 :
                print("\033[32mFreeable :\033[1;31m False -> Invalid pointer misaligned chunkaddr (0x%x) & (0x%x) != 0\033[37m" % (chunkaddr,(capsize*2 - 1)))
                return
            if (size < capsize*4) :
                print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid size (size(0x%x) < 0x%x )\033[37m" % (chunkaddr,size,capsize*4))
                return
            if (size & (capsize)) !=0 :
                print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid size (size(0x%x) & 0x%x != 0 )\033[37m" % (chunkaddr,size,capsize))
                return
            cmd = "x/" + word + hex(chunkaddr + size + capsize)
            nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            nextchunk = chunkaddr + size
            status = nextsize & 1
            if size <= capsize*0x10 :  #fastbin
                if nextsize < capsize*4 :
                    print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid next size (size(0x%x) < 0x%x )\033[37m" % (chunkaddr,size,capsize*4))
                    return
                if nextsize >= system_mem :
                    print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid next size (size(0x%x) > system_mem(0x%x) )\033[37m" % (chunkaddr,size,system_mem))
                    return
                old = fastbin[int(size/0x10)-2][0]["addr"]
                if chunkaddr == old :
                    print("\033[32mFreeable :\033[1;31m false -> Double free chunkaddr(0x%x) == 0x%x )\033[37m" % (chunkaddr,old)) 
                    return
            else :
                if chunkaddr == top["addr"]:
                    print("\033[32mFreeable :\033[1;31m False -> Free top chunkaddr(0x%x) == 0x%x )\033[37m" % (chunkaddr,top["addr"]))
                    return
                cmd = "x/" + word + hex(top["addr"] + capsize)
                topsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                if nextchunk >= top["addr"] + topsize :
                    print("\033[32mFreeable :\033[1;31m False -> Out of top chunkaddr(0x%x) > 0x%x )\033[37m" % (chunkaddr,top["addr"] + topsize))
                    return
                if status == 0 :
                    print("\033[32mFreeable :\033[1;31m false -> Double free chunkaddr(0x%x) inused bit is not seted )\033[37m" % (chunkaddr))
                    return
                if nextsize < capsize*4 :
                    print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid next size (size(0x%x) < 0x%x )\033[37m" % (chunkaddr,size,capsize*4))
                    return
                if nextsize >= system_mem :
                    print("\033[32mFreeable :\033[1;31m False -> Chunkaddr (0x%x) invalid next size (size(0x%x) > system_mem(0x%x) )\033[37m" % (chunkaddr,size,system_mem))
                    return
                if not prev_inuse : 
                    cmd = "x/" + word + hex(chunkaddr - prev_size + capsize)
                    prev_chunk_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16) & 0xfffffffffffffff8
                    if prev_size  != prev_chunk_size :
                        print("\033[32mFreeable :\033[1;31m False -> p->size(0x%x) != next->prevsize(0x%x) \033[37m" % (prev_chunk_size,prev_size))
                        return
                    
                if len(unsortbin) > 0 :
                    bck = unsortbin[0]["addr"]
                    cmd = "x/" + word + hex(bck + capsize*2)
                    fwd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                    cmd = "x/" + word + hex(fwd + capsize*3)
                    bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                    if bk != bck :
                        print("\033[32mFreeable :\033[1;31m False -> Corrupted unsorted chunkaddr fwd->bk(0x%x) != bck(0x%x) )\033[37m" % (bk,bck))
                        return
            print("\033[32mFreeable :\033[1;33m True\033[37m") 
    except :
        print("Can't access memory")
    
def get_heapbase():
    if (main_arena and not enable_thread) or thread_arena == main_arena :
        heapbase = int(gdb.execute("x/" + word + " &mp_.sbrk_base",to_string=True).split(":")[1].strip(),16)
    elif thread_arena :
        arena_size = int(gdb.execute("p sizeof(main_arena)",to_string=True).split("=")[1].strip(),16)
        heapbase = thread_arena + arena_size
    else :
        return None
    return heapbase

def chunkinfo(victim):
    global fastchunk
    if capsize == 0 :
        arch = getarch()
    chunkaddr = victim
    try :
        if not get_heap_info() :
            print("Can't find heap info")
            return
        cmd = "x/" + word + hex(chunkaddr)
        prev_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*1)
        size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*2)
        fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*3)
        bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + (size & 0xfffffffffffffff8) + capsize)
        nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        status = nextsize & 1    
        print("==================================")
        print("            Chunk info            ")
        print("==================================")
        if status:
            if chunkaddr in fastchunk :
                print("\033[1;32mStatus : \033[1;34m Freed (fast) \033[37m")
            elif chunkaddr in all_tcache_entry:
                print("\033[1;32mStatus : \033[1;34m Freed (tcache) \033[37m")
            else :
                print("\033[1;32mStatus : \033[31m Used \033[37m")
        else :
            print("\033[1;32mStatus : \033[1;34m Freed \033[37m")
            unlinkable(chunkaddr,fd,bk)
        freeable(chunkaddr)
        print("\033[32mprev_size :\033[37m 0x%x                  " % prev_size)
        print("\033[32msize :\033[37m 0x%x                  " % (size & 0xfffffffffffffff8))
        print("\033[32mprev_inused :\033[37m %x                    " % (size & 1) )
        print("\033[32mis_mmap :\033[37m %x                    " % (size & 2) )
        print("\033[32mnon_mainarea :\033[37m %x                     " % (size & 4) )
        if not status :
            print("\033[32mfd :\033[37m 0x%x                  " % fd)
            print("\033[32mbk :\033[37m 0x%x                  " % bk)
        if size >= 512*(capsize/4) :
            cmd = "x/" + word + hex(chunkaddr + capsize*4)
            fd_nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(chunkaddr + capsize*5)
            bk_nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            print("\033[32mfd_nextsize :\033[37m 0x%x  " % fd_nextsize)
            print("\033[32mbk_nextsize :\033[37m 0x%x  " % bk_nextsize) 
    except :
        print("Can't access memory")

def freeptr(ptr):
    if capsize == 0 :
        arch = getarch()
    freeable(ptr-capsize*2) 

def chunkptr(ptr):
    if capsize == 0 :
        arch = getarch()
    chunkinfo(ptr-capsize*2) 





def mergeinfo(victim):
    global fastchunk
    if capsize == 0 :
        arch = getarch()
    chunkaddr = victim
    try :
        if not get_heap_info():
            print("Can't find heap info")
            return
        print("==================================")
        print("            Merge info            ")
        print("==================================")
        cmd = "x/" + word + hex(chunkaddr)
        prev_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + capsize*1)
        size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        cmd = "x/" + word + hex(chunkaddr + (size & 0xfffffffffffffff8) + capsize)
        nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
        status = nextsize & 1
        if status :
            if chunkaddr in fastchunk :
                print("The chunk is freed")
            else :
                if (size & 0xfffffffffffffff8) <= 0x80 :
                    print("The chunk will be a\033[32m fastchunk\033[37m")
                else :
                    prev_status = size & 1
                    next_chunk = chunkaddr + (size & 0xfffffffffffffff8)
                    cmd = "x/" + word + hex(next_chunk + (nextsize & 0xfffffffffffffff8) + capsize)
                    if next_chunk != top["addr"] :
                        next_nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
                        next_status = next_nextsize & 1
                    if not prev_status: #if prev chunk is freed
                        prev_chunk = chunkaddr - prev_size
                        if next_chunk == top["addr"] : #if next chunk is top
                            print("The chunk will merge into top , top will be \033[1;33m0x%x\033[37m " % prev_chunk)
                            print("\033[32mUnlink info : \033[1;33m0x%x\033[37m" % prev_chunk)
                            unlinkable(prev_chunk)
                        elif not next_status : #if next chunk is freed
                            print("The chunk and \033[1;33m0x%x\033[0m will merge into \033[1;33m0x%x\033[37m" % (next_chunk,prev_chunk))
                            print("\033[32mUnlink info : \033[1;33m0x%x\033[37m" % prev_chunk)
                            unlinkable(prev_chunk)
                            print("\033[32mUnlink info : \033[1;33m0x%x\033[37m" % next_chunk)
                            unlinkable(next_chunk)
                        else :
                            print("The chunk will merge into \033[1;33m0x%x\033[37m" % prev_chunk)
                            print("\033[32mUnlink info : \033[1;33m0x%x\033[37m" % prev_chunk)
                            unlinkable(prev_chunk)
                    else :
                        if next_chunk == top["addr"] : #if next chunk is top
                            print("The chunk will merge into top , top will be \033[1;34m0x%x\033[37m" % chunkaddr)
                        elif not next_status : #if next chunk is freed
                            print("The chunk will merge with \033[1;33m0x%x\033[37m" % next_chunk)
                            print("\033[32mUnlink info : \033[1;33m0x%x\033[37m" % next_chunk)
                            unlinkable(next_chunk)
                        else :
                            print("The chunk will not merge with other") 
        else :
            print("The chunk is freed")
    except :
        print("Can't access memory")

def force(target):
    if capsize == 0 :
        arch = getarch()
    if not get_heap_info():
        print("Can't find heap info")
        return
    if target % capsize != 0 :
        print("Not alignment")
    else :
        nb = target - top["addr"] - capsize*2
        print("nb = %d" % nb)

def putfastbin(arena=None):
    if capsize == 0 :
        arch = getarch()

    if not get_heap_info(arena):
         print("Can't find heap info")
         return False
    for i,bins in enumerate(fastbin) :
        cursize = (capsize*2)*(i+2)
        print("\033[32m(0x%02x)     fastbin[%d]:\033[37m " % (cursize,i),end = "")
        for chunk in bins :
            if "memerror" in chunk :
                print("\033[31m0x%x (%s)\033[37m" % (chunk["addr"],chunk["memerror"]),end = "")
            elif chunk["size"] != cursize and chunk["addr"] != 0 :
                print("\033[36m0x%x (size error (0x%x))\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            elif chunk["overlap"] and chunk["overlap"][0]:
                print("\033[31m0x%x (overlap chunk with \033[36m0x%x(%s)\033[31m )\033[37m" % (chunk["addr"],chunk["overlap"][0]["addr"],chunk["overlap"][1]),end = "")
            elif chunk == bins[0]  :
                print("\033[34m0x%x\033[37m" % chunk["addr"],end = "")
            else  :
                if print_overlap :
                    if find_overlap(chunk,bins):
                        print("\033[31m0x%x\033[37m" % chunk["addr"],end ="")
                    else :
                        print("0x%x" % chunk["addr"],end = "")
                else :
                    print("0x%x" % chunk["addr"],end = "")
            if chunk != bins[-1]:
                print(" --> ",end = "")
        print("")
    return True

def put_tcache():
    if not tcache_enable :
        return
    for i,entry in enumerate(tcache_entry):
        if capsize == 4 :
            cursize = (8*2)*(i+1)
        else :
            cursize = (8*2)*(i+2)
        if len(tcache_entry[i]) > 0 :
            print("\033[33;1m(0x%02x)   tcache_entry[%d]\033[32m(%d)\033[33;1m:\033[37m " % (cursize,i,tcache_count[i]),end = "")
        elif tcache_count[i] > 0:            
            print("\033[33;1m(0x%02x)   tcache_entry[%d]\033[31;1m(%d)\033[33;1m:\033[37m 0\n" % (cursize,i,tcache_count[i]),end = "")
        for chunk in entry :
            if "memerror" in chunk :
                print("\033[31m0x%x (%s)\033[37m" % (chunk["addr"]+capsize*2,chunk["memerror"]),end = "")
            elif chunk["overlap"] and chunk["overlap"][0]:
                print("\033[31m0x%x (overlap chunk with \033[36m0x%x(%s)\033[31m )\033[37m" % (chunk["addr"]+capsize*2,chunk["overlap"][0]["addr"],chunk["overlap"][1]),end = "")
            elif chunk == entry[0]  :
                print("\033[34m0x%x\033[37m" % (chunk["addr"]+capsize*2),end = "")
            else  :
                if print_overlap :
                    if find_overlap(chunk,entry):
                        print("\033[31m0x%x\033[37m" % chunk["addr"],end ="")
                    else :
                        print("0x%x" % (chunk["addr"] + capsize*2),end = "")
                else :
                    print("0x%x" % (chunk["addr"] + capsize*2),end = "")
            if chunk != entry[-1]:
                print(" --> ",end = "")
        if len(tcache_entry[i]) > 0 :
            print("")

    return True



def putheapinfo(arena=None):
    if capsize == 0 :
        arch = getarch()
    if not putfastbin(arena) :
        return
    if "memerror" in top :
        print("\033[35m %20s:\033[31m 0x%x \033[33m(size : 0x%x)\033[31m (%s)\033[37m " % ("top",top["addr"],top["size"],top["memerror"]))
    else :
        print("\033[35m %20s:\033[34m 0x%x \033[33m(size : 0x%x)\033[37m " % ("top",top["addr"],top["size"]))

    print("\033[35m %20s:\033[34m 0x%x \033[33m(size : 0x%x)\033[37m " % ("last_remainder",last_remainder["addr"],last_remainder["size"]))
    if unsortbin and len(unsortbin) > 0 :
        print("\033[35m %20s:\033[37m " % "unsortbin",end="")
        for chunk in unsortbin :
            if "memerror" in chunk :
                print("\033[31m0x%x (%s)\033[37m" % (chunk["addr"],chunk["memerror"]),end = "")
            elif chunk["overlap"] and chunk["overlap"][0]:
                print("\033[31m0x%x (overlap chunk with \033[36m0x%x(%s)\033[31m )\033[37m" % (chunk["addr"],chunk["overlap"][0]["addr"],chunk["overlap"][1]),end = "")
            elif chunk == unsortbin[-1]:
                print("\033[34m0x%x\033[37m \33[33m(size : 0x%x)\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            else :
                print("0x%x \33[33m(size : 0x%x)\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            if chunk != unsortbin[-1]:
                print(" <--> ",end = "")
        print("")
    else :
        print("\033[35m %20s:\033[37m 0x%x" % ("unsortbin",0)) #no chunk in unsortbin
    for size,bins in smallbin.items() :
        idx = int((int(size,16)/(capsize*2)))-2 
        print("\033[33m(0x%03x)  %s[%2d]:\033[37m " % (int(size,16),"smallbin",idx),end="")
        for chunk in bins :
            if "memerror" in chunk :
                print("\033[31m0x%x (%s)\033[37m" % (chunk["addr"],chunk["memerror"]),end = "")
            elif chunk["size"] != int(size,16) :
                print("\033[36m0x%x (size error (0x%x))\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            elif chunk["overlap"] and chunk["overlap"][0]:
                print("\033[31m0x%x (overlap chunk with \033[36m0x%x(%s)\033[31m )\033[37m" % (chunk["addr"],chunk["overlap"][0]["addr"],chunk["overlap"][1]),end = "")
            elif chunk == bins[-1]:
                print("\033[34m0x%x\033[37m" % chunk["addr"],end = "")
            else :
                print("0x%x " % chunk["addr"],end = "")
            if chunk != bins[-1]:
                print(" <--> ",end = "")
        print("") 
    for idx,bins in largebin.items():
        print("\033[33m  %15s[%2d]:\033[37m " % ("largebin",idx-64),end="")
        for chunk in bins :
            if "memerror" in chunk :
                print("\033[31m0x%x (%s)\033[37m" % (chunk["addr"],chunk["memerror"]),end = "")
            elif chunk["overlap"] and chunk["overlap"][0]:
                print("\033[31m0x%x (overlap chunk with \033[36m0x%x(%s)\033[31m )\033[37m" % (chunk["addr"],chunk["overlap"][0]["addr"],chunk["overlap"][1]),end = "")
            elif largbin_index(chunk["size"]) != idx : 
                print("\033[31m0x%x (incorrect bin size :\033[36m 0x%x\033[31m)\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            elif chunk == bins[-1]:
                print("\033[34m0x%x\033[37m \33[33m(size : 0x%x)\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            else :
                print("0x%x \33[33m(size : 0x%x)\033[37m" % (chunk["addr"],chunk["size"]),end = "")
            if chunk != bins[-1]:
                print(" <--> ",end = "")
        print("")
    if not arena :
        put_tcache()
    if corruptbin :
        print("\033[31m Some bins is corrupted !\033[37m")

def putarenainfo():
    set_main_arena()
    if capsize == 0 :
        arch = getarch()
    cur_arena = 0
    if main_arena :
        try : 
            if capsize == 4 :
                nextoff = 0x10d*capsize + 0xc
            else :
                nextoff = 0x10d*capsize
            count = 0
            print("  Main Arena  ".center(50,"="))
            putheapinfo(main_arena)
            cur_arena = int(gdb.execute("x/" + word + hex(main_arena+nextoff),to_string=True).split(":")[1].strip(),16)
            while cur_arena != main_arena  :
                count +=1
                print(("  Arena " + str(count) + "  ").center(50,"="))
                putheapinfo(cur_arena)
                cur_arena = int(gdb.execute("x/" + word  + hex(cur_arena+nextoff),to_string=True).split(":")[1].strip(),16)
        except :
            print("Memory Error (heap)")
    else :
        print("Can't find heap info ")

def putheapinfoall():
    cur_thread_id = get_curthread()
    all_threads = get_all_threads()
    for thread_id in all_threads:
        if thread_id == cur_thread_id :
            print("\033[33;1m"+("  Thread " + str(thread_id) + "  ").center(50,"=") + "\033[0m",end="")
        else :
            print(("  Thread " + str(thread_id) + "  ").center(50,"="),end="")
        result = thread_cmd_execute(thread_id,"heapinfo")
        print(result.split("):")[1],end="")


def putinused():
    print("\033[33m %s:\033[37m " % "inused ",end="")
    for addr,(start,end,chunk) in allocmemoryarea.items() :
        print("0x%x," % (chunk["addr"]),end="")
    print("")


def parse_heap(arena=None):
    if capsize == 0 :
        arch = getarch()
    if not get_heap_info(arena):
        print("can't find heap info")
        return

    hb = get_heapbase()
    chunkaddr = hb
    if not chunkaddr:
        print("Can't find heap")
        return
    print('\033[1;33m{:<20}{:<20}{:<21}{:<20}{:<18}{:<18}\033[0m'.format('addr', 'prev', 'size', 'status', 'fd', 'bk'))
    while chunkaddr != top["addr"] :
        try :
            cmd = "x/" + word + hex(chunkaddr)
            prev_size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(chunkaddr + capsize*1)
            size = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(chunkaddr + capsize*2)
            if size == 0 and chunkaddr == hb :
                chunkaddr += capsize*2
                continue
            fd = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(chunkaddr + capsize*3)
            bk = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            cmd = "x/" + word + hex(chunkaddr + (size & 0xfffffffffffffff8) + capsize)
            nextsize = int(gdb.execute(cmd,to_string=True).split(":")[1].strip(),16)
            status = nextsize & 1 
            size = size & 0xfffffffffffffff8
            if size == 0 :
                print("\033[31mCorrupt ?! \033[0m(size == 0) (0x%x)" % chunkaddr)
                break 
            if status :
                if chunkaddr in fastchunk or chunkaddr in all_tcache_entry:
                    msg = "\033[1;34m Freed \033[0m"
                    print('0x{:<18x}0x{:<18x}0x{:<18x}{:<16}{:>18}{:>18}'.format(chunkaddr, prev_size, size, msg, hex(fd), "None"))
                else :
                    msg = "\033[31m Used \033[0m"
                    print('0x{:<18x}0x{:<18x}0x{:<18x}{:<16}{:>18}{:>18}'.format(chunkaddr, prev_size, size, msg, "None", "None"))
            else :
                msg = "\033[1;34m Freed \033[0m"
                print('0x{:<18x}0x{:<18x}0x{:<18x}{:<16}{:>18}{:>18}'.format(chunkaddr, prev_size, size, msg, hex(fd), hex(bk)))
            chunkaddr = chunkaddr + (size & 0xfffffffffffffff8)

            if chunkaddr > top["addr"] :
                print("\033[31mCorrupt ?!\033[0m")
                break 
        except :
            print("Corrupt ?!")
            break

def fastbin_idx(size):
    if capsize == 0 :
        arch = getarch()
    if capsize == 8 :
        return (size >> 4) - 2
    else:
        return (size >> 3) - 2

def fake_fast(addr,size):
    if not get_heap_info():
        print("Can't find heap info")
        return
    result = []
    idx = fastbin_idx(size)
    chunk_size = size & 0xfffffffffffffff8
    start = addr - chunk_size
    chunk_data = gdb.selected_inferior().read_memory(start, chunk_size)
    for offset in range(chunk_size-4):
        fake_size = u32(chunk_data[offset:offset+4])
        if fastbin_idx(fake_size) == idx :
            if ((fake_size & 2 == 2) and (fake_size & 4 == 4)) or (fake_size & 4 == 0) :
                padding = addr - (start+offset-capsize) - capsize*2
                result.append((start+offset-capsize,padding))
    return result

def get_fake_fast(addr,size = None):
    if capsize == 0 :
        arch = getarch()
    fast_max = int(gdb.execute("x/" + word + "&global_max_fast",to_string=True).split(":")[1].strip(),16)
    if not fast_max :
        fast_max = capsize*0x10
    if size :
        chunk_list = fake_fast(addr,size)
        for fakechunk in chunk_list :
            if len(chunk_list) > 0 :
                print("\033[1;33mfake chunk : \033[1;0m0x{:<12x}\033[1;33m  padding :\033[1;0m {:<8d}".format(fakechunk[0],fakechunk[1]))
    else :
        for i in range(int(fast_max/(capsize*2)-1)):
            size = capsize*2*2 + i*capsize*2
            chunk_list = fake_fast(addr,size) 
            if len(chunk_list) > 0 :
                print("-- size : %s --" % hex(size))
                for fakechunk in chunk_list :
                    print("\033[1;33mfake chunk :\033[1;0m 0x{:<12x}\033[1;33m  padding :\033[1;0m {:<8d}".format(fakechunk[0],fakechunk[1]))