@@ -291,6 +291,10 @@ class Unsupported(Exception): pass
291291# call, ret, an instruction touching c->st/c->top, end of block) only the modified
292292# slots still on the stack are written back and c->top is updated once.
293293import re as _re
294+ import re
295+ PLACEHOLDERS_XP = (0x5d , 0x6b , 0x71 , 0x4f , 0x63 , 0x3b )
296+ class XPatchError (Exception ):
297+ """a bad xpatch site: generation must stop (genall re-raises it)"""
294298_ST_RE = _re .compile (r'ST\((\d+)\)' )
295299_STA_RE = _re .compile (r'ST\((\d+)\)\s*=(?!=)' )
296300_BOUNDARY_RE = _re .compile (r'\b\w+\(c[,)]|\breturn\b|c->st|c->top|\bST\(|FPUSH|FPOP|fsw_get' )
@@ -354,7 +358,7 @@ class RawLifter:
354358 def __init__ (self , data , base = 0x00401000 , comments = True ):
355359 self .comments = comments ; self .trap = False ; self .traps = 0
356360 self .trap_kinds = collections .Counter (); self .hooks = set (); self .watch = False
357- self .cov = False
361+ self .cov = False ; self . xp = None
358362 self .secs = [('.text' , base , len (data ), bytes (data ))]
359363 self .TVA = base ; self .TSZ = len (data ); self .TDATA = bytes (data )
360364 self .md = Cs (CS_ARCH_X86 ,CS_MODE_32 ); self .md .detail = True
@@ -364,7 +368,7 @@ class Lifter:
364368 def __init__ (self , pe_path , comments = True , trap = False , hooks = None , watch = False ):
365369 self .comments = comments ; self .trap = trap ; self .traps = 0 ; self .trap_kinds = collections .Counter ()
366370 self .hooks = set (hooks or ()); self .watch = watch
367- self .cov = False
371+ self .cov = False ; self . xp = None ; self . xp_hits = set ()
368372 pe = pefile .PE (pe_path ); self .IB = pe .OPTIONAL_HEADER .ImageBase
369373 self .secs = []
370374 for s in pe .sections :
@@ -1599,6 +1603,65 @@ def qwords(k, half):
15991603
16001604 raise Unsupported (m )
16011605
1606+ # ---------- LOCAL PATCH: run-time switchable patches (tools/xpatch.py) ----------
1607+ def _xp_lines (self , s , data , lbl , targets ):
1608+ """patched bytes -> C lines; returns (lines, ends_with_transfer)"""
1609+ L = []; pos = s .a ; ends = False
1610+ saved = getattr (self , 'cur_fmask' , F_ALL )
1611+ for i in self .md .disasm (data , s .a ):
1612+ m = i .mnemonic ; pos = i .address + i .size
1613+ if m == 'jmp' and i .operands [0 ].type == X86_OP_IMM :
1614+ t = i .operands [0 ].imm
1615+ if t not in lbl : raise XPatchError (f"xpatch { s .name } : jmp to { t :#x} is not a label" )
1616+ L .append (f"goto L_{ t :08x} ;" ); ends = True ; break
1617+ if m .startswith ('j' ) and m [1 :] in CC and i .operands [0 ].type == X86_OP_IMM :
1618+ t = i .operands [0 ].imm
1619+ if t not in lbl : raise XPatchError (f"xpatch { s .name } : { m } to { t :#x} is not a label" )
1620+ L .append (f"if({ CC [m [1 :]]} ) goto L_{ t :08x} ;" )
1621+ else :
1622+ self .cur_fmask = F_ALL
1623+ if m [:1 ] == 'f' or X86_GRP_FPU in i .groups :
1624+ raise XPatchError (f"xpatch { s .name } : x87 in patched code" )
1625+ try : L += self .insn (i , targets )
1626+ except Unsupported as e : raise XPatchError (f"xpatch { s .name } : { m } not supported ({ e } )" )
1627+ if m in ('ret' , 'retf' ): ends = True ; break
1628+ if s .kind == 'w' and pos >= s .e : break
1629+ self .cur_fmask = saved
1630+ if s .kind == 'w' and not ends and pos != s .e :
1631+ raise XPatchError (f"xpatch { s .name } : patched code at { s .a :#x} ends at { pos :#x} , region ends at { s .e :#x} " )
1632+ if s .kind == 'cave' and not ends and pos != s .a + len (data ):
1633+ raise XPatchError (f"xpatch { s .name } : cave did not decode to its end" )
1634+ return L , ends
1635+
1636+ def _xp_emit (self , s , lbl , targets , resume ):
1637+ if s .kind == 'c' :
1638+ L = [s .data ]; ends = s .data .rstrip ().endswith ('return;' )
1639+ elif s .kind == 'cave' :
1640+ L , ends = self ._xp_lines (s , s .data , lbl , targets )
1641+ else :
1642+ L = None
1643+ for ph in PLACEHOLDERS_XP :
1644+ buf = bytearray (s .data )
1645+ for off , var in s .var : buf [off ] = ph
1646+ L0 , ends = self ._xp_lines (s , bytes (buf ), lbl , targets )
1647+ if not s .var : L = L0 ; break
1648+ tok = re .compile (r'\b0x%xu\b' % ph )
1649+ text = "\n " .join (L0 )
1650+ if len (s .var ) == 1 and len (tok .findall (text )) == 1 :
1651+ var = s .var [0 ][1 ]
1652+ L = [tok .sub (f"((uint32_t)g_xpval[{ self .xp .vars .index (var )} ] /* { var } */)" , text )]
1653+ break
1654+ if L is None :
1655+ raise XPatchError (f"xpatch { s .name } : no unique placeholder at { s .a :#x} " )
1656+ self .xp_hits .add ((s .name , s .a ))
1657+ out = [f"if (g_xpatch[{ s .idx } ]) {{ /* xpatch { s .name } { s .a :#x} ..{ s .e :#x} */" ]
1658+ out += [" " + l for l in L ]
1659+ if not ends :
1660+ out .append (f" goto L_{ s .e :08x} ;" if s .e in lbl else f" goto XR_{ s .e :08x} ;" )
1661+ if s .e not in lbl : resume .add (s .e )
1662+ out .append ("}" )
1663+ return out
1664+
16021665 # ---------- function -> C ----------
16031666 def lift (self , entry ):
16041667 blocks , calls = self .discover (entry )
@@ -1622,12 +1685,26 @@ def lift(self, entry):
16221685 body = []
16231686 x87opt = _os .environ .get ('X87OPT' ,'' ) == '1'
16241687 xb = X87Region ()
1688+ # LOCAL PATCH: xpatch sites in this function; the resume labels needed
1689+ # are known after the sites are emitted, so they are collected first.
1690+ xsites = self .xp .sites_for (self , blocks ) if self .xp else {}
1691+ xresume = set (); xr_done = set (); xs_done = set ()
1692+ if xsites :
1693+ for s in xsites .values ():
1694+ if s .e not in lbl : xresume .add (s .e )
16251695 for ba in addrs :
16261696 body .append (f"L_{ ba :08x} :; /* --- block --- */" )
16271697 ins = blocks [ba ]
16281698 xb .reset ()
16291699 for k ,i in enumerate (ins ):
16301700 m = i .mnemonic
1701+ if i .address in xresume and i .address not in xr_done :
1702+ if x87opt and xb .active : body += [" " + l for l in xb .close ()]
1703+ body .append (f"XR_{ i .address :08x} :;" )
1704+ xr_done .add (i .address )
1705+ if i .address in xsites :
1706+ if x87opt and xb .active : body += [" " + l for l in xb .close ()]
1707+ body += [" " + l for l in self ._xp_emit (xsites [i .address ], lbl , targets , set ())]
16311708 if x87opt and xb .active and (m == 'jmp' or (m .startswith ('j' ) and m [1 :] in CC )
16321709 or m in ('jecxz' ,'jcxz' ,'loop' ,'loope' ,'loopz' ,'loopne' ,'loopnz' )):
16331710 body += [" " + l for l in xb .close ()]
0 commit comments