From df536f2f5f3c70dc19c0d2417fbd31342ee39c64 Mon Sep 17 00:00:00 2001 From: Karl Vogel Date: Sun, 26 Oct 2014 12:09:15 +0100 Subject: [PATCH] Basic MIPS support --- elftools/dwarf/callframe.py | 10 +- elftools/dwarf/descriptions.py | 12 ++- elftools/elf/constants.py | 19 ++++ elftools/elf/descriptions.py | 59 +++++++++- elftools/elf/elffile.py | 2 + elftools/elf/enums.py | 102 +++++++++++++++++- elftools/elf/relocation.py | 16 ++- scripts/readelf.py | 28 +++-- test/test_mips_support.py | 30 ++++++ .../simple_mips_gcc.o.elf | Bin 0 -> 2768 bytes .../simple_gcc.elf.mips | Bin 0 -> 2768 bytes 11 files changed, 253 insertions(+), 25 deletions(-) create mode 100644 test/test_mips_support.py create mode 100644 test/testfiles_for_readelf/simple_mips_gcc.o.elf create mode 100644 test/testfiles_for_unittests/simple_gcc.elf.mips diff --git a/elftools/dwarf/callframe.py b/elftools/dwarf/callframe.py index 264adb8..22f6a71 100644 --- a/elftools/dwarf/callframe.py +++ b/elftools/dwarf/callframe.py @@ -263,7 +263,7 @@ class CFIEntry(object): cie = self.cie cie_decoded_table = cie.get_decoded() last_line_in_CIE = copy.copy(cie_decoded_table.table[-1]) - cur_line = last_line_in_CIE + cur_line = copy.copy(last_line_in_CIE) cur_line['pc'] = self['initial_location'] reg_order = copy.copy(cie_decoded_table.reg_order) @@ -346,10 +346,10 @@ class CFIEntry(object): dwarf_assert( isinstance(self, FDE), '%s instruction must be in a FDE' % name) - dwarf_assert( - instr.args[0] in last_line_in_CIE, - '%s: can not find register in CIE') - cur_line[instr.args[0]] = last_line_in_CIE[instr.args[0]] + if instr.args[0] in last_line_in_CIE: + cur_line[instr.args[0]] = last_line_in_CIE[instr.args[0]] + else: + cur_line.pop(instr.args[0], None) elif name == 'DW_CFA_remember_state': line_stack.append(cur_line) elif name == 'DW_CFA_restore_state': diff --git a/elftools/dwarf/descriptions.py b/elftools/dwarf/descriptions.py index ca00cd1..ac8f772 100644 --- a/elftools/dwarf/descriptions.py +++ b/elftools/dwarf/descriptions.py @@ -49,7 +49,11 @@ def describe_CFI_instructions(entry): 'Unexpected instruction "%s" for a CIE' % instr) def _full_reg_name(regnum): - return 'r%s (%s)' % (regnum, describe_reg_name(regnum)) + regname = describe_reg_name(regnum, _MACHINE_ARCH, False) + if regname: + return 'r%s (%s)' % (regnum, regname) + else: + return 'r%s' % regnum if isinstance(entry, CIE): cie = entry @@ -146,7 +150,7 @@ def describe_DWARF_expr(expr, structs): return '(' + dwarf_expr_dumper.get_str() + ')' -def describe_reg_name(regnum, machine_arch=None): +def describe_reg_name(regnum, machine_arch=None, default=True): """ Provide a textual description for a register name, given its serial number. The number is expected to be valid. """ @@ -157,8 +161,10 @@ def describe_reg_name(regnum, machine_arch=None): return _REG_NAMES_x86[regnum] elif machine_arch == 'x64': return _REG_NAMES_x64[regnum] + elif default: + return 'r%s' % regnum else: - return '' + return None #------------------------------------------------------------------------------- diff --git a/elftools/elf/constants.py b/elftools/elf/constants.py index b41f35a..df6e0af 100644 --- a/elftools/elf/constants.py +++ b/elftools/elf/constants.py @@ -26,6 +26,25 @@ class E_FLAGS(object): EF_ARM_ABI_FLOAT_SOFT=0x00000200 EF_ARM_ABI_FLOAT_HARD=0x00000400 + EF_MIPS_NOREORDER=1 + EF_MIPS_PIC=2 + EF_MIPS_CPIC=4 + EF_MIPS_XGOT=8 + EF_MIPS_64BIT_WHIRL=16 + EF_MIPS_ABI2=32 + EF_MIPS_ABI_ON32=64 + EF_MIPS_NAN2008=1024 + EF_MIPS_ARCH=0xf0000000 + EF_MIPS_ARCH_1=0x00000000 + EF_MIPS_ARCH_2=0x10000000 + EF_MIPS_ARCH_3=0x20000000 + EF_MIPS_ARCH_4=0x30000000 + EF_MIPS_ARCH_5=0x40000000 + EF_MIPS_ARCH_32=0x50000000 + EF_MIPS_ARCH_64=0x60000000 + EF_MIPS_ARCH_32R2=0x70000000 + EF_MIPS_ARCH_64R2=0x80000000 + class SHN_INDICES(object): """ Special section indices """ diff --git a/elftools/elf/descriptions.py b/elftools/elf/descriptions.py index a25e51b..f085b87 100644 --- a/elftools/elf/descriptions.py +++ b/elftools/elf/descriptions.py @@ -7,8 +7,9 @@ # This code is in the public domain #------------------------------------------------------------------------------- from .enums import ( - ENUM_D_TAG, ENUM_E_VERSION, ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, - ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64) + ENUM_D_TAG, ENUM_E_VERSION, ENUM_P_TYPE, ENUM_SH_TYPE, + ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, + ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64, ENUM_RELOC_TYPE_MIPS) from .constants import P_FLAGS, SH_FLAGS, SUNW_SYMINFO_FLAGS, VER_FLAGS from ..common.py3compat import iteritems @@ -38,7 +39,12 @@ def describe_e_version_numeric(x): return '0x%x' % ENUM_E_VERSION[x] def describe_p_type(x): - return _DESCR_P_TYPE.get(x, _unknown) + if x in _DESCR_P_TYPE: + return _DESCR_P_TYPE.get(x) + elif x >= ENUM_P_TYPE['PT_LOOS'] and x <= ENUM_P_TYPE['PT_HIOS']: + return 'LOOS+%lx' % (x - ENUM_P_TYPE['PT_LOOS']) + else: + return _unknown def describe_p_flags(x): s = '' @@ -47,7 +53,12 @@ def describe_p_flags(x): return s def describe_sh_type(x): - return _DESCR_SH_TYPE.get(x, _unknown) + if x in _DESCR_SH_TYPE: + return _DESCR_SH_TYPE.get(x) + elif x >= ENUM_SH_TYPE['SHT_LOOS'] and x < ENUM_SH_TYPE['SHT_GNU_versym']: + return 'loos+%lx' % (x - ENUM_SH_TYPE['SHT_LOOS']) + else: + return _unknown def describe_sh_flags(x): s = '' @@ -81,6 +92,8 @@ def describe_reloc_type(x, elffile): return _DESCR_RELOC_TYPE_ARM.get(x, _unknown) elif arch == 'AArch64': return _DESCR_RELOC_TYPE_AARCH64.get(x, _unknown) + elif arch == 'MIPS': + return _DESCR_RELOC_TYPE_MIPS.get(x, _unknown) else: return 'unrecognized: %-7x' % (x & 0xFFFFFFFF) @@ -242,6 +255,41 @@ _DESCR_SH_TYPE = dict( SHT_ARM_PREEMPTMAP='ARM_PREEMPTMAP', SHT_ARM_ATTRIBUTES='ARM_ATTRIBUTES', SHT_ARM_DEBUGOVERLAY='ARM_DEBUGOVERLAY', + SHT_MIPS_LIBLIST='MIPS_LIBLIST', + SHT_MIPS_DEBUG='MIPS_DEBUG', + SHT_MIPS_REGINFO='MIPS_REGINFO', + SHT_MIPS_PACKAGE='MIPS_PACKAGE', + SHT_MIPS_PACKSYM='MIPS_PACKSYM', + SHT_MIPS_RELD='MIPS_RELD', + SHT_MIPS_IFACE='MIPS_IFACE', + SHT_MIPS_CONTENT='MIPS_CONTENT', + SHT_MIPS_OPTIONS='MIPS_OPTIONS', + SHT_MIPS_SHDR='MIPS_SHDR', + SHT_MIPS_FDESC='MIPS_FDESC', + SHT_MIPS_EXTSYM='MIPS_EXTSYM', + SHT_MIPS_DENSE='MIPS_DENSE', + SHT_MIPS_PDESC='MIPS_PDESC', + SHT_MIPS_LOCSYM='MIPS_LOCSYM', + SHT_MIPS_AUXSYM='MIPS_AUXSYM', + SHT_MIPS_OPTSYM='MIPS_OPTSYM', + SHT_MIPS_LOCSTR='MIPS_LOCSTR', + SHT_MIPS_LINE='MIPS_LINE', + SHT_MIPS_RFDESC='MIPS_RFDESC', + SHT_MIPS_DELTASYM='MIPS_DELTASYM', + SHT_MIPS_DELTAINST='MIPS_DELTAINST', + SHT_MIPS_DELTACLASS='MIPS_DELTACLASS', + SHT_MIPS_DWARF='MIPS_DWARF', + SHT_MIPS_DELTADECL='MIPS_DELTADECL', + SHT_MIPS_SYMBOL_LIB='MIPS_SYMBOL_LIB', + SHT_MIPS_EVENTS='MIPS_EVENTS', + SHT_MIPS_TRANSLATE='MIPS_TRANSLATE', + SHT_MIPS_PIXIE='MIPS_PIXIE', + SHT_MIPS_XLATE='MIPS_XLATE', + SHT_MIPS_XLATE_DEBUG='MIPS_XLATE_DEBUG', + SHT_MIPS_WHIRL='MIPS_WHIRL', + SHT_MIPS_EH_REGION='MIPS_EH_REGION', + SHT_MIPS_XLATE_OLD='MIPS_XLATE_OLD', + SHT_MIPS_PDR_EXCEPTION='MIPS_PDR_EXCEPTION', ) _DESCR_SH_FLAGS = { @@ -350,5 +398,8 @@ _DESCR_RELOC_TYPE_ARM = dict( _DESCR_RELOC_TYPE_AARCH64 = dict( (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_AARCH64)) +_DESCR_RELOC_TYPE_MIPS = dict( + (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_MIPS)) + _DESCR_D_TAG = dict( (v, k) for k, v in iteritems(ENUM_D_TAG)) diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index 4132c1e..cbb1428 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py @@ -177,6 +177,8 @@ class ELFFile(object): return 'ARM' elif self['e_machine'] == 'EM_AARCH64': return 'AArch64' + elif self['e_machine'] == 'EM_MIPS': + return 'MIPS' else: return '' diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py index ea4dcf1..494827f 100644 --- a/elftools/elf/enums.py +++ b/elftools/elf/enums.py @@ -188,7 +188,7 @@ ENUM_SH_TYPE = dict( SHT_GNU_HASH=0x6ffffff6, SHT_GNU_verdef=0x6ffffffd, # also SHT_SUNW_verdef SHT_GNU_verneed=0x6ffffffe, # also SHT_SUNW_verneed - SHT_GNU_versym=0x6fffffff, # also SHT_SUNW_versym + SHT_GNU_versym=0x6fffffff, # also SHT_SUNW_versym, SHT_HIOS SHT_LOPROC=0x70000000, SHT_HIPROC=0x7fffffff, SHT_LOUSER=0x80000000, @@ -196,10 +196,45 @@ ENUM_SH_TYPE = dict( SHT_AMD64_UNWIND=0x70000001, SHT_SUNW_LDYNSYM=0x6ffffff3, SHT_SUNW_syminfo=0x6ffffffc, - SHT_ARM_EXIDX=0x70000001, - SHT_ARM_PREEMPTMAP=0x70000002, - SHT_ARM_ATTRIBUTES=0x70000003, - SHT_ARM_DEBUGOVERLAY=0x70000004, + SHT_ARM_EXIDX=0x70000001, # also SHT_MIPS_MSYM + SHT_ARM_PREEMPTMAP=0x70000002, # also SHT_MIPS_CONFLICT + SHT_ARM_ATTRIBUTES=0x70000003, # also SHT_MIPS_GPTAB + SHT_ARM_DEBUGOVERLAY=0x70000004, # also SHT_MIPS_UCODE + SHT_MIPS_LIBLIST=0x70000000, + SHT_MIPS_DEBUG=0x70000005, + SHT_MIPS_REGINFO=0x70000006, + SHT_MIPS_PACKAGE=0x70000007, + SHT_MIPS_PACKSYM=0x70000008, + SHT_MIPS_RELD=0x70000009, + SHT_MIPS_IFACE=0x7000000b, + SHT_MIPS_CONTENT=0x7000000c, + SHT_MIPS_OPTIONS=0x7000000d, + SHT_MIPS_SHDR=0x70000010, + SHT_MIPS_FDESC=0x70000011, + SHT_MIPS_EXTSYM=0x70000012, + SHT_MIPS_DENSE=0x70000013, + SHT_MIPS_PDESC=0x70000014, + SHT_MIPS_LOCSYM=0x70000015, + SHT_MIPS_AUXSYM=0x70000016, + SHT_MIPS_OPTSYM=0x70000017, + SHT_MIPS_LOCSTR=0x70000018, + SHT_MIPS_LINE=0x70000019, + SHT_MIPS_RFDESC=0x7000001a, + SHT_MIPS_DELTASYM=0x7000001b, + SHT_MIPS_DELTAINST=0x7000001c, + SHT_MIPS_DELTACLASS=0x7000001d, + SHT_MIPS_DWARF=0x7000001e, + SHT_MIPS_DELTADECL=0x7000001f, + SHT_MIPS_SYMBOL_LIB=0x70000020, + SHT_MIPS_EVENTS=0x70000021, + SHT_MIPS_TRANSLATE=0x70000022, + SHT_MIPS_PIXIE=0x70000023, + SHT_MIPS_XLATE=0x70000024, + SHT_MIPS_XLATE_DEBUG=0x70000025, + SHT_MIPS_WHIRL=0x70000026, + SHT_MIPS_EH_REGION=0x70000027, + SHT_MIPS_XLATE_OLD=0x70000028, + SHT_MIPS_PDR_EXCEPTION=0x70000029, _default_=Pass, ) @@ -214,6 +249,8 @@ ENUM_P_TYPE = dict( PT_SHLIB=5, PT_PHDR=6, PT_TLS=7, + PT_LOOS=0x60000000, + PT_HIOS=0x6fffffff, PT_LOPROC=0x70000000, PT_HIPROC=0x7fffffff, PT_GNU_EH_FRAME=0x6474e550, @@ -373,6 +410,61 @@ ENUM_D_TAG = dict( _default_=Pass, ) +ENUM_RELOC_TYPE_MIPS = dict( + R_MIPS_NONE=0, + R_MIPS_16=1, + R_MIPS_32=2, + R_MIPS_REL32=3, + R_MIPS_26=4, + R_MIPS_HI16=5, + R_MIPS_LO16=6, + R_MIPS_GPREL16=7, + R_MIPS_LITERAL=8, + R_MIPS_GOT16=9, + R_MIPS_PC16=10, + R_MIPS_CALL16=11, + R_MIPS_GPREL32=12, + R_MIPS_SHIFT5=16, + R_MIPS_SHIFT6=17, + R_MIPS_64=18, + R_MIPS_GOT_DISP=19, + R_MIPS_GOT_PAGE=20, + R_MIPS_GOT_OFST=21, + R_MIPS_GOT_HI16=22, + R_MIPS_GOT_LO16=23, + R_MIPS_SUB=24, + R_MIPS_INSERT_A=25, + R_MIPS_INSERT_B=26, + R_MIPS_DELETE=27, + R_MIPS_HIGHER=28, + R_MIPS_HIGHEST=29, + R_MIPS_CALL_HI16=30, + R_MIPS_CALL_LO16=31, + R_MIPS_SCN_DISP=32, + R_MIPS_REL16=33, + R_MIPS_ADD_IMMEDIATE=34, + R_MIPS_PJUMP=35, + R_MIPS_RELGOT=36, + R_MIPS_JALR=37, + R_MIPS_TLS_DTPMOD32=38, + R_MIPS_TLS_DTPREL32=39, + R_MIPS_TLS_DTPMOD64=40, + R_MIPS_TLS_DTPREL64=41, + R_MIPS_TLS_GD=42, + R_MIPS_TLS_LDM=43, + R_MIPS_TLS_DTPREL_HI16=44, + R_MIPS_TLS_DTPREL_LO16=45, + R_MIPS_TLS_GOTTPREL=46, + R_MIPS_TLS_TPREL32=47, + R_MIPS_TLS_TPREL64=48, + R_MIPS_TLS_TPREL_HI16=49, + R_MIPS_TLS_TPREL_LO16=50, + R_MIPS_GLOB_DAT=51, + R_MIPS_COPY=126, + R_MIPS_JUMP_SLOT=127, + _default_=Pass, +) + ENUM_RELOC_TYPE_i386 = dict( R_386_NONE=0, R_386_32=1, diff --git a/elftools/elf/relocation.py b/elftools/elf/relocation.py index 176f7c5..9d9701f 100644 --- a/elftools/elf/relocation.py +++ b/elftools/elf/relocation.py @@ -11,7 +11,7 @@ from collections import namedtuple from ..common.exceptions import ELFRelocationError from ..common.utils import elf_assert, struct_parse from .sections import Section -from .enums import ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64 +from .enums import ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS class Relocation(object): @@ -147,6 +147,11 @@ class RelocationHandler(object): raise ELFRelocationError( 'Unexpected REL relocation for x64: %s' % reloc) recipe = self._RELOCATION_RECIPES_X64.get(reloc_type, None) + elif self.elffile.get_machine_arch() == 'MIPS': + if reloc.is_RELA(): + raise ELFRelocationError( + 'Unexpected RELA relocation for MIPS: %s' % reloc) + recipe = self._RELOCATION_RECIPES_MIPS.get(reloc_type, None) if recipe is None: raise ELFRelocationError( @@ -210,6 +215,15 @@ class RelocationHandler(object): def _reloc_calc_sym_plus_addend_pcrel(value, sym_value, offset, addend=0): return sym_value + addend - offset + # https://dmz-portal.mips.com/wiki/MIPS_relocation_types + _RELOCATION_RECIPES_MIPS = { + ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE( + bytesize=4, has_addend=False, calc_func=_reloc_calc_identity), + ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE( + bytesize=4, has_addend=False, + calc_func=_reloc_calc_sym_plus_value), + } + _RELOCATION_RECIPES_X86 = { ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_identity), diff --git a/scripts/readelf.py b/scripts/readelf.py index 967fb0e..02508d2 100755 --- a/scripts/readelf.py +++ b/scripts/readelf.py @@ -127,6 +127,16 @@ class ReadElf(object): version = flags & E_FLAGS.EF_ARM_EABIMASK if version == E_FLAGS.EF_ARM_EABI_VER5: description += ", Version5 EABI" + elif self.elffile['e_machine'] == "EM_MIPS": + if flags & E_FLAGS.EF_MIPS_NOREORDER: + description += ", noreorder" + if flags & E_FLAGS.EF_MIPS_CPIC: + description += ", cpic" + if not (flags & E_FLAGS.EF_MIPS_ABI2) and not (flags & E_FLAGS.EF_MIPS_ABI_ON32): + description += ", o32" + if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_1: + description += ", mips1" + return description def display_program_headers(self, show_heading=True): @@ -1005,15 +1015,19 @@ class ReadElf(object): reg_order = sorted(ifilter( lambda r: r != ra_regnum, decoded_table.reg_order)) + if len(decoded_table.reg_order): + + # Headings for the registers + for regnum in reg_order: + self._emit('%-6s' % describe_reg_name(regnum)) + self._emitline('ra ') - # Headings for the registers - for regnum in reg_order: - self._emit('%-6s' % describe_reg_name(regnum)) - self._emitline('ra ') + # Now include ra_regnum in reg_order to print its values similarly + # to the other registers. + reg_order.append(ra_regnum) + else: + self._emitline() - # Now include ra_regnum in reg_order to print its values similarly - # to the other registers. - reg_order.append(ra_regnum) for line in decoded_table.table: self._emit(self._format_hex( line['pc'], fullhex=True, lead0x=False)) diff --git a/test/test_mips_support.py b/test/test_mips_support.py new file mode 100644 index 0000000..c0537d0 --- /dev/null +++ b/test/test_mips_support.py @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Karl Vogel (karl.vogel@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +try: + import unittest2 as unittest +except ImportError: + import unittest +import os + +from utils import setup_syspath; setup_syspath() +from elftools.elf.elffile import ELFFile + +class TestMIPSSupport(unittest.TestCase): + def test_hello(self): + with open(os.path.join('test', 'testfiles_for_unittests', + 'simple_gcc.elf.mips'), 'rb') as f: + elf = ELFFile(f) + self.assertEqual(elf.get_machine_arch(), 'MIPS') + + # Check some other properties of this ELF file derived from readelf + self.assertEqual(elf['e_entry'], 0x0) + self.assertEqual(elf.num_sections(), 25) + self.assertEqual(elf.num_segments(), 0) + +if __name__ == '__main__': + unittest.main() + diff --git a/test/testfiles_for_readelf/simple_mips_gcc.o.elf b/test/testfiles_for_readelf/simple_mips_gcc.o.elf new file mode 100644 index 0000000000000000000000000000000000000000..af20c820552d82c9a338b6b7264ca0bf1bc90fc0 GIT binary patch literal 2768 zcmb_eO>7%g5T4z&Q@2i1=Lbkc+HO#)1gO_0b)~lag;dZ~1cJm*41qpgVLV^PzIDurodHbHTwU9uFk><^O zGqc}&vpe4P#m5()&~%NuqEQNdEReWKlu1%SRLsyg-Px^$Gkv|Y)BcQ(v_GYp_J=gp zzDB0;?$49$o0M-s`(<(a29341XaeL@971kF-W$Y&M>U&naS4Y z>_du~N#<D4M%w^KyZ=>X)1#*Sz4kmC*CtAX{C@g-#HPrBZX9V7cP?%f4fmQ9@KL zl?unRL9O0&9V_rlRw)|fq&SMvucfa3MtVa3gcp5b2uADswJ){yq;BF2r=Nb_JY^QF z;9Rj#+P=aZ?3>scGSh0PlY-X!wb}4UWD+rZew*AU%wp6KQ?VwKo zy^q}Av_Yp&ojPgGEH$0RSwGAz)*80&nF#W6v$OdJ3Rb>g`i0pO$Bx|_wLp*Ij_$8E zR>eKolQ75%F4RN22pjs5T~VIzxK`+_hh&xQ&?c)G1Z4S6wbrP3?6_95?2}b5JH^$i zWfyBlXQQwPELVKH?!*Jpby3nT7JX+ep0$0uQFX))t`!xzwFU?xABQ5VhjI#Y+_&~U-Z6<7RDH@;6R0vl6 z>`uUs_W^ow4(@~Rf$C!(ew=RsI4&yp90>S*Cq?7BXB2`}`*JM4?>cY>%KLI$?d$aL zT|ur2RigH_5s33$0?tF3FAY_E=X>}%$eo2MQGBo8UZ=l+0#g;is=ABt2zGhE~s$(l>nKP*maHKgn zJj2Yv{W3q_lNjILpMYP4Dp7n2E8C4HsPl^ovISW;Cvl8l7%g5T4z&Q@2i1=Lbkc+HO#)1gO_0b)~lag;dZ~1cJm*41qpgVLV^PzIDurodHbHTwU9uFk><^O zGqc}&vpe4P#m5()&~%NuqEQNdEReWKlu1%SRLsyg-Px^$Gkv|Y)BcQ(v_GYp_J=gp zzDB0;?$49$o0M-s`(<(a29341XaeL@971kF-W$Y&M>U&naS4Y z>_du~N#<D4M%w^KyZ=>X)1#*Sz4kmC*CtAX{C@g-#HPrBZX9V7cP?%f4fmQ9@KL zl?unRL9O0&9V_rlRw)|fq&SMvucfa3MtVa3gcp5b2uADswJ){yq;BF2r=Nb_JY^QF z;9Rj#+P=aZ?3>scGSh0PlY-X!wb}4UWD+rZew*AU%wp6KQ?VwKo zy^q}Av_Yp&ojPgGEH$0RSwGAz)*80&nF#W6v$OdJ3Rb>g`i0pO$Bx|_wLp*Ij_$8E zR>eKolQ75%F4RN22pjs5T~VIzxK`+_hh&xQ&?c)G1Z4S6wbrP3?6_95?2}b5JH^$i zWfyBlXQQwPELVKH?!*Jpby3nT7JX+ep0$0uQFX))t`!xzwFU?xABQ5VhjI#Y+_&~U-Z6<7RDH@;6R0vl6 z>`uUs_W^ow4(@~Rf$C!(ew=RsI4&yp90>S*Cq?7BXB2`}`*JM4?>cY>%KLI$?d$aL zT|ur2RigH_5s33$0?tF3FAY_E=X>}%$eo2MQGBo8UZ=l+0#g;is=ABt2zGhE~s$(l>nKP*maHKgn zJj2Yv{W3q_lNjILpMYP4Dp7n2E8C4HsPl^ovISW;Cvl8l