Skip to content

Commit 10fec20

Browse files
author
rocky
committed
Start formatting stack manip opcodes...
These are things like SWAP, COPY, DUP_TOP, ROT_XXX
1 parent f53adb3 commit 10fec20

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

xdis/opcodes/format/basic.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) Copyright 2023 by Rocky Bernstein
1+
# (C) Copyright 2023, 2025 by Rocky Bernstein
22
#
33
# This program is free software; you can redistribute it and/or
44
# modify it under the terms of the GNU General Public License
@@ -17,7 +17,6 @@
1717
Routines for formatting opcodes.
1818
"""
1919

20-
2120
def format_extended_arg(arg):
2221
return str(arg * (1 << 16))
2322

@@ -47,7 +46,7 @@ def format_MAKE_FUNCTION_10_27(argc: int) -> str:
4746

4847

4948
# Up until 3.7
50-
def format_RAISE_VARARGS_older(argc):
49+
def format_RAISE_VARARGS_older(argc) -> str:
5150
assert 0 <= argc <= 3
5251
if argc == 0:
5352
return "reraise"
@@ -57,6 +56,12 @@ def format_RAISE_VARARGS_older(argc):
5756
return "exception, parameter"
5857
elif argc == 3:
5958
return "exception, parameter, traceback"
59+
return ""
60+
61+
def format_ROT_TWO(_: int) -> str:
62+
# We add a space at the end as a sentinal to use in get_instruction_tos_str()
63+
return "TOS, TOS1 = TOS1, TOS"
64+
6065

6166

6267
opcode_arg_fmt_base = opcode_arg_fmt34 = {
@@ -65,4 +70,5 @@ def format_RAISE_VARARGS_older(argc):
6570
"CALL_FUNCTION_VAR_KW": format_CALL_FUNCTION_pos_name_encoded,
6671
"EXTENDED_ARG": format_extended_arg,
6772
"RAISE_VARARGS": format_RAISE_VARARGS_older,
73+
"ROT_TWO": format_ROT_TWO,
6874
}

xdis/opcodes/format/extended.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) Copyright 2023-2024 by Rocky Bernstein
1+
# (C) Copyright 2023-2025 by Rocky Bernstein
22
#
33
# This program is free software; you can redistribute it and/or
44
# modify it under the terms of the GNU General Public License
@@ -16,11 +16,15 @@
1616
"""
1717
Routines for formatting opcodes.
1818
"""
19+
20+
import re
1921
from typing import List, Optional, Tuple
2022

2123
from xdis.instruction import Instruction
2224
from xdis.opcodes.format.basic import format_IS_OP, format_RAISE_VARARGS_older
2325

26+
NULL_EXTENDED_OP = "", None
27+
2428

2529
def extended_format_binary_op(
2630
opc, instructions: List[Instruction], fmt_str: str
@@ -108,11 +112,7 @@ def extended_format_infix_binary_op(
108112
instructions[j].opcode in opc.operator_set
109113
and instructions[i].opcode in opc.operator_set
110114
):
111-
arg2 = (
112-
instructions[j].tos_str
113-
if instructions[j].tos_str is not None
114-
else instructions[j].argrepr
115-
)
115+
arg2 = get_instruction_tos_str(instructions[j])
116116
start_offset = instructions[j].start_offset
117117
return f"{arg2}{op_str}{arg1}", start_offset
118118
elif instructions[j].start_offset is not None:
@@ -265,7 +265,7 @@ def extended_format_ATTR(
265265
instr1.tos_str
266266
or instr1.opcode in opc.NAME_OPS | opc.CONST_OPS | opc.LOCAL_OPS | opc.FREE_OPS
267267
):
268-
base = get_instruction_arg(instr1)
268+
base = get_instruction_tos_str(instr1)
269269

270270
return (
271271
f"{base}.{instructions[0].argrepr}",
@@ -448,6 +448,21 @@ def extended_format_COMPARE_OP(
448448
)
449449

450450

451+
def extended_format_DUP_TOP(
452+
opc, instructions: List[Instruction]
453+
) -> Tuple[str, Optional[int]]:
454+
"""Try to extract TOS value and show that surrounded in a "push() ".
455+
The trailing space at the used as a sentinal for `get_instruction_tos_str()`
456+
which tries to remove the push() part when the operand value string is needed.
457+
"""
458+
459+
# We add a space at the end as a sentinal to use in get_instruction_tos_str()
460+
if instructions[1].optype not in ['jrel', 'jabs']:
461+
return extended_format_unary_op(opc, instructions, "push(%s) ")
462+
else:
463+
return NULL_EXTENDED_OP
464+
465+
451466
def extended_format_CALL_FUNCTION(opc, instructions) -> Tuple[str, Optional[int]]:
452467
"""call_function_inst should be a "CALL_FUNCTION" instruction. Look in
453468
`instructions` to see if we can find a method name. If not we'll
@@ -468,7 +483,7 @@ def extended_format_CALL_FUNCTION(opc, instructions) -> Tuple[str, Optional[int]
468483

469484
assert i is not None
470485
if i >= len(instructions) - 1:
471-
return "", None
486+
return NULL_EXTENDED_OP
472487

473488
fn_inst = instructions[i + 1]
474489
if fn_inst.opcode in opc.operator_set:
@@ -480,7 +495,7 @@ def extended_format_CALL_FUNCTION(opc, instructions) -> Tuple[str, Optional[int]
480495
arglist.reverse()
481496
s = f'{fn_name}({", ".join(arglist)})'
482497
return s, start_offset
483-
return "", None
498+
return NULL_EXTENDED_OP
484499

485500

486501
def extended_format_IMPORT_FROM(
@@ -493,7 +508,8 @@ def extended_format_IMPORT_FROM(
493508
instructions[i].start_offset, instructions, 1
494509
)
495510
if i is None:
496-
return "", None
511+
return NULL_EXTENDED_OP
512+
497513
module_name = get_instruction_arg(instructions[i])
498514
if module_name.startswith("import_module("):
499515
module_name = module_name[len("import_module(") : -1]
@@ -647,7 +663,7 @@ def extended_format_CALL_METHOD(opc, instructions) -> Tuple[str, Optional[int]]:
647663
arglist, arg_count, first_arg = get_arglist(instructions, 0, arg_count)
648664

649665
if first_arg is None or first_arg >= len(instructions) - 1:
650-
return "", None
666+
return NULL_EXTENDED_OP
651667

652668
fn_inst = instructions[first_arg + 1]
653669
if fn_inst.opcode in opc.operator_set and arglist is not None:
@@ -657,7 +673,8 @@ def extended_format_CALL_METHOD(opc, instructions) -> Tuple[str, Optional[int]]:
657673
arglist.reverse()
658674
s = f'{fn_name}({", ".join(arglist)})'
659675
return s, start_offset
660-
return "", None
676+
return NULL_EXTENDED_OP
677+
661678

662679

663680
def extended_format_RAISE_VARARGS_older(
@@ -771,6 +788,18 @@ def get_instruction_arg(inst: Instruction, argval=None) -> str:
771788
return inst.tos_str if inst.tos_str is not None else argval
772789

773790

791+
def get_instruction_tos_str(inst: Instruction) -> str:
792+
if inst.tos_str is not None:
793+
argval = inst.tos_str
794+
argval_without_push = re.match(r"^push\((.+)\) ", argval)
795+
if argval_without_push:
796+
# remove surrounding "push(...)" string
797+
argval = argval_without_push.group(1)
798+
else:
799+
argval = inst.argrepr
800+
return argval
801+
802+
774803
def get_instruction_index_from_offset(
775804
target_offset: int, instructions: List[Instruction], start_index: int = 1
776805
) -> Optional[int]:
@@ -858,6 +887,7 @@ def skip_cache(instructions: List[Instruction], i: int) -> int:
858887
"BUILD_TUPLE": extended_format_BUILD_TUPLE,
859888
"CALL_FUNCTION": extended_format_CALL_FUNCTION,
860889
"COMPARE_OP": extended_format_COMPARE_OP,
890+
"DUP_TOP": extended_format_DUP_TOP,
861891
"IMPORT_FROM": extended_format_IMPORT_FROM,
862892
"IMPORT_NAME": extended_format_IMPORT_NAME,
863893
"INPLACE_ADD": extended_format_INPLACE_ADD,

xdis/opcodes/opcode_311.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from typing import Dict, List, Optional, Tuple
2525

2626
import xdis.opcodes.opcode_310 as opcode_310
27+
from xdis.instruction import Instruction
2728
from xdis.opcodes.base import (
2829
binary_op,
2930
def_op,
@@ -244,10 +245,38 @@ def extended_format_BINARY_OP(opc, instructions) -> Tuple[str, Optional[int]]:
244245
return extended_format_binary_op(opc, instructions, f"%s {opname} %s")
245246

246247

247-
def format_BINARY_OP(arg) -> str:
248+
def extended_format_SWAP(
249+
opc, instructions: List[Instruction]
250+
) -> Tuple[str, Optional[int]]:
251+
"""call_function_inst should be a "SWAP" instruction. See if
252+
`we can find the two instructions to be swapped. If not we'll
253+
return None.
254+
255+
"""
256+
# From opcode description: argc indicates the total number of
257+
# positional and keyword arguments. Sometimes the function name
258+
# is in the stack arg positions back.
259+
# From opcode description: arg_count indicates the total number of
260+
# positional and keyword arguments.
261+
262+
swap_instr = instructions[0]
263+
i = swap_instr.argval
264+
# s = ""
265+
266+
if (i is None or not (0 < i < len(instructions))):
267+
return "", None
268+
269+
# To be continued
270+
return "", None
271+
272+
def format_BINARY_OP(arg: int) -> str:
248273
return _nb_ops[arg][1]
249274

250275

276+
def format_SWAP_OP(arg: int) -> str:
277+
return f"TOS <-> TOS{arg-1}"
278+
279+
251280
opcode_arg_fmt311 = opcode_arg_fmt310.copy()
252281
del opcode_arg_fmt311["CALL_FUNCTION"]
253282
del opcode_arg_fmt311["CALL_FUNCTION_KW"]
@@ -257,6 +286,7 @@ def format_BINARY_OP(arg) -> str:
257286
**opcode_arg_fmt310,
258287
**{
259288
"BINARY_OP": format_BINARY_OP,
289+
"SWAP": format_SWAP_OP,
260290
},
261291
}
262292

0 commit comments

Comments
 (0)