import io
import os
import logging
import tempfile

import envi
import envi.exc as e_exc
import envi.const as e_const
import envi.memcanvas as e_mcanvas

import vivisect
import vivisect.exc as v_exc
import vivisect.const as v_const
import vivisect.tools.graphutil as v_t_graphutil

import vivisect.tests.helpers as helpers
import vivisect.tests.utils as v_t_utils


logger = logging.getLogger(__name__)


def glen(g):
    return len([x for x in g])


def isint(x):
    return type(x) is int


class VivisectTest(v_t_utils.VivTest):
    @classmethod
    def setUpClass(cls):
        cls.firefox_vw = helpers.getTestWorkspace('windows', 'amd64', 'firefox.exe')
        cls.chgrp_vw = helpers.getTestWorkspace('linux', 'i386', 'chgrp.llvm')
        cls.vdir_vw = helpers.getTestWorkspace('linux', 'i386', 'vdir.llvm')
        cls.sh_vw = helpers.getTestWorkspace('linux', 'arm', 'sh')
        cls.chown_vw = helpers.getTestWorkspace('linux', 'amd64', 'chown')

        for vw in cls.vdir_vw, cls.chgrp_vw, cls.firefox_vw:
            oldcanv = vw.canvas
            vw.canvas = e_mcanvas.StringMemoryCanvas(vw)
            vw.canvas.renderers = oldcanv.renderers
            vw.canvas.setRenderer('viv')

    def test_xrefs_types(self):
        '''
        Test that we have data consistency in xrefs
        '''
        for vw in [self.chgrp_vw, self.vdir_vw, self.firefox_vw]:
            for xfrom, xto, xtype, xflags in vw.getXrefs():
                self.assertEqual((xfrom, isint(xfrom)),
                                 (xfrom, True))
                self.assertEqual((xto, isint(xto)),
                                 (xto, True))
                self.assertEqual((xtype, isint(xtype)),
                                 (xtype, True))
                self.assertEqual((xflags, isint(xflags)),
                                 (xflags, True))

    def test_cli_search(self):
        '''
        Test that EnviCli.do_search works
        '''
        self.chgrp_vw.do_search("-e utf-16le foo")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("pattern not found: 66006f006f00 (b'f\\x00o\\x00o\\x00')", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-X ffffffff253c40050868")
        output = self.chgrp_vw.canvas.strval
        self.assertIn('0x08048e5d: -r-x loc_08048e5d + 0x0\ndone ', output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-E 0x805104b")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("matches for: 4b100508 (b'K\\x10\\x05\\x08')\n0x08050818: -r-x loc_08050818 + 0x0\ndone (1 results).", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-r ab.*rt")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("(b'ab.*rt')\n0x0804874e:", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-r al.*rt -c")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x8051333  61 6c 6d 6f 73 74 20 63 65 72 74 61 69 6e 6c 79   almost certainly", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-c -r qsort")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x80488e3  71 73 6f 72 74 00 66 63 6e 74 6c 00 6d 65 6d 6d   qsort.fcntl", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-c -r qsort -R 0x8048000:0x200")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("searching from 0x08048000 for 512 bytes\npattern not found: 71736f7274 (b'qsort')\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_search("-c -r qsort -R 0x8048000:0x2000")
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x80488e3  71 73 6f 72 74 00 66 63 6e 74 6c 00 6d 65 6d 6d   qsort.fcntl.memm\n0x80488f3  6f 76 65 00 62 69 6e 64 74 65 78 74 64 6f 6d 61   ove.bindtextdoma\n\ndone (1 results).\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_searchopcode(self):
        '''
        Test that VivCli.do_searchopcodes works
        '''
        # i have no idea why this is necessary (we did it at setup time)
        # but the current renderer at this point is envi.memcanvas.renderers.ByteRend
        self.chgrp_vw.canvas.setRenderer('viv')
        self.chgrp_vw.do_searchopcodes('sar')
        output = self.chgrp_vw.canvas.strval
        self.assertIn(".text:0x08049294  d1f8             sar eax,1\n\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-f 0x08050200 ret')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("matches for: 726574 ('ret')\n.text:0x0805020a  c3               ret \n\ndone (1 results).\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-c rol')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("pattern not found: 726f6c ('rol')\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-o rol')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("pattern not found: 726f6c ('rol')\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-t rol')
        output = self.chgrp_vw.canvas.strval
        self.assertIn(".text:0x08050249  66d3c0           rol ax,cl", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-M red rol')
        output = self.chgrp_vw.canvas.strval
        self.assertIn(".text:0x08050268  d2c0             rol al,cl", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_searchopcodes('-f 0x08050200 -R r.t')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("matches for: 722e74 ('r.t')\n.text:0x0805020a  c3               ret \n\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        ## FIXME: make tests when this works
    #def test_cli_argtrack(self):
    #    self.chgrp_vw.do_argtrack('')
    #    output = self.chgrp_vw.canvas.strval
    #    self.assertIn('Usage: argtrack <func_addr_expr> <arg_idx>', output)
    #    self.chgrp_vw.canvas.clearCanvas()

    #    self.chgrp_vw.do_argtrack('')
    #    output = self.chgrp_vw.canvas.strval
    #    self.assertIn('Usage: argtrack <func_addr_expr> <arg_idx>', output)
    #    self.chgrp_vw.canvas.clearCanvas()

    def test_cli_chat(self):
        self.chgrp_vw.do_chat('foo')
        output = self.chgrp_vw.canvas.strval
        self.assertIn(': foo', output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_codepath(self):
        self.chgrp_vw.do_codepath('0x0804bc10 0x0804c5c0')
        output = self.chgrp_vw.canvas.strval
        self.assertIn('chgrp.quotearg_n_options\n0x0804c6e0\t0x0804c6e0\t   5\tchgrp.xcharalloc\n0x0804c5c0\t0x0804c5c0', output)
        self.chgrp_vw.canvas.clearCanvas()

    # still unsure how to test do_emulate() well

    def test_cli_eval(self):
        self.chgrp_vw.do_eval('chgrp')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("chgrp = 0x08048000 loc_08048000 + 0", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_exports(self):
        self.chgrp_vw.do_exports('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("chgrp:\n    0x08050654  _IO_stdin_used\n    0x080491e0  __entry\n    0x08054170  __progname\n    0x08054180  __progname_full\n    0x08048e20  free\n    0x08054198  optarg\n    0x08054190  optind\n    0x08054180  program_invocation_name\n    0x08054170  program_invocation_short_name\n    0x08054178  stderr\n    0x08054194  stdout\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_filemeta(self):
        self.chgrp_vw.do_filemeta('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("Loaded Files:\n    chgrp\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_filemeta('chgrp')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("'DT_INIT': 134516068", output)
        self.assertIn("'canaries': False,\n 'imagebase': 134512640", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_fscope(self):
        self.chgrp_vw.do_fscope('-S 0x804c8a0')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x0804caf1 'strtol_error xstrtoul(const char *, char **, int, unsigned long *, const char *)\\x00'\n0x0804cb01 './lib/xstrtol.c\\x00'\n0x0804cb09 '0 <= strtol_base && strtol_base <= 36\\x00'\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_funcmeta(self):
        self.chgrp_vw.do_funcmeta('chgrp.plt_free')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("'MnemDist': {'jmp': 1},", output)
        self.assertIn("'Thunk': '*.free',", output)
        self.assertIn("'api': ('void', None, 'cdecl', '*.free', [('void *', 'ptr')])}", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_imports(self):
        self.chgrp_vw.do_imports('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x08054114 *.calloc", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_loc(self):
        self.chgrp_vw.do_loc('0x08054108')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("IMPORT: *.getgrgid", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_make(self):
        # create a play area so we don't dork up other analysis
        self.chgrp_vw.addMemoryMap(0x41008000, 7, 'play area', self.chgrp_vw.readMemory(0x8048000, 0xb000))
        self.chgrp_vw.addMemoryMap(0x41013f0c, 7, 'play area', self.chgrp_vw.readMemory(0x8053f0c, 0x1000))

        # -n <size> Make a number
        self.chgrp_vw.do_make('-n 4 0x41011987')
        self.chgrp_vw.do_loc('0x41011987')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("4 BYTES: 2701171072 (0xa1009980)", output)
        self.chgrp_vw.canvas.clearCanvas()

        # -s Make a string
        self.chgrp_vw.do_make('-s 0x41011980')
        self.chgrp_vw.do_loc('0x41011980')
        output = self.chgrp_vw.canvas.strval
        self.assertIn('"\'\\x00"\n', output)
        self.chgrp_vw.canvas.clearCanvas()

        # -u Make a unicode string
        self.chgrp_vw.do_make('-u 0x41011628')
        self.chgrp_vw.do_loc('0x41011628')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("u'-'\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        # -p <size> Make a pad
        self.chgrp_vw.do_make('-p 1 0x4100c49f')
        self.chgrp_vw.do_loc('0x4100c49f')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("90", output)
        self.chgrp_vw.canvas.clearCanvas()

        # -c Make code
        self.chgrp_vw.do_make('-c 0x41011982')
        self.chgrp_vw.do_loc('0x41011982')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("loop 0x41011904", output)
        self.chgrp_vw.canvas.clearCanvas()

        # -f Make function
        self.chgrp_vw.do_make('-f 0x41011982')
        self.assertEqual(self.chgrp_vw.getFunction(0x41011982), 0x41011982)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.delMemoryMap(0x41008000)
        self.chgrp_vw.delMemoryMap(0x41013f0c)

    def test_cli_mem(self):
        self.chgrp_vw.do_mem('-F viv chgrp.plt_free')
        output = self.chgrp_vw.canvas.strval
        self.assertIn(".plt:0x08048e20  FUNC: void cdecl chgrp.plt_free( void * ptr, )", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_mem('chgrp.plt_free')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x8048e20  ff 25 2c 40 05 08 68 40 00 00 00 e9 60 ff ff ff  ", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_maps(self):
        self.chgrp_vw.do_maps('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("[ address ] [ size ] [ perms ] [ File ]\n0x08048000     44K\t-r-x\tchgrp\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_memcmp(self):
        self.chgrp_vw.do_memcmp('chgrp.plt_strcmp chgrp.plt_strncmp 16')
        output = self.chgrp_vw.canvas.strval
        self.assertIn('==== 1 byte difference at offset 2\n0x08048db2:10\n0x08049122:ec\n==== 2 byte difference at offset 7\n0x08048db7:0800\n0x08049127:c001\n==== 2 byte difference at offset 12\n0x08048dbc:d0ff\n0x0804912c:60fc\n', output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_memdump(self):
        tmpf = tempfile.NamedTemporaryFile(delete=False)
        try:
            self.chgrp_vw.do_memdump(f'chgrp.plt_strncmp {tmpf.name} 16')
            with open(tmpf.name, 'rb') as fd:
                dumpedmem = fd.read()
            vwmem = self.chgrp_vw.readMemory(self.chgrp_vw.parseExpression('chgrp.plt_strncmp'), 16)
            self.assertEqual(dumpedmem, vwmem)
            self.chgrp_vw.canvas.clearCanvas()
        finally:
            tmpf.close()
            os.unlink(tmpf.name)

    def test_cli_names(self):
        self.chgrp_vw.do_names('plt_.*64')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x08048e60: chgrp.plt_fseeko64", output)
        self.chgrp_vw.do_names('__entry')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("0x080491e0: chgrp.__entry  ('chgrp' + 0x11e0)", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_pathcount(self):
        self.chgrp_vw.do_pathcount('chgrp.quotearg_free')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("Path through 0x0804b8a0: ['0x804b8a0', '0x804b8d5', '0x804b8fb', '0x804b915']", output)
        self.assertIn("Total Paths: 8", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_report(self):
        self.chgrp_vw.do_report('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("Path:     vivisect.reports.overlaplocs (Name: Overlapped Locations)", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_report('vivisect.reports.funccomplexity')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("chgrp.plt_fseeko64:\n    Code Blocks: 1\n    Mnem Dist: {'jmp': 1}\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_script(self):
        tmpf = tempfile.NamedTemporaryFile(delete=False)
        try:
            tmpf.write(b"vw.vprint('doin the do')\nvw.vprint(repr(argv))")
            tmpf.flush()
            self.chgrp_vw.do_script(f'{tmpf.name} arg1 arg2 arg3')
            output = self.chgrp_vw.canvas.strval
            self.assertIn(f"doin the do\n['{tmpf.name}', 'arg1', 'arg2', 'arg3']", output)
            self.chgrp_vw.canvas.clearCanvas()
        finally:
            tmpf.close()
            os.unlink(tmpf.name)

    def test_cli_symboliks(self):
        self.chgrp_vw.do_symboliks('')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("-A  Run the emu and show the state of the machine for all found paths", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_symboliks('0x08050129')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("RETURN  Mem(o_add(Arg(0,width=4),Const(0x00000008,4),4), Const(0x00000004,4))", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_vampsig(self):
        self.chgrp_vw.do_vampsig('chgrp.rpl_fseeko')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("""SIGNATURE: 5553575683ec0c8b7424208b46083b4604750e8b46143b46107506837e2400740c83c40c5e5f5b5de9538dffff8b7c24288b5c24248b6c242c83ec0c56e82e8fffff83c41055575350e8a28fffff83c41089c121d183f9ff740d8026ef89464c89565031c0eb05b8ffffffff83c40c5e5f5b5dc3\nMASK: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff""", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_cli_xrefs(self):
        self.chgrp_vw.do_xrefs('-F 0x08050129')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("From: 0x08050129, To: 0x080490d0, Type: Code, Flags: 0x00010001\n", output)
        self.chgrp_vw.canvas.clearCanvas()

        self.chgrp_vw.do_xrefs('-T chgrp.plt_lseek64')
        output = self.chgrp_vw.canvas.strval
        self.assertIn("From: 0x0804fe94, To: 0x080490d0, Type: Code, Flags: 0x00010001\n", output)
        self.chgrp_vw.canvas.clearCanvas()

    def test_loc_types(self):
        '''
        Test that we have data consistency in locations
        '''
        for vw in [self.chgrp_vw, self.vdir_vw, self.firefox_vw]:
            for lva, lsize, ltype, linfo in vw.getLocations():
                self.assertEqual((lva, isint(lva)),
                                 (lva, True))
                self.assertEqual((lsize, isint(lsize)),
                                 (lsize, True))
                self.assertEqual((ltype, isint(ltype)),
                                 (ltype, True))
                if linfo:
                    self.assertTrue(type(linfo) in (int, str, list))

    def test_vaset_populate(self):
        '''
        Make sure the the VASets are populated in roughly the way we expect
        '''
        vw = self.vdir_vw
        ans = {
            'FileSymbols': [('', 0), ('filenamecat-lgpl.c', 0), ('gettime.c', 0),
                            ('areadlink-with-size.c', 0), ('filenamecat.c', 0), ('umaxtostr.c', 0),
                            ('close-stream.c', 0), ('filemode.c', 0), ('obstack.c', 0),
                            ('mbswidth.c', 0), ('imaxtostr.c', 0), ('mbsalign.c', 0),
                            ('time_rz.c', 0), ('fflush.c', 0), ('hash-triple.c', 0),
                            ('hard-locale.c', 0), ('se-selinux.c', 0), ('xstrtoul.c', 0),
                            ('mpsort.c', 0), ('version-etc-fsf.c', 0), ('xdectoumax.c', 0),
                            ('hash.c', 0), ('dirname-lgpl.c', 0), ('xgetcwd.c', 0),
                            ('quotearg.c', 0), ('hash-pjw.c', 0), ('closeout.c', 0),
                            ('progname.c', 0), ('localcharset.c', 0), ('basename-lgpl.c', 0),
                            ('ls.c', 0), ('human.c', 0), ('version-etc.c', 0),
                            ('nstrftime.c', 0), ('c-ctype.c', 0), ('xgethostname.c', 0),
                            ('c-strncasecmp.c', 0), ('xalloc-die.c', 0), ('xstrtol-error.c', 0),
                            ('crtstuff.c', 0), ('ls-vdir.c', 0), ('argmatch.c', 0),
                            ('xstrtoumax.c', 0), ('canonicalize.c', 0), ('stat-time.c', 0),
                            ('calloc.c', 0), ('file-set.c', 0), ('xmalloc.c', 0),
                            ('timespec.c', 0), ('fseeko.c', 0), ('file-has-acl.c', 0),
                            ('bitrotate.c', 0), ('idcache.c', 0), ('mbrtowc.c', 0),
                            ('dirname.c', 0), ('exitfail.c', 0), ('filevercmp.c', 0),
                            ('fclose.c', 0), ('version.c', 0), ('same.c', 0)],
            'DynamicBranches': [(134541059, 'call eax', 65537), (134555409, 'call dword [edi + 28]', 65541),
                                (134540947, 'call ebp', 65537), (134580376, 'call dword [ecx + 28]', 65541),
                                (134553117, 'call dword [ecx + 24]', 65541), (134540968, 'call ebp', 65537),
                                (134580408, 'call dword [ecx + 32]', 65541), (134554170, 'call eax', 65537),
                                (134519869, 'call edx', 65537), (134547907, 'call dword [esp + 52]', 65541),
                                (134555467, 'call dword [edi + 28]', 65541), (134554325, 'call dword [esi + 32]', 65541),
                                (134541400, 'call eax', 65537), (134561888, 'call eax', 65537),
                                (134562273, 'call dword [esp + 68]', 65541), (134583780, 'call dword [ebx + edi * 4 - 244]', 65541),
                                (134554213, 'call eax', 65537), (134541288, 'call eax', 65537),
                                (134553449, 'call edi', 65537), (134519792, 'call eax', 65537),
                                (134553075, 'call dword [ebx + 28]', 65541), (134540791, 'call dword [esp + 32]', 65541),
                                (134541176, 'call eax', 65537), (134562046, 'call dword [esp + 68]', 65541),
                                (134562878, 'jmp dword [0x0805e218 + ecx * 4]', 65545),
                                (134533775, 'jmp dword [0x08059bb8 + ecx * 4]', 65545),
                                (134544641, 'jmp dword [0x08059fac + edx * 4]', 65545),
                                (134535515, 'jmp dword [0x08059e30 + ecx * 4]', 65545),
                                (134569625, 'jmp dword [0x0805e4d8 + eax * 4]', 65545),
                                (134567270, 'jmp dword [0x0805e43c + ebp * 4]', 65545),
                                (134570812, 'jmp dword [0x0805e6d4 + edx * 4]', 65545),
                                (134577906, 'jmp dword [0x0805eb58 + ebp * 4]', 65545),
                                (134578230, 'jmp dword [0x0805ebec + eax * 4]', 65545),
                                (134579289, 'jmp dword [0x0805ef3c + edi * 4]', 65545),
                                (134521960, 'jmp dword [0x08059718 + eax * 4]', 65545),
                                (134533577, 'jmp dword [0x08059ba4 + ebp * 4]', 65545),
                                (134576219, 'jmp dword [0x0805e810 + ebx * 4]', 65545),
                                (134524193, 'jmp dword [0x08059b68 + eax * 4]', 65545),
                                (134548286, 'jmp dword [0x0805dde4 + ecx * 4]', 65545),
                                (134524938, 'jmp dword [0x08059b78 + eax * 4]', 65545),
                                (134579170, 'jmp dword [0x0805ee7c + eax * 4]', 65545),
                                (134528750, 'jmp dword [0x08059b90 + eax * 4]', 65545),
                                (134561776, 'jmp dword [0x0805e09c + eax * 4]', 65545),
                                (134533607, 'jmp dword [0x08059d9c + ecx * 4]', 65545),
                                (134578347, 'jmp dword [0x0805ecac + edi * 4]', 65545),
                                (134563178, 'jmp dword [0x0805e250 + edi * 4]', 65545),
                                (134569031, 'jmp dword [0x0805e4ac + edi * 4]', 65545)],
            'NoReturnCalls': [(134573120,), (134524676,), (134577802,), (134582295,), (134551055,), (134525456,),
                              (134568495,), (134534039,), (134528303,), (134578863,), (134577311,),
                              (134549928,), (134544175,), (134553138,), (134572852,),
                              (134550582,), (134579529,), (134572857,), (134534204,), (134577471,), (134580544,),
                              (134568769,), (134546505,), (134578743,), (134553208,), (134555349,),
                              (134521306,), (134572084,), (134578576,), (134554727,),
                              (134576873,), (134521331,), (134550133,), (134521336,), (134529017,),
                              (134524668,), (134577049,), (134577158,),
                              (134576974,), (134576909,),

                              (134531547,),  # void usage(int status) - call exit
                              (134547258,),  # void __argmatch_die() - call usage
                              (134524568,),  # int decode_switches(int argc, char** argv) - call usage
                              (134524356,),  # int decode_switches(int argc, char** argv) - call usage
                              (134524581,),  # int decode_switches(int argc, char** argv) - call usage
                              (134580057,),  # _obstack_begin_worker(...) - call obstack_alloc_failed_handler
                              (134580661,),  # void print_and_abort() - call exit
                              (134580336,),  # void _obstack_newchunk(...) - call obstack_alloc_failed_handler
                              ],
            'Bookmarks': [],
            'SwitchCases': [(134533577, 134533577, 162),
                            (134578347, 134578347, 53),
                            (134524193, 134524193, 3),
                            (134578230, 134578230, 101),
                            (134577906, 134577906, 36),
                            (134563178, 134563178, 126),
                            (134567270, 134567270, 3),
                            (134548286, 134548286, 93),
                            (134524938, 134524938, 173),
                            (134579289, 134579289, 53),
                            (134569625, 134569625, 128),
                            (134533775, 134533775, 120),
                            (134528750, 134528750, 4),
                            (134535515, 134535515, 106),
                            (134533607, 134533607, 36),
                            (134570812, 134570812, 1),
                            (134579170, 134579170, 101),
                            (134521960, 134521960, 453),
                            (134562878, 134562878, 140),
                            (134544641, 134544641, 11),
                            (134561776, 134561776, 94),
                            (134569031, 134569031, 139),
                            (134576219, 134576219, 9)],
            'thunk_reg': [(134519744, 'ebx', 134615040),
                          (134519715, 'ebx', 134615040)],
            'FuncWrappers': [],
            'Emulation Anomalies': [(134546338, 'DivideByZero at 0x80503a2'),
                                    (134544390, 'DivideByZero at 0x804fc06'),
                                    (134545954, 'DivideByZero at 0x8050222'),
                                    (134546126, 'DivideByZero at 0x80502ce')],
            'WeakSymbols': [('data_start', 0x80611b0),
                            ('program_invocation_name', 0x8061300),
                            ('program_invocation_short_name', 0x80612f0),
                            ('__gmon_start__', 0x8060ffc)],
            'PointersFromFile': [(0x8060f0c, 0x8049c80, 'vdir', '__frame_dummy_init_array_entry'),
                                 (0x8060f10, 0x8049c50, 'vdir', '__do_global_dtors_aux_fini_array_entry'),
                                 (0x805a00c, 0x805d77c, 'vdir', 'long_options'),
                                 (0x805a01c, 0x805e7c9, 'vdir', 'long_options'),
                                 (0x805a02c, 0x805da20, 'vdir', 'long_options'),
                                 (0x805a03c, 0x805d6fa, 'vdir', 'long_options'),
                                 (0x805a04c, 0x805d700, 'vdir', 'long_options'),
                                 (0x805a05c, 0x805d70a, 'vdir', 'long_options'),
                                 (0x805a06c, 0x805d722, 'vdir', 'long_options'),
                                 (0x805a07c, 0x805d731, 'vdir', 'long_options'),
                                 (0x805a08c, 0x805d737, 'vdir', 'long_options'),
                                 (0x805a09c, 0x805d741, 'vdir', 'long_options'),
                                 (0x805a0ac, 0x805d751, 'vdir', 'long_options'),
                                 (0x805a0bc, 0x805d75a, 'vdir', 'long_options'),
                                 (0x805a0cc, 0x805d76d, 'vdir', 'long_options'),
                                 (0x805a0dc, 0x805d54a, 'vdir', 'long_options'),
                                 (0x805a0ec, 0x805d534, 'vdir', 'long_options'),
                                 (0x805a0fc, 0x805d775, 'vdir', 'long_options'),
                                 (0x805a10c, 0x805d780, 'vdir', 'long_options'),
                                 (0x805a11c, 0x805d78f, 'vdir', 'long_options'),
                                 (0x805a12c, 0x805d798, 'vdir', 'long_options'),
                                 (0x805a13c, 0x805d7a2, 'vdir', 'long_options'),
                                 (0x805a14c, 0x805d7a5, 'vdir', 'long_options'),
                                 (0x805a15c, 0x805d7be, 'vdir', 'long_options'),
                                 (0x805a16c, 0x805d7e6, 'vdir', 'long_options'),
                                 (0x805a17c, 0x805d7eb, 'vdir', 'long_options'),
                                 (0x805a18c, 0x805d585, 'vdir', 'long_options'),
                                 (0x805a19c, 0x805d7f2, 'vdir', 'long_options'),
                                 (0x805a1ac, 0x805d7fe, 'vdir', 'long_options'),
                                 (0x805a1bc, 0x805d806, 'vdir', 'long_options'),
                                 (0x805a1cc, 0x805d597, 'vdir', 'long_options'),
                                 (0x805a1dc, 0x805d811, 'vdir', 'long_options'),
                                 (0x805a1ec, 0x805d55f, 'vdir', 'long_options'),
                                 (0x805a1fc, 0x805d81b, 'vdir', 'long_options'),
                                 (0x805a20c, 0x805d551, 'vdir', 'long_options'),
                                 (0x805a21c, 0x805d82e, 'vdir', 'long_options'),
                                 (0x805a22c, 0x805d558, 'vdir', 'long_options'),
                                 (0x805a23c, 0x805d836, 'vdir', 'long_options'),
                                 (0x805a24c, 0x805d571, 'vdir', 'long_options'),
                                 (0x805a25c, 0x805d579, 'vdir', 'long_options'),
                                 (0x805a26c, 0x805d841, 'vdir', 'long_options'),
                                 (0x805a27c, 0x805d84c, 'vdir', 'long_options'),
                                 (0x805a28c, 0x805d854, 'vdir', 'long_options'),
                                 (0x805a29c, 0x805d85b, 'vdir', 'long_options'),
                                 (0x805a2ac, 0x805dae1, 'vdir', 'long_options'),
                                 (0x805a2e0, 0x805d860, 'vdir', 'sort_args'),
                                 (0x805a2e4, 0x805d558, 'vdir', 'sort_args'),
                                 (0x805a2e8, 0x805d54a, 'vdir', 'sort_args'),
                                 (0x805a2ec, 0x805d865, 'vdir', 'sort_args'),
                                 (0x805a2f0, 0x805dae1, 'vdir', 'sort_args'),
                                 (0x805a30c, 0x805d86f, 'vdir', 'time_args'),
                                 (0x805a310, 0x805d875, 'vdir', 'time_args'),
                                 (0x805a314, 0x805d87c, 'vdir', 'time_args'),
                                 (0x805a318, 0x805d880, 'vdir', 'time_args'),
                                 (0x805a31c, 0x805d886, 'vdir', 'time_args'),
                                 (0x805a340, 0x805d88d, 'vdir', 'format_args'),
                                 (0x805a344, 0x805d895, 'vdir', 'format_args'),
                                 (0x805a348, 0x805d89a, 'vdir', 'format_args'),
                                 (0x805a34c, 0x805d8a1, 'vdir', 'format_args'),
                                 (0x805a350, 0x805d8ac, 'vdir', 'format_args'),
                                 (0x805a354, 0x805d8b3, 'vdir', 'format_args'),
                                 (0x805a358, 0x805d8bc, 'vdir', 'format_args'),
                                 (0x805a384, 0x805e7dd, 'vdir', 'when_args'),
                                 (0x805a388, 0x805d8ca, 'vdir', 'when_args'),
                                 (0x805a38c, 0x805d8ce, 'vdir', 'when_args'),
                                 (0x805a390, 0x805d8d4, 'vdir', 'when_args'),
                                 (0x805a394, 0x805d8da, 'vdir', 'when_args'),
                                 (0x805a398, 0x805d860, 'vdir', 'when_args'),
                                 (0x805a39c, 0x805d8dd, 'vdir', 'when_args'),
                                 (0x805a3a0, 0x805d8e5, 'vdir', 'when_args'),
                                 (0x805a3a4, 0x805d8e2, 'vdir', 'when_args'),
                                 (0x805a3bc, 0x805d860, 'vdir', 'indicator_style_args'),
                                 (0x805a3c0, 0x805d8e9, 'vdir', 'indicator_style_args'),
                                 (0x805a3c4, 0x805d798, 'vdir', 'indicator_style_args'),
                                 (0x805a3c8, 0x805d78f, 'vdir', 'indicator_style_args'),
                                 (0x805a3d8, 0x805d566, 'vdir', 'time_style_args'),
                                 (0x805a3dc, 0x805d905, 'vdir', 'time_style_args'),
                                 (0x805a3e0, 0x805d90a, 'vdir', 'time_style_args'),
                                 (0x805a3e4, 0x805b6f2, 'vdir', 'time_style_args'),
                                 (0x805a3fc, 0x805d987, 'vdir', 'indicator_name'),
                                 (0x805a400, 0x805d98a, 'vdir', 'indicator_name'),
                                 (0x805a404, 0x805d98d, 'vdir', 'indicator_name'),
                                 (0x805a408, 0x805d76a, 'vdir', 'indicator_name'),
                                 (0x805a40c, 0x805d8da, 'vdir', 'indicator_name'),
                                 (0x805a410, 0x805d990, 'vdir', 'indicator_name'),
                                 (0x805a414, 0x805d993, 'vdir', 'indicator_name'),
                                 (0x805a418, 0x805d996, 'vdir', 'indicator_name'),
                                 (0x805a41c, 0x805d999, 'vdir', 'indicator_name'),
                                 (0x805a420, 0x805d90b, 'vdir', 'indicator_name'),
                                 (0x805a424, 0x805d99c, 'vdir', 'indicator_name'),
                                 (0x805a428, 0x805d99f, 'vdir', 'indicator_name'),
                                 (0x805a42c, 0x805d9a2, 'vdir', 'indicator_name'),
                                 (0x805a430, 0x805d858, 'vdir', 'indicator_name'),
                                 (0x805a434, 0x805d9a5, 'vdir', 'indicator_name'),
                                 (0x805a438, 0x805d9a8, 'vdir', 'indicator_name'),
                                 (0x805a43c, 0x805d9ab, 'vdir', 'indicator_name'),
                                 (0x805a440, 0x805d9ae, 'vdir', 'indicator_name'),
                                 (0x805a444, 0x805d71f, 'vdir', 'indicator_name'),
                                 (0x805a448, 0x805d9b1, 'vdir', 'indicator_name'),
                                 (0x805a44c, 0x805d9b4, 'vdir', 'indicator_name'),
                                 (0x805a450, 0x805d9b7, 'vdir', 'indicator_name'),
                                 (0x805a454, 0x805d9ba, 'vdir', 'indicator_name'),
                                 (0x805a458, 0x805d9bd, 'vdir', 'indicator_name'),
                                 (0x805b524, 0x804e130, 'vdir', 'sort_functions'),
                                 (0x805b528, 0x804e150, 'vdir', 'sort_functions'),
                                 (0x805b52c, 0x804e1b0, 'vdir', 'sort_functions'),
                                 (0x805b530, 0x804e1d0, 'vdir', 'sort_functions'),
                                 (0x805b534, 0x804e230, 'vdir', 'sort_functions'),
                                 (0x805b538, 0x804e250, 'vdir', 'sort_functions'),
                                 (0x805b53c, 0x804e2b0, 'vdir', 'sort_functions'),
                                 (0x805b540, 0x804e2d0, 'vdir', 'sort_functions'),
                                 (0x805b544, 0x804e330, 'vdir', 'sort_functions'),
                                 (0x805b548, 0x804e350, 'vdir', 'sort_functions'),
                                 (0x805b54c, 0x804e3b0, 'vdir', 'sort_functions'),
                                 (0x805b550, 0x804e3d0, 'vdir', 'sort_functions'),
                                 (0x805b554, 0x804e430, 'vdir', 'sort_functions'),
                                 (0x805b558, 0x804e450, 'vdir', 'sort_functions'),
                                 (0x805b55c, 0x804e4b0, 'vdir', 'sort_functions'),
                                 (0x805b560, 0x804e4d0, 'vdir', 'sort_functions'),
                                 (0x805b564, 0x804e530, 'vdir', 'sort_functions'),
                                 (0x805b568, 0x804e550, 'vdir', 'sort_functions'),
                                 (0x805b56c, 0x804e5b0, 'vdir', 'sort_functions'),
                                 (0x805b570, 0x804e5d0, 'vdir', 'sort_functions'),
                                 (0x805b574, 0x804e630, 'vdir', 'sort_functions'),
                                 (0x805b578, 0x804e650, 'vdir', 'sort_functions'),
                                 (0x805b57c, 0x804e6b0, 'vdir', 'sort_functions'),
                                 (0x805b580, 0x804e6d0, 'vdir', 'sort_functions'),
                                 (0x805b584, 0x804e730, 'vdir', 'sort_functions'),
                                 (0x805b588, 0x804e740, 'vdir', 'sort_functions'),
                                 (0x805b58c, 0x804e790, 'vdir', 'sort_functions'),
                                 (0x805b590, 0x804e7a0, 'vdir', 'sort_functions'),
                                 (0x805b5a4, 0x804e7f0, 'vdir', 'sort_functions'),
                                 (0x805b5a8, 0x804e810, 'vdir', 'sort_functions'),
                                 (0x805b5ac, 0x804e870, 'vdir', 'sort_functions'),
                                 (0x805b5b0, 0x804e890, 'vdir', 'sort_functions'),
                                 (0x805b5b4, 0x804e8f0, 'vdir', 'sort_functions'),
                                 (0x805b5b8, 0x804e910, 'vdir', 'sort_functions'),
                                 (0x805b5bc, 0x804e970, 'vdir', 'sort_functions'),
                                 (0x805b5c0, 0x804e990, 'vdir', 'sort_functions'),
                                 (0x805b5c4, 0x804e9f0, 'vdir', 'sort_functions'),
                                 (0x805b5c8, 0x804ea10, 'vdir', 'sort_functions'),
                                 (0x805b5cc, 0x804ea70, 'vdir', 'sort_functions'),
                                 (0x805b5d0, 0x804ea90, 'vdir', 'sort_functions'),
                                 (0x805b5d4, 0x804eaf0, 'vdir', 'sort_functions'),
                                 (0x805b5d8, 0x804eb10, 'vdir', 'sort_functions'),
                                 (0x805b5dc, 0x804eb70, 'vdir', 'sort_functions'),
                                 (0x805b5e0, 0x804eb90, 'vdir', 'sort_functions'),
                                 (0x805b5e4, 0x804ebf0, 'vdir', 'sort_functions'),
                                 (0x805b5e8, 0x804ec10, 'vdir', 'sort_functions'),
                                 (0x805b5ec, 0x804ec70, 'vdir', 'sort_functions'),
                                 (0x805b5f0, 0x804ec90, 'vdir', 'sort_functions'),
                                 (0x805b5f4, 0x804ecf0, 'vdir', 'sort_functions'),
                                 (0x805b5f8, 0x804ed10, 'vdir', 'sort_functions'),
                                 (0x805b5fc, 0x804ed70, 'vdir', 'sort_functions'),
                                 (0x805b600, 0x804ed90, 'vdir', 'sort_functions'),
                                 (0x805e050, 0x805d722, 'vdir', 'block_size_args'),
                                 (0x805e054, 0x805d7a2, 'vdir', 'block_size_args'),
                                 (0x805e75c, 0x805d7fe, 'vdir', 'quoting_style_args'),
                                 (0x805e760, 0x805e7b0, 'vdir', 'quoting_style_args'),
                                 (0x805e764, 0x805e7b6, 'vdir', 'quoting_style_args'),
                                 (0x805e768, 0x805e7c3, 'vdir', 'quoting_style_args'),
                                 (0x805e76c, 0x805e7d0, 'vdir', 'quoting_style_args'),
                                 (0x805e770, 0x805edb8, 'vdir', 'quoting_style_args'),
                                 (0x805e774, 0x805e7e4, 'vdir', 'quoting_style_args'),
                                 (0x805e778, 0x805e7c9, 'vdir', 'quoting_style_args'),
                                 (0x805e77c, 0x805b6f2, 'vdir', 'quoting_style_args'),
                                 (0x805e780, 0x805e7ec, 'vdir', 'quoting_style_args'),
                                 (0x80611bc, 0x805b722, 'vdir', 'color_indicator'),
                                 (0x80611c4, 0x805dca1, 'vdir', 'color_indicator'),
                                 (0x80611d4, 0x805b789, 'vdir', 'color_indicator'),
                                 (0x80611ec, 0x805d420, 'vdir', 'color_indicator'),
                                 (0x80611f4, 0x805d426, 'vdir', 'color_indicator'),
                                 (0x80611fc, 0x805d435, 'vdir', 'color_indicator'),
                                 (0x8061204, 0x805d42c, 'vdir', 'color_indicator'),
                                 (0x806120c, 0x805d432, 'vdir', 'color_indicator'),
                                 (0x8061214, 0x805d432, 'vdir', 'color_indicator'),
                                 (0x806122c, 0x805d438, 'vdir', 'color_indicator'),
                                 (0x8061234, 0x805d42c, 'vdir', 'color_indicator'),
                                 (0x806123c, 0x805d43e, 'vdir', 'color_indicator'),
                                 (0x8061244, 0x805d444, 'vdir', 'color_indicator'),
                                 (0x806124c, 0x805d44a, 'vdir', 'color_indicator'),
                                 (0x8061254, 0x805d450, 'vdir', 'color_indicator'),
                                 (0x806125c, 0x805d456, 'vdir', 'color_indicator'),
                                 (0x8061264, 0x805d45c, 'vdir', 'color_indicator'),
                                 (0x8061274, 0x805d462, 'vdir', 'color_indicator'),
                                 (0x8061280, 0x805d8ef, 'vdir', 'long_time_format'),
                                 (0x8061284, 0x805d8f9, 'vdir', 'long_time_format'),
                                 (0x8061294, 0x805dd7b, 'vdir', 'Version'),
                                 (0x8061298, 0x8050730, 'vdir', 'argmatch_die'),
                                 (0x80612a0, 0x80612a8, 'vdir', 'slotvec'),
                                 (0x80612ac, 0x806231c, 'vdir', 'slotvec0'),
                                 (0x80612e0, 0x8058980, 'vdir', 'obstack_alloc_failed_handler'),
                                 (0x8061000, 0x8060f14, 'vdir', '_GLOBAL_OFFSET_TABLE_'),
                                 ],
            'EmucodeFunctions': []

        }

        for name, valist in ans.items():
            retn = vw.getVaSetRows(name)
            try:
                self.assertEqual((name, set(retn)), (name, set(valist)))
            except Exception as e:
                mesg = f'On VaSet {name}, we failed due to: {str(e)}'
                self.fail(mesg)

        self.assertEqual(len(vw.getVaSetRows('CodeFragments')), 213)
        self.assertEqual(len(vw.getVaSetRows('EntryPoints')), 536)

    def test_basic_apis(self):
        '''
        Test a bunch of the simpler workspace APIs
        '''
        vw = self.firefox_vw
        self.assertIsNotNone(vw.parsedbin)
        self.assertEqual(set(['Emulation Anomalies', 'EntryPoints', 'SwitchCases', 'EmucodeFunctions', 'PointersFromFile', 'FuncWrappers', 'CodeFragments', 'DynamicBranches', 'Bookmarks', 'NoReturnCalls', 'DelayImports', 'Library Loads', 'pe:ordinals', 'SwitchCases_TimedOut', 'thunk_reg', 'ResolvedImports', 'Null Offset Functions']), set(vw.getVaSetNames()))

        self.assertEqual((0x14001fa5a, 6, 10, None), vw.getPrevLocation(0x14001fa60))
        self.assertEqual((0x14001fa5a, 6, 10, None), vw.getPrevLocation(0x14001fa60, adjacent=True))

        self.assertEqual(None, vw.getPrevLocation(0x140051fe0, adjacent=True))
        self.assertEqual((0x140051fd0, 8, 9, 'kernel32.lstrlenW'), vw.getPrevLocation(0x140051fe0, adjacent=False))

        self.assertEqual((0x140048ea0, 4, v_const.LOC_OP, envi.ARCH_AMD64), vw.getLocationByName('firefox.__entry'))

        ans = set([5368714880, 5368714906, 5368714941, 5368714975, 5368714999, 5368715022, 5368715029, 5368715044, 5368715058, 5368715070])
        for bva, bsize, bfunc in vw.getFunctionBlocks(0x140001680):
            self.assertIn(bva, ans)
            self.assertEqual(bfunc, 0x140001680)
        locs = [(5368713520, 5, 5, 131072),
                (5368713525, 3, 5, 131072),
                (5368713528, 6, 5, 131076),
                (5368713534, 3, 5, 131072),
                (5368713537, 4, 5, 131072),
                (5368713541, 4, 5, 131072),
                (5368713545, 4, 5, 131072),
                (5368713549, 6, 5, 131076)]
        for loc in vw.getLocationRange(0x140001130, 32):
            self.assertIn(loc, locs)

        # even missing a bunch, there still should be more than 76k here)
        self.assertTrue(len(vw.getLocations()) > 76000)

        # tuples are Name, Number of Locations, Size in bytes, Percentage of space
        ans = {0: ('Undefined', 0, 55596, 14),
               1: ('Num/Int',   715, 3703, 0),
               2: ('String',    265, 6485, 1),
               3: ('Unicode',   174, 5596, 1),
               4: ('Pointer',   361, 2888, 0),
               5: ('Opcode',    72507, 279377, 73),
               6: ('Structure', 1018, 12740, 3),
               7: ('Clsid',     0, 0, 0),
               8: ('VFTable',   0, 0, 0),
               9: ('Import Entry', 370, 2960, 0),
               10: ('Pad',      864, 8511, 2)}
        dist = vw.getLocationDistribution()
        for loctype, locdist in dist.items():
            self.assertEqual(locdist, ans[loctype])

    def test_render(self):
        vw = self.firefox_vw
        cb = vw.getCodeBlock(0x1400017b0)
        rndr = vw.getRenderInfo(cb[v_const.CB_VA], cb[v_const.CB_SIZE])
        self.assertIsNotNone(rndr)
        locs, funcs, names, comments, extras = rndr

        locans = [(5368715184, 1, 5, 131072),
                  (5368715185, 1, 5, 131072),
                  (5368715186, 5, 5, 131072),
                  (5368715191, 5, 5, 131076),
                  (5368715196, 3, 5, 131072),
                  (5368715199, 3, 5, 131072),
                  (5368715202, 8, 5, 131072),
                  (5368715210, 8, 5, 131072),
                  (5368715218, 8, 5, 131072),
                  (5368715226, 7, 5, 131072),
                  (5368715233, 3, 5, 131072),
                  (5368715236, 8, 5, 131072),
                  (5368715244, 8, 5, 131072),
                  (5368715252, 5, 5, 131072),
                  (5368715257, 5, 5, 131076),
                  (5368715262, 3, 5, 131072),
                  (5368715265, 5, 5, 131072),
                  (5368715270, 5, 5, 131072),
                  (5368715275, 9, 5, 131072),
                  (5368715284, 8, 5, 131072),
                  (5368715292, 6, 5, 131072),
                  (5368715298, 3, 5, 131072),
                  (5368715301, 7, 5, 131072),
                  (5368715308, 6, 5, 131076),
                  (5368715314, 5, 5, 131072),
                  (5368715319, 5, 5, 131072),
                  (5368715324, 8, 5, 131072),
                  (5368715332, 5, 5, 131072),
                  (5368715337, 2, 5, 131072),
                  (5368715339, 3, 5, 131072),
                  (5368715342, 6, 5, 131072),
                  (5368715348, 6, 5, 131076),
                  (5368715354, 7, 5, 131072),
                  (5368715361, 6, 5, 131076),
                  (5368715367, 3, 5, 131072),
                  (5368715370, 2, 5, 131112)]

        ops = {5368715184: 'push rsi',
               5368715185: 'push rdi',
               5368715186: 'mov eax,0x00001848',
               5368715191: 'call 0x140048a10',
               5368715196: 'sub rsp,rax',
               5368715199: 'mov rsi,rcx',
               5368715202: 'mov qword [rsp + 6248],rdx',
               5368715210: 'mov qword [rsp + 6256],r8',
               5368715218: 'mov qword [rsp + 6264],r9',
               5368715226: 'mov rax,qword [rip + 346167]',
               5368715233: 'xor rax,rsp',
               5368715236: 'mov qword [rsp + 6208],rax',
               5368715244: 'lea rdi,qword [rsp + 6248]',
               5368715252: 'mov qword [rsp + 56],rdi',
               5368715257: 'call 0x140001a40',
               5368715262: 'mov rcx,qword [rax]',
               5368715265: 'mov qword [rsp + 48],rdi',
               5368715270: 'mov qword [rsp + 32],rsi',
               5368715275: 'mov qword [rsp + 40],0',
               5368715284: 'lea rsi,qword [rsp + 4160]',
               5368715292: 'mov r8d,2048',
               5368715298: 'mov rdx,rsi',
               5368715301: 'mov r9,0xffffffff',
               5368715308: 'call qword [rip + 329734]',
               5368715314: 'lea rax,qword [rsp + 64]',
               5368715319: 'mov qword [rsp + 32],rax',
               5368715324: 'mov dword [rsp + 40],2048',
               5368715332: 'mov ecx,0x0000fde9',
               5368715337: 'xor edx,edx',
               5368715339: 'mov r8,rsi',
               5368715342: 'mov r9d,0xffffffff',
               5368715348: 'call qword [rip + 329182]',
               5368715354: 'lea rcx,qword [rip + 316585]',
               5368715361: 'call qword [rip + 329129]',
               5368715367: 'test rax,rax',
               5368715370: 'jz 0x1400018a3'}

        self.assertEqual(len(locans), len(locs))
        dcdd = 0
        for tupl in locs:
            self.assertIn(tupl, locans)
            if tupl[0] in ops:
                self.assertEqual(repr(vw.parseOpcode(tupl[0])), ops[tupl[0]])
                dcdd += 1
        self.assertEqual(dcdd, len(ops))
        self.assertEqual({5368715184: True}, funcs)
        self.assertEqual({5368715184: 'sub_1400017b0'}, names)

    def test_repr(self):
        vw = self.firefox_vw
        ans = [
            (0x14004d57a, "'MessageBoxW'"),
            (0x14000183c, 'mov dword [rsp + 40],2048'),
            (0x14004ed0a, "u'user32.dll'"),
            (0x140051e10, 'IMPORT: kernel32.LoadLibraryW'),
            (0x140056880, '8 BYTES: 0 (0x00000000)'),
            (0x14004c9b8, "'nsBrowserApp main'"),
            (0xdeadbeef, 'None'),
        ]
        for va, disp in ans:
            self.assertEqual(disp, vw.reprVa(va))

    def test_basic_reloc(self):
        vw = self.chgrp_vw
        rloc = vw.addRelocation(0xdeadbeef, v_const.RTYPE_BASEOFF)
        self.assertIsNone(rloc)

        rloc = vw.addRelocation(0x08054184, v_const.RTYPE_BASEPTR, data=0)
        self.assertIsNotNone(rloc)

    def test_naughty(self):
        '''
        Test us some error conditions
        '''
        vw = self.firefox_vw
        with self.assertRaises(v_exc.InvalidLocation):
            vw.delLocation(0x51515151)

        with self.assertRaises(v_exc.InvalidFunction):
            vw.setFunctionMeta(0xdeadbeef, 'monty', 'python')

        with self.assertRaises(v_exc.InvalidLocation):
            vw.getLocationByName(0xabad1dea)

    def test_basic_callers(self):
        vw = self.firefox_vw
        self.assertTrue(18000 < len(vw.getXrefs()))
        self.assertEqual(45, len(vw.getImportCallers('kernel32.GetProcAddress')))
        self.assertEqual(9, len(vw.getImportCallers('kernel32.LoadLibraryW')))

    def test_consecutive_jump_table(self):
        primaryJumpOpVa = 0x804c9b6
        secondJumpOpVa = 0x804ca2b

        pfva = self.chgrp_vw.getFunction(primaryJumpOpVa)
        sfva = self.chgrp_vw.getFunction(secondJumpOpVa)
        self.assertEqual(pfva, sfva)
        self.assertTrue(pfva is not None)

        # 2 actual codeblocks and 1 xref to the jumptable itself
        prefs = self.chgrp_vw.getXrefsFrom(primaryJumpOpVa)
        self.assertEqual(len(prefs), 3)
        cmnt = self.chgrp_vw.getComment(0x804c9bd)
        self.assertEqual(cmnt, 'Other Case(s): 2, 6, 8, 11, 15, 20, 21, 34, 38, 40, 47')

        cmnts = self.chgrp_vw.getComments()
        self.assertTrue(len(cmnts) > 1)

        # 13 actual codeblocks and 1 xref to the jumptable itself
        srefs = self.chgrp_vw.getXrefsFrom(secondJumpOpVa)
        self.assertEqual(len(srefs), 14)
        cmnt = self.chgrp_vw.getComment(0x804ca4a)
        self.assertEqual(cmnt, 'Other Case(s): 41')

    def test_jumptable_adjacent_strings(self):
        jmpop = 0x804abc7
        cases = [k[1] for k in self.chgrp_vw.getXrefsFrom(jmpop)]
        self.assertEqual(len(cases), 11)

        casevas = [
            0x804ac76,
            0x804ac88,
            0x804ac98,
            0x804ac86,
            0x804ac8a,
            0x804b671,
            0x804ac56,
            0x804acd6,
            0x804abce,
            0x804abf4,
            0x805162c
        ]
        self.assertEqual(casevas, cases)

        strlocs = [
            (0x8051930, 8, 2, b'literal\x00'),
            (0x8051938, 6, 2, b'shell\x00'),
            (0x805193e, 13, 2, b'shell-always\x00'),
            (0x805194b, 13, 2, b'shell-escape\x00'),
            (0x8051958, 20, 2, b'shell-escape-always\x00'),
            (0x805196c, 8, 2, b'c-maybe\x00'),
            (0x8051974, 8, 2, b'clocale\x00'),
        ]
        for lva, lsize, ltype, lstr in strlocs:
            loctup = self.chgrp_vw.getLocation(lva)
            self.assertEqual(loctup[0], lva)
            self.assertEqual(lsize, loctup[1])
            self.assertEqual(ltype, loctup[2])
            self.assertEqual(lstr, self.chgrp_vw.readMemory(lva, lsize))

        jmpop = 0x804c32b
        cases = [k[1] for k in self.chgrp_vw.getXrefsFrom(jmpop)]
        self.assertEqual(len(cases), 11)
        casevas = [
            0x0804c456,
            0x0804c332,
            0x0804c368,
            0x0804c37e,
            0x0804c394,
            0x0804c3aa,
            0x0804c3da,
            0x0804c3f0,
            0x0804c41f,
            0x0804c406,
            0x08051994
        ]
        self.assertEqual(casevas, cases)

    def test_chgrp_sections(self):
        '''
        Test that section information lines up with what we expect
        '''
        vw = self.chgrp_vw
        segments = set([
            (0x08048154, 0x000013, '.interp', 'chgrp'),
            (0x08048168, 0x000020, '.note.ABI-tag', 'chgrp'),
            (0x08048188, 0x00004c, '.gnu.hash', 'chgrp'),
            (0x080481d4, 0x0004e0, '.dynsym', 'chgrp'),
            (0x080486b4, 0x000343, '.dynstr', 'chgrp'),
            (0x080489f8, 0x00009c, '.gnu.version', 'chgrp'),
            (0x08048a94, 0x000080, '.gnu.version_r', 'chgrp'),
            (0x08048b14, 0x000038, '.rel.dyn', 'chgrp'),
            (0x08048b4c, 0x000218, '.rel.plt', 'chgrp'),
            (0x08048d64, 0x000023, '.init', 'chgrp'),
            (0x08048d90, 0x000440, '.plt', 'chgrp'),
            (0x080491d0, 0x000008, '.plt.got', 'chgrp'),
            (0x080491e0, 0x00745b, '.text', 'chgrp'),
            (0x0805063c, 0x000014, '.fini', 'chgrp'),
            (0x08050650, 0x001be6, '.rodata', 'chgrp'),
            (0x08052238, 0x00006c, '.eh_frame_hdr', 'chgrp'),
            (0x080522a4, 0x000284, '.eh_frame', 'chgrp'),
            (0x08053f0c, 0x000004, '.init_array', 'chgrp'),
            (0x08053f10, 0x000004, '.fini_array', 'chgrp'),
            (0x08053f14, 0x0000e8, '.dynamic', 'chgrp'),
            (0x08053ffc, 0x000004, '.got', 'chgrp'),
            (0x08054000, 0x000118, '.got.plt', 'chgrp'),
            (0x08054118, 0x000050, '.data', 'chgrp'),
            (0x08054170, 0x000184, '.bss', 'chgrp'),
            (0x08048000, 0x00a528, 'PHDR0', 'chgrp'),
            (0x08053f0c, 0x0003e8, 'PHDR1', 'chgrp')
        ])
        self.assertEqual(segments, set(vw.getSegments()))

    def test_chgrp_exports(self):
        '''
        Test that some fo the exports/names made by the elf parser
        aren't accidentally made into functions.
        '''
        vw = self.chgrp_vw
        exports = [
            # .rodata
            (0x08050650, 4, v_const.LOC_NUMBER, 'chgrp._fp_hw'),  # 08050650  uint32_t _fp_hw = 0x3
            (0x08050654, 4, v_const.LOC_NUMBER, 'chgrp._IO_stdin_used'),  # 08050654  uint32_t _IO_stdin_used = 0x20001

            # long options is really an array of pointers, but that isn't obvi
            # (0x08050748, 4, v_const.LOC_POINTER, 'chgrp.long_options'),
            #(0x08050850, 0x27, v_const.LOC_STRING, None),

            # these are zero-initialized global pointers, like:
            #
            #     uint32_t* const __progname = 0x0
            #
            # however, we can't differentiate this from a zero-initialized variable,
            # especially without data flow analysis, 
            # so we assume (incorrectly) that these are numbers rather than pointers.
            # (0x8054170, 4, v_const.LOC_POINTER, 'chgrp.program_invocation_short_name@@GLIBC_2.0'),
            # (0x8054178, 4, v_const.LOC_POINTER, 'chgrp.stderr@@GLIBC_2.0'),
            # (0x8054180, 4, v_const.LOC_POINTER, 'chgrp.program_invocation_name@@GLIBC_2.0'),
            # (0x8054190, 4, v_const.LOC_POINTER, 'chgrp.optind@@GLIBC_2.0'),
            # (0x8054194, 4, v_const.LOC_POINTER, 'chgrp.stdout@@GLIBC_2.0'),
            # (0x8054198, 4, v_const.LOC_POINTER, 'chgrp.optarg@@GLIBC_2.0'),
            #
            # this, on the other hand, is a zero-initialized global variable,
            # which we can correctly identify.
            (0x805419c, 1, v_const.LOC_NUMBER, 'chgrp.completed.7282'),

            # .data pointers/numbers
            (0x805411c, 4, v_const.LOC_NUMBER, 'chgrp.__dso_handle'),  # 0805411c  int32_t __dso_handle = 0x0
            (0x8054120, 4, v_const.LOC_POINTER, 'chgrp.Version'),
            (0x8054124, 4, v_const.LOC_NUMBER, 'chgrp.exit_failure'),  # 08054124  uint32_t exit_failure = 0x1
            (0x8054128, 4, v_const.LOC_POINTER, 'chgrp.slotvec'),
            (0x805412c, 4, v_const.LOC_NUMBER, 'chgrp.nslots'),  # 0805412c  uint32_t nslots = 0x1
            (0x8054130, 8, v_const.LOC_NUMBER, 'chgrp.slotvec0'),  # this one kinda pulls double duty as both 4 and 8....
            # Another horrible little saved space of bytes for....well technically a struct, not that we
            # can easily detect that
            # (0x8054138, 4, v_const.LOC_POINTER, 'chgrp.quote_quoting_options'),
        ]

        for va, size, ltyp, name in exports:
            loc = vw.getLocation(va)
            self.assertIsNotNone(loc)
            self.assertEqual(loc[v_const.L_VA], va)
            self.assertEqual(loc[v_const.L_SIZE], size)
            self.assertEqual(loc[v_const.L_LTYPE], ltyp)
            self.assertEqual(vw.getName(loc[v_const.L_VA]), name)

    def test_libfunc_meta_equality(self):
        '''
        both vdir and chgrp have a bunch of library functions in common, and while the addresses
        may be off, other information like # of blocks, # of xrefs from each block, etc. are the
        same
        '''
        vdir_fva = 0x8055bb0
        chgp_fva = 0x804ab30

        vmeta = self.vdir_vw.getFunctionMetaDict(vdir_fva)
        cmeta = self.chgrp_vw.getFunctionMetaDict(chgp_fva)

        self.assertEqual(vmeta['InstructionCount'], cmeta['InstructionCount'])
        self.assertEqual(vmeta['InstructionCount'], 868)

        self.assertEqual(vmeta['BlockCount'], cmeta['BlockCount'])
        self.assertEqual(vmeta['BlockCount'], 261)

        self.assertEqual(vmeta['Size'], cmeta['Size'])
        self.assertEqual(vmeta['Size'], 3154)  # or 877?

        self.assertEqual(vmeta['MnemDist'], cmeta['MnemDist'])

        self.assertEqual(vmeta['Recursive'], cmeta['Recursive'])
        self.assertTrue(vmeta['Recursive'])

    def test_callargs(self):
        answers = [
            (0x804f7f0, 0x8052560, 'cdecl', 3, 'hash_insert_if_absent'),
            (0x804aad0, 0x8055b50, 'cdecl', 5, 'quotearg_buffer'),
            # FIXME: and the problem child continue to suck.
            (0x804ab30, 0x8055bb0, 'cdecl', 7, 'quotearg_buffer_restyled'),
            (0x804b7c0, 0x8056840, 'cdecl', 3, 'quotearg_alloc'),
            (0x804b7e0, 0x8056860, 'cdecl', 4, 'quotearg_alloc_mem'),
            (0x804bc10, 0x8056c90, 'cdecl', 2, 'quotearg_style'),
            (0x804bbd0, 0x8056c50, 'cdecl', 4, 'quotearg_n_style_mem'),
            (0x804bce0, 0x8056d60, 'cdecl', 2, 'quotearg_char'),
            (0x804bc50, 0x8056cd0, 'cdecl', 3, 'quotearg_char_mem'),
            (0x804bae0, 0x8056b60, 'cdecl', 1, 'quotearg'),
            (0x804b930, 0x80569b0, 'cdecl', 2, 'quotearg_n'),
            (0x804bac0, 0x8056b40, 'cdecl', 3, 'quotearg_n_mem'),
            (0x804bd80, 0x8056e00, 'cdecl', 4, 'quotearg_n_custom'),
            (0x804bda0, 0x8056e20, 'cdecl', 5, 'quotearg_n_custom_mem'),
            (0x804be60, 0x8056ee0, 'cdecl', 4, 'quotearg_custom_mem'),
            (0x804be40, 0x8056ec0, 'cdecl', 3, 'quotearg_custom'),
            (0x804bc30, 0x8056cb0, 'cdecl', 3, 'quotearg_style_mem'),
            (0x804bb20, 0x8056ba0, 'cdecl', 3, 'quotearg_n_style'),
            (0x804bb00, 0x8056b80, 'cdecl', 2, 'quotearg_mem'),
            (0x804bd00, 0x8056d80, 'cdecl', 1, 'quotearg_colon'),
            (0x804bd20, 0x8056da0, 'cdecl', 2, 'quotearg_colon_mem'),
            (0x804bd40, 0x8056dc0, 'cdecl', 3, 'quotearg_n_style_colon'),
            (0x804b950, 0x80569d0, 'msfastcall_caller', 4, 'quotearg_n_options'),
            (0x804bee0, 0x8056f60, 'cdecl', 1, 'quote'),
            (0x804bec0, 0x8056f40, 'cdecl', 2, 'quote_n'),
            (0x804be80, 0x8056f00, 'cdecl', 3, 'quote_n_mem'),
            (0x804a7c0, 0x80511d0, 'cdecl', 0, 'close_stdout'),
            (0x804a920, 0x80559a0, 'cdecl', 1, 'set_program_name')
        ]
        for cfva, vfva, cconv, arglen, funcname in answers:
            capi = self.chgrp_vw.getFunctionMeta(cfva, 'api')
            vapi = self.vdir_vw.getFunctionMeta(vfva, 'api')
            self.assertIsNotNone(capi)
            self.assertIsNotNone(vapi)

            self.assertEqual(capi[2], cconv, f'{hex(cfva)}/{hex(vfva)} wanted cconv of {cconv}, got {capi[2]}')
            self.assertEqual(len(capi[4]), arglen)
            self.assertEqual(capi[2], vapi[2])
            self.assertEqual(capi[4], vapi[4])

            cname = self.chgrp_vw.getName(cfva)
            self.assertIsNotNone(cname)
            cname = cname.split('.')[-1]

            vname = self.vdir_vw.getName(vfva)
            self.assertIsNotNone(vname)
            vname = vname.split('.')[-1]

            self.assertEqual(cname, funcname)
            self.assertEqual(vname, cname)

        chgrp_spec = [
            # chgrp specific that I should test
            (0x8049c70, 'msfastcall_caller', 7, 'change_file_owner'),
            (0x80499f0, 'thiscall_caller', 1, 'parse_group'),
            (0x8049b60, 'cdecl', 7, 'chown_files'),
            (0x804a5a0, 'msfastcall_caller', 6, 'describe_change')
        ]

        vw = self.chgrp_vw
        for fva, cconv, arglen, funcname in chgrp_spec:
            self.assertEqual(fva, vw.getFunction(fva))
            api = vw.getFunctionMeta(fva, 'api')
            self.assertEqual(len(api[4]), arglen)
            self.assertEqual(api[2], cconv)

            name = vw.getName(fva)
            self.assertIsNotNone(name)
            name = name.split('.')[-1]
            self.assertEqual(name, funcname)

    def test_non_codeblock(self):
        '''
        So these VAs used to be recognized as codeblocks, which is not correct.
        <va> should be actually be a VA in the middle of a string
        <strtbl> should be a table of *string* pointers, not code block pointers
        '''
        vw = self.vdir_vw
        badva = 0x0805b6f2
        loctup = vw.getLocation(badva, range=False)
        self.assertEqual((134592163, 86, 2, [(134592242, 7)]), loctup)

        # this should directly reflect what's in src/ls.c for color_indicator
        strtbl = 0x805e75c
        loctup = vw.getLocation(strtbl)
        for idx, addr in enumerate(range(strtbl, strtbl + 40, 4)):
            self.assertEqual(vw.getLocation(addr), (addr, 4, 4, None))

    def test_consecutive_jump_table_diff_func(self):
        jumptabl = [
            0x8059718,
            0x8059b68,
            0x8059b78,
            0x8059b90,
            0x8059ba4,
            0x8059bb8,
            0x8059d9c,
            0x8059e30,
            0x8059fac
        ]

        # list of tuples of (xref addr, func addr, number of xrefs from xref addr)
        ans = [
            (0x804a468, 0x804a210, 62),
            (0x804ad21, 0x804a210, 5),
            (0x804b00a, 0x804af40, 7),  # 0x8059b78
            (0x804beee, 0x804bee0, 6),
            (0x804d1c9, 0x804d1a0, 6),
            (0x804d28f, 0x804d1a0, 15),  # 0x8059bb8
            (0x804d1e7, 0x804d1a0, 6),
            (0x804d95b, 0x804d820, 3),
            (0x804fd01, 0x804fc70, 9)  # 0x8059fac
        ]

        for i, tablva in enumerate(jumptabl):
            refva = self.vdir_vw.getXrefsTo(tablva)
            self.assertEqual(len(refva), 1)
            refva = refva[0]
            func = self.vdir_vw.getFunction(refva[0])
            refs = self.vdir_vw.getXrefsFrom(refva[0])
            test = ans[i]
            self.assertEqual(refva[0], test[0])
            self.assertEqual(func, test[1])
            self.assertEqual(len(refs), test[2])

    def test_main(self):
        vw = self.chgrp_vw
        self.assertIsNotNone(vw.parsedbin)
        self.assertTrue(vw.isFunction(0x8049650))
        self.assertTrue(vw.getFunction(0x0804a9a0), 0x0804a920)

    def test_viv_bigend(self):
        fd = io.BytesIO(b'ABCDEFG')

        vw = vivisect.VivWorkspace()
        vw.config.viv.parsers.blob.arch = 'arm'
        vw.config.viv.parsers.blob.bigend = True
        vw.config.viv.parsers.blob.baseaddr = 0x22220000

        vw.loadFromFd(fd, fmtname='blob')

        self.assertEqual(vw.castPointer(0x22220000), 0x41424344)
        self.assertEqual(vw.parseNumber(0x22220000, 2), 0x4142)

    def test_substrings(self):
        vw = self.firefox_vw
        loc = vw.getLocation(5369027475)
        # real boy test
        self.assertEqual((5369027475, 121, 2, [(5369027482, 114)]), loc)
        rep = vw.readMemory(loc[0], loc[1]).decode('utf-8')
        self.assertEqual(rep, 'https://crash-reports.mozilla.com/submit?id={ec8030f7-c20a-464f-9b0e-13a3a9e97384}&version=78.0.2&buildid=20200708170202\x00')

        subloc = vw.getLocation(5369027585, range=True)
        self.assertEqual((5369027482, 114, 2, []), subloc)

        toploc = vw.getLocation(5369027585, range=False)
        self.assertEqual(toploc, loc)

        # make up some substrings
        base = 5369027475
        s = 'https://crash-reports.mozilla.com/submit?id={ec8030f7-c20a-464f-9b0e-13a3a9e97384}&version=78.0.2&buildid=20200708170202\x00'
        vw.delLocation(base)
        for i in range(1, len(s)):
            vw.makeString(base + len(s) - i)
        vw.makeString(base)
        for i in range(1, len(s)):
            loc = vw.getLocation(base + i, range=True)
            self.assertEqual(loc[0], base + i)
            self.assertEqual(loc[1], len(s) - i)
            self.assertEqual(loc[2], 2)
            self.assertEqual(loc[3], [])
        loc = vw.getLocation(base)
        self.assertTrue(len(loc[v_const.L_TINFO]), 15)
        vw.delLocation(base)
        # be a good citizen and clean up
        vw.makeString(base)

    def test_more_substrings(self):
        vw = self.firefox_vw
        va = 5369027475
        vw.delLocation(va)
        # assert it got deleted
        self.assertIsNone(vw.getLocation(va))

        # little string first
        vw.makeString(va + 7)
        loc = vw.getLocation(va + 7)
        self.assertEqual(loc, (va + 7, 114, 2, []))

        # make the outer string
        wat = vw.makeString(va)
        newloc = vw.getLocation(va)
        self.assertEqual(newloc, (va, 121, 2, [(va+7, 114)]))
        self.assertEqual(wat, newloc)

    def test_make_noname(self):
        vw = self.vdir_vw
        name = 'TheBinaryAnalysisPlantsCrave'
        va = 0x08058691
        vw.makeName(va, name)
        self.assertEqual(vw.getName(va), name)

        vw.makeName(va, None)
        self.assertIsNone(vw.getName(va))
        self.assertEqual(vw.getName(va, smart=True), 'vdir.rpl_mbrtowc+0x31')

        fva = 0x08058660
        oldname = vw.getName(fva)
        self.assertEqual(oldname, 'vdir.rpl_mbrtowc')
        vw.makeName(fva, None)
        noname = 'sub_0%x' % fva
        self.assertEqual(vw.getName(fva), None)
        self.assertEqual(vw.getName(fva, smart=True), noname)
        # set it back just in case
        vw.makeName(fva, oldname)

    def test_graphutil_longpath(self):
        '''
        order matters
        '''
        vw = self.firefox_vw
        g = v_t_graphutil.buildFunctionGraph(vw, 0x1400037c0)
        longpath = []
        longpath = [0x1400037c0, 0x14000382d, 0x1400038a4, 0x140003964, 0x140003994, 0x1400039cc, 0x1400039f6, 0x140003a29, 0x140003a59, 0x140003a83, 0x140003ab3, 0x140003b3b, 0x140003b3e, 0x140003ccd, 0x140003c3c, 0x140003c3f, 0x140003c4c, 0x140003c52, 0x140003c5f, 0x140003c65, 0x140003c72, 0x140003c78, 0x140003c85, 0x140003c8b, 0x140003c98, 0x140003c9e, 0x140003cab, 0x140003cb1, 0x140003cc2, 0x1400038fd, 0x14000390a, 0x140003910, 0x14000392b, 0x140003938, 0x14000393e]
        path = next(v_t_graphutil.getLongPath(g))
        path = [k[0] for k in path]
        self.assertEqual(path, longpath)

    def test_graphutil_getcodepaths(self):
        '''
        In this function, order doesn't matter
        '''
        vw = self.firefox_vw
        g = v_t_graphutil.buildFunctionGraph(vw, 0x140010e60)
        paths = [
            set([0x140010e60, 0x140010e6e, 0x140010e7a, 0x140010e7e, 0x140010e9a, 0x140010ea0]),
            set([0x140010e60, 0x140010e6e, 0x140010e7a, 0x140010e7e, 0x140010e9a, 0x140010ea0, 0x140010f02, 0x140010f13]),
            set([0x140010e60, 0x140010e6e, 0x140010e7a, 0x140010e7e, 0x140010f02, 0x140010f18]),
            set([0x140010e60, 0x140010e6e, 0x140010e7e, 0x140010e9a, 0x140010ea0, 0x140010ef2]),
            set([0x140010e60, 0x140010e6e, 0x140010e7e, 0x140010e9a, 0x140010ea0, 0x140010ef2, 0x140010f02, 0x140010f13]),
            set([0x140010e60, 0x140010e6e, 0x140010e7e, 0x140010ef2, 0x140010f02, 0x140010f18]),
            set([0x140010e60, 0x140010ea0]),
        ]

        pathcount = 0
        genr = v_t_graphutil.getCodePaths(g, loopcnt=0, maxpath=None)
        for path in genr:
            p = set([k[0] for k in path])
            self.assertIn(p, paths)
            pathcount += 1

        self.assertEqual(7, pathcount)

        g = v_t_graphutil.buildFunctionGraph(vw, vw.getFunction(0x1400110a0))
        thruCnt = glen(v_t_graphutil.getCodePathsThru(g, 0x1400110a0))
        self.assertEqual(21, thruCnt)
        thruCnt = glen(v_t_graphutil.getCodePathsThru(g, 0x1400110a0, maxpath=2))
        self.assertEqual(2, thruCnt)

        g = v_t_graphutil.buildFunctionGraph(vw, vw.getFunction(0x14001ead0))
        toCnt = glen(v_t_graphutil.getCodePathsTo(g, 0x14001ec2a))
        self.assertEqual(2, toCnt)
        toCnt = glen(v_t_graphutil.getCodePathsTo(g, 0x14001ec2a, maxpath=99))
        self.assertEqual(2, toCnt)


        g = v_t_graphutil.buildFunctionGraph(vw, vw.getFunction(0x1400019ab))
        fromCnt = glen(v_t_graphutil.getCodePathsFrom(g, 0x1400019ab))
        self.assertEqual(2, fromCnt)
        fromCnt = glen(v_t_graphutil.getCodePathsFrom(g, 0x1400019ab, maxpath=1))
        self.assertEqual(1, fromCnt)

    def test_graphutil_getopsfrompath(self):
        vw = self.firefox_vw
        g = v_t_graphutil.buildFunctionGraph(vw, 0x140048b84)
        path = next(v_t_graphutil.getLongPath(g))

        ops = [
            'push rbx',
            'push rsi',
            'push rdi',
            'sub rsp,64',
            'mov rbx,rcx',
            'call qword [rip + 36451]',
            'mov rsi,qword [rbx + 248]',
            'xor edi,edi',
            'xor r8d,r8d',
            'lea rdx,qword [rsp + 96]',
            'mov rcx,rsi',
            'call qword [rip + 36505]',
            'test rax,rax',
            'jz 0x140048bed',
            'and qword [rsp + 56],0',
            'lea rcx,qword [rsp + 104]',
            'mov rdx,qword [rsp + 96]',
            'mov r9,rax',
            'mov qword [rsp + 48],rcx',
            'mov r8,rsi',
            'lea rcx,qword [rsp + 112]',
            'mov qword [rsp + 40],rcx',
            'xor ecx,ecx',
            'mov qword [rsp + 32],rbx',
            'call qword [rip + 36514]',
            'inc edi', 'cmp edi,2',
            'jl 0x140048b9e',
            'add rsp,64',
            'pop rdi',
            'pop rsi',
            'pop rbx',
            'ret '
        ]
        self.assertEqual(ops, [str(op) for op in v_t_graphutil.getOpsFromPath(vw, g, path)])

    def test_graphutil_coverage(self):
        # FIXME: So fun anecdote for later, originally I wanted to use fva 0x804af40 (parse_ls_colors)
        # out of vdir for this test, but unfortunately, we detect the codeblock of that fva
        # as 0x804af09, which crosses function boundaries into the function decode_switches.
        # Reason being is that at VA 0x804af31, there's a call to error() with value 2 as the first
        # parameter, which according to the man page for error means it should quit out. We don't grab
        # that at codeflow time (because such things would require an emulator with knowledge of calling
        # conventions)
        # But that raises the question if makeFunction should split the codeblock
        # or if we ignore that and just let the CodeBlockGraph stuff do it all for us,
        # or if we should allow codeflow to carry an emulator with it.
        vw = self.vdir_vw
        fvas = [
            0x804c030,
            0x804ce40,
            0x804cec0,
            0x804d1a0,
        ]
        for fva in fvas:
            g = v_t_graphutil.buildFunctionGraph(vw, fva)  # print_dir
            hits = set()
            for path in v_t_graphutil.getCoveragePaths(g):
                for nid, edge in path:
                    hits.add(nid)

            self.assertEqual(len(hits), len(vw.getFunctionBlocks(fva)))
            for nid in hits:
                cb = vw.getCodeBlock(nid)
                self.assertEqual(nid, cb[0])
                self.assertEqual(fva, cb[2])

    def test_firefox_segments(self):
        vw = self.firefox_vw
        ans = {
            'PE_Header': (0x140000000, 0x1000, e_const.MM_READ),
            '.text': (0x140001000, 0x49000, e_const.MM_READ | e_const.MM_EXEC),
            '.rdata': (0x14004a000, 0xc000, e_const.MM_READ),
            '.data': (0x140056000, 0x2a00, e_const.MM_READ | e_const.MM_WRITE),
            '.pdata': (0x140059000, 0x3000, e_const.MM_READ),
            '.00cfg': (0x14005c000, 0x200, e_const.MM_READ),
            '.freestd': (0x14005d000, 0x200, e_const.MM_READ),
            '.tls': (0x14005e000, 0x200, e_const.MM_READ | e_const.MM_WRITE),
            '.reloc': (0x140092000, 0x400, e_const.MM_READ),
        }
        for sva, ssize, sname, sfname in vw.getSegments():
            self.assertEqual(ans[sname][0], sva)
            self.assertEqual(ans[sname][1], ssize)
            self.assertEqual(sfname, 'firefox')

            mva, msize, flags, mfname = vw.getMemoryMap(sva)
            self.assertEqual(mva, sva)
            self.assertEqual(msize, ssize)
            self.assertEqual(flags, ans[sname][2])
            self.assertEqual(mfname, sfname)

    def test_opcache(self):
        vw = self.firefox_vw
        self.assertGreater(len(vw._op_cache), 0)
        vw.clearOpcache()
        self.assertEqual(len(vw._op_cache), 0)

        op = vw.parseOpcode(0x140010ef2, skipcache=True)
        self.assertEqual(str(op), 'mov rax,qword [rsi + 56]')
        self.assertEqual(len(vw._op_cache), 0)

        op = vw.parseOpcode(0x140010ef2)
        self.assertEqual(str(op), 'mov rax,qword [rsi + 56]')
        self.assertEqual(len(vw._op_cache), 1)

    def test_string_without_termination(self):
        vw = self.firefox_vw
        vw.addMemoryMap(0x2000, 7, 'test', b'this is a string that never terminates.')
        vw.makeString(0x2000)
        self.assertEqual(vw.readMemString(0x2000), b'this is a string that never terminates.')

    def test_function_thunks(self):
        vw = self.firefox_vw

        thunks = set([fva for fva in vw.getFunctions() if vw.isFunctionThunk(fva)])
        impthunk = [0x140049920, 0x1400498a0, 0x140049ba0, 0x140049b10, 0x140049bb0, 0x140049c30, 0x140049b30,
                    0x140049ab0, 0x1400498b0, 0x140049830, 0x140049930, 0x140049a30, 0x1400497b0, 0x140049730,
                    0x140049bc0, 0x140049940, 0x1400498c0, 0x140049ac0, 0x140049c40, 0x140049a40, 0x1400497c0,
                    0x140049840, 0x14001d2c0, 0x140049740, 0x140049ad0, 0x140049850, 0x14001d050, 0x140049bd0,
                    0x140049950, 0x1400497d0, 0x1400498d0, 0x140049750, 0x140049c50, 0x140049b50, 0x140049ae0,
                    0x140049be0, 0x1400498e0, 0x140049860, 0x14000dde0, 0x140049b60, 0x1400497e0, 0x140049760,
                    0x140015c60, 0x140049af0, 0x1400499f0, 0x1400498f0, 0x1400496f0, 0x140049870, 0x1400497f0,
                    0x140049770, 0x140049bf0, 0x140049b80, 0x140049780, 0x140049a00, 0x140049900, 0x140049700,
                    0x140049800, 0x140049880, 0x140049c00, 0x140049b00, 0x140049a10, 0x140049790, 0x140049810,
                    0x140049a90, 0x140049710, 0x14001ef10, 0x140049890, 0x140049910, 0x140049b90, 0x140049c10,
                    0x140049b40, 0x140049aa0, 0x1400497a0, 0x140049720, 0x140049820, 0x140049a20, 0x140048a10,
                    0x140048a80]

        self.assertEqual(thunks, set(impthunk))

    def test_noret(self):

        # test analysis stops after a few known NoRets
        # arm/sh
        self.assertIsNone(self.sh_vw.getLocation(0x0000ddaa))
        self.assertIsNone(self.sh_vw.getLocation(0x000288ce))

        # linux/i386/vdir.llvm
        self.assertIsNone(self.vdir_vw.getLocation(0x080589ba))
        self.assertIsNone(self.vdir_vw.getLocation(0x0805875f))
        self.assertIsNone(self.vdir_vw.getLocation(0x08057b12))
        self.assertIsNone(self.vdir_vw.getLocation(0x0804adc9))

        # linux/i386/chgrp.llvm
        self.assertIsNone(self.chgrp_vw.getLocation(0x0804cb15))
        self.assertIsNone(self.chgrp_vw.getLocation(0x0804c814))
        self.assertIsNone(self.chgrp_vw.getLocation(0x0804c66e))
        self.assertIsNone(self.chgrp_vw.getLocation(0x0804b7b9))
        self.assertIsNone(self.chgrp_vw.getLocation(0x0804d0ae))
        self.assertIsNone(self.chgrp_vw.getLocation(0x080494d6))
        self.assertIsNone(self.chgrp_vw.getLocation(0x080499e5))

        # windows/amd64/firefox
        self.assertIsNone(self.firefox_vw.getLocation(0x14000dd2c))
        self.assertIsNone(self.firefox_vw.getLocation(0x1400175bb))
        self.assertIsNone(self.firefox_vw.getLocation(0x140048e8f))
        self.assertIsNone(self.firefox_vw.getLocation(0x14001bd26))
        self.assertIsNone(self.firefox_vw.getLocation(0x1400163a7))

    def test_del_reloc(self):
        with self.snap(self.chown_vw) as vw:
            base = vw.getFileMeta('chown', 'imagebase')

            va = 0x20f950
            rva = 0x20f950 + base

            reloc = [rdat for rdat in vw.getRelocations() if rdat[1] == va]
            self.len(reloc, 1)
            rtyp = vw.getRelocation(rva)
            self.assertEqual(2, rtyp)

            old = len(vw.getRelocations())
            self.assertEqual(2, vw.delRelocation(rva, full=True))

            self.none(vw.getLocation(rva))
            self.len(vw.getXrefsFrom(rva), 0)
            self.len(vw.getXrefsTo(0x20028a0), 1)
            self.eq(2, self.chown_vw.getRelocation(rva))
            self.eq(1, old - len(vw.getRelocations()))

            self.none(vw.delRelocation(0xabad1dea))

            self.nn(self.chown_vw.getLocation(rva))
            self.len(self.chown_vw.getXrefsFrom(rva), 1)
            self.len(self.chown_vw.getXrefsTo(0x20028a0), 2)

    def test_string_naming(self):
        # string naming should not chop off the last character
        self.assertEqual(self.chown_vw.getName(0x0200c050), 'str_ownership of %s _0200c050')

    def test_relocatable_elf_simple(self):
        kmod = helpers.getTestWorkspace('linux', 'amd64', 'hellokernel.ko')
        self.eq(set(kmod.getFunctions()), set([0x7c, 0x95]))

    def test_guid(self):
        vw = vivisect.VivWorkspace()

        # raw workspace should not have a GUID
        self.assertEqual(vw.getMeta('GUID'), None)

        chownpath = helpers.getTestPath('linux', 'amd64', 'chown')
        vw.loadFromFile(chownpath)

        # workspace gets a GUID after one of the "vw.loadFrom*" functions is called
        newguid = vw.getMeta('GUID')
        self.assertNotEqual(newguid, None)

        # since it's assigned, the result from "vw.getVivGuid()" should be the same
        self.assertEqual(newguid, vw.getVivGuid())

    def test_write_fail(self):
        with self.snap(self.chown_vw) as vw:
            base = vw.getFileMeta('chown', 'imagebase')

            oldmem = vw.readMemory(base, 10)

            with self.assertRaises(e_exc.SegmentationViolation):
                vw.writeMemory(base, b"testing...")

            self.assertEqual(oldmem, vw.readMemory(base, 10))

            with vw.getAdminRights():
                vw.writeMemory(base, b"testing...")

            self.assertEqual(b'testing...', vw.readMemory(base, 10))

            with self.assertRaises(e_exc.SegmentationViolation):
                vw.writeMemory(base, b"FOOBARBAZ.")

            self.assertEqual(b'testing...', vw.readMemory(base, 10))

            with vw.getAdminRights():
                vw.writeMemory(base, oldmem)

