간접 피연산자와 인덱스 피연산자 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""「간접 피연산자와 인덱스 피연산자」: [esi] 추적, 배열 합(워드·더블워드), 인덱스와 TYPE 곱하기, 포인터 변수, 카드 C1·C3
실행: python3 23_indirect-addressing_verify.py (표준 라이브러리만 쓴다)
MASM으로 돌린 결과가 아니다. 아래의 작은 x86 흉내기(8·16·32비트 레지스터, ZF·SF·CF·OF,
리틀 엔디언 메모리)로 문서에 적힌 값을 확인한다.
주소와 배열 번호는 0부터 센다.
"""
# ---- x86 흉내기 (이 파일 안에서만 쓴다. 다른 파일을 import하지 않는다) ----
MASK = {1: 0xFF, 2: 0xFFFF, 4: 0xFFFFFFFF}
R32 = ["eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp"]
PART = {}
for _r in "abcd":
PART[_r + "l"] = ("e" + _r + "x", 0, 1)
PART[_r + "h"] = ("e" + _r + "x", 8, 1)
PART[_r + "x"] = ("e" + _r + "x", 0, 2)
for _r in ("si", "di", "bp", "sp"):
PART[_r] = ("e" + _r, 0, 2)
for _r in R32:
PART[_r] = (_r, 0, 4)
SREG = {"cs", "ds", "ss", "es", "fs", "gs"}
class AsmError(Exception):
"""어셈블러가 받아 주지 않는 문장."""
def signed(v, size):
"""size바이트 값 v를 부호 있는 수로 읽는다."""
bits = 8 * size
v &= MASK[size]
return v - (1 << bits) if v >> (bits - 1) else v
class CPU:
def __init__(self):
self.r = dict.fromkeys(R32, 0)
self.mem = {}
self.ZF = self.SF = self.CF = self.OF = 0
# --- 메모리: 리틀 엔디언 ---
def rd(self, addr, size):
return sum(self.mem.get(addr + i, 0) << (8 * i) for i in range(size))
def wr(self, addr, size, v):
for i in range(size):
self.mem[addr + i] = (v >> (8 * i)) & 0xFF
def dump(self, addr, n):
return [self.mem.get(addr + i, 0) for i in range(n)]
# --- 피연산자: "al" 같은 레지스터 이름, 정수(즉시값), ("m", 주소, 크기) ---
def size(self, op):
if isinstance(op, str):
if op in SREG or op in ("ip",):
return 2
if op == "eip":
return 4
return PART[op][2]
if isinstance(op, tuple):
return op[2]
return None
def get(self, op):
if isinstance(op, int):
return op
if isinstance(op, tuple):
return self.rd(op[1], op[2])
full, shift, size = PART[op]
return (self.r[full] >> shift) & MASK[size]
def set(self, op, v):
if isinstance(op, tuple):
self.wr(op[1], op[2], v & MASK[op[2]])
return
full, shift, size = PART[op]
keep = self.r[full] & ~(MASK[size] << shift) & 0xFFFFFFFF
self.r[full] = keep | ((v & MASK[size]) << shift)
def _check(self, d, s):
if isinstance(d, int):
raise AsmError("immediate value cannot be destination")
if d in ("eip", "ip", "cs"):
raise AsmError("EIP/IP/CS cannot be the destination")
if isinstance(d, tuple) and isinstance(s, tuple):
raise AsmError("memory-to-memory not permitted")
if isinstance(d, str) and d in SREG and isinstance(s, int):
raise AsmError("immediate move to segment register not permitted")
ds, ss = self.size(d), self.size(s)
if ds is None:
raise AsmError("ambiguous operand size")
if ss is not None and ds != ss:
raise AsmError("size mismatch")
if ss is None and not -(1 << (8 * ds - 1)) <= s <= MASK[ds]:
raise AsmError("immediate too large")
return ds
def flags(self):
return (self.ZF, self.SF, self.CF, self.OF)
def _zs(self, r, n):
self.ZF = int(r == 0)
self.SF = (r >> (8 * n - 1)) & 1
# --- 명령 ---
def mov(self, d, s):
self._check(d, s)
if isinstance(d, str) and d in SREG:
return
self.set(d, self.get(s)) # 플래그는 건드리지 않는다
def xchg(self, a, b):
if isinstance(a, int) or isinstance(b, int):
raise AsmError("no immediate operands")
self._check(a, b)
va, vb = self.get(a), self.get(b)
self.set(a, vb)
self.set(b, va)
def movzx(self, d, s):
if not isinstance(d, str):
raise AsmError("destination must be a register")
if self.size(s) >= self.size(d):
raise AsmError("source must be smaller")
self.set(d, self.get(s))
def movsx(self, d, s):
if not isinstance(d, str):
raise AsmError("destination must be a register")
if self.size(s) >= self.size(d):
raise AsmError("source must be smaller")
self.set(d, signed(self.get(s), self.size(s)))
def _add(self, a, b, n, keep_cf=False):
m, top = MASK[n], 8 * n - 1
full = a + b
r = full & m
if not keep_cf:
self.CF = (full >> (top + 1)) & 1
self.OF = (((a ^ r) & (b ^ r)) >> top) & 1
self._zs(r, n)
return r
def _sub(self, a, b, n, keep_cf=False):
m, top = MASK[n], 8 * n - 1
r = (a - b) & m
if not keep_cf:
self.CF = int(a < b)
self.OF = (((a ^ b) & (a ^ r)) >> top) & 1
self._zs(r, n)
return r
def add(self, d, s):
n = self._check(d, s)
self.set(d, self._add(self.get(d), self.get(s) & MASK[n], n))
def sub(self, d, s):
n = self._check(d, s)
self.set(d, self._sub(self.get(d), self.get(s) & MASK[n], n))
def inc(self, d):
n = self.size(d)
if n is None:
raise AsmError("ambiguous operand size")
self.set(d, self._add(self.get(d), 1, n, keep_cf=True)) # CF는 그대로
def dec(self, d):
n = self.size(d)
if n is None:
raise AsmError("ambiguous operand size")
self.set(d, self._sub(self.get(d), 1, n, keep_cf=True)) # CF는 그대로
def neg(self, d):
n = self.size(d)
self.set(d, self._sub(0, self.get(d), n)) # 0 - 값
def dup(count, *items):
return list(items) * count
class Data:
"""데이터 세그먼트. 정의한 순서대로 바이트를 채우고 이름마다 주소·TYPE·LENGTHOF·SIZEOF를 적어 둔다."""
def __init__(self, cpu, base=0x00404000):
self.cpu, self.base, self.loc, self.sym = cpu, base, base, {}
def _flat(self, items):
for it in items:
if isinstance(it, list):
yield from self._flat(it)
else:
yield it
def define(self, name, tsize, *items):
start, count = self.loc, 0
for it in self._flat(items):
if isinstance(it, str) and tsize == 1:
for ch in it:
self.cpu.wr(self.loc, 1, ord(ch))
self.loc += 1
count += 1
else:
self.cpu.wr(self.loc, tsize, (it or 0) & MASK.get(tsize, (1 << 8 * tsize) - 1))
self.loc += tsize
count += 1
if name:
self.sym[name] = {"addr": start, "type": tsize, "length": count, "size": count * tsize}
def byte(self, name, *items):
self.define(name, 1, *items)
def word(self, name, *items):
self.define(name, 2, *items)
def dword(self, name, *items):
self.define(name, 4, *items)
def label(self, name, tsize):
self.sym[name] = {"addr": self.loc, "type": tsize, "length": 1, "size": tsize}
def offset(self, name):
return self.sym[name]["addr"]
def m(self, name, off=0, size=None):
"""이름+오프셋 메모리 피연산자. size를 주면 PTR로 크기를 바꾼 것이다."""
return ("m", self.sym[name]["addr"] + off, size or self.sym[name]["type"])
def bytes_of(self, name):
return self.cpu.dump(self.sym[name]["addr"], self.sym[name]["size"])
def must_fail(fn, *args):
try:
fn(*args)
except AsmError as e:
return str(e)
raise AssertionError("오류가 나야 하는데 통과했다")
# ---- 흉내기 끝 ----
if __name__ == "__main__":
# 간접 피연산자
c = CPU(); d = Data(c)
d.byte("val1", 0x10, 0x20, 0x30)
c.mov("esi", d.offset("val1"))
c.mov("al", ("m", c.get("esi"), 1)); assert c.get("al") == 0x10
c.inc("esi"); c.mov("al", ("m", c.get("esi"), 1)); assert c.get("al") == 0x20
c.inc("esi"); c.mov("al", ("m", c.get("esi"), 1)); assert c.get("al") == 0x30
# 크기를 모르는 [esi]
c = CPU(); d = Data(c)
d.word("myCount", 0)
c.mov("esi", d.offset("myCount"))
assert must_fail(c.inc, ("m", c.get("esi"), None)) == "ambiguous operand size"
c.inc(("m", c.get("esi"), 2)); assert c.get(d.m("myCount")) == 1
assert must_fail(c.add, ("m", c.get("esi"), None), 20) == "ambiguous operand size"
# 크기에 따라 결과가 달라진다: FFh 뒤에 01h가 있을 때 BYTE PTR, WORD PTR로 inc
c = CPU(); c.mem[0x500], c.mem[0x501] = 0xFF, 0x01
c.inc(("m", 0x500, 1)); assert c.dump(0x500, 2) == [0x00, 0x01]
c.mem[0x500], c.mem[0x501] = 0xFF, 0x01
c.inc(("m", 0x500, 2)); assert c.dump(0x500, 2) == [0x00, 0x02]
# 배열 합 (추적 표)
c = CPU(); d = Data(c, base=0x00404000)
d.word("arrayW", 0x1000, 0x2000, 0x3000)
trace = []
c.mov("esi", d.offset("arrayW")); trace.append((c.get("esi"), c.get("ax")))
c.mov("ax", ("m", c.get("esi"), 2)); trace.append((c.get("esi"), c.get("ax")))
c.add("esi", 2); trace.append((c.get("esi"), c.get("ax")))
c.add("ax", ("m", c.get("esi"), 2)); trace.append((c.get("esi"), c.get("ax")))
c.add("esi", d.sym["arrayW"]["type"]); trace.append((c.get("esi"), c.get("ax")))
c.add("ax", ("m", c.get("esi"), 2)); trace.append((c.get("esi"), c.get("ax")))
assert trace == [(0x00404000, 0), (0x00404000, 0x1000), (0x00404002, 0x1000),
(0x00404002, 0x3000), (0x00404004, 0x3000), (0x00404004, 0x6000)]
# ToDo: 더블워드 배열로 바꾼 것
c = CPU(); d = Data(c)
d.dword("arrayD", 0x10000, 0x20000, 0x30000)
c.mov("esi", d.offset("arrayD")); c.mov("eax", ("m", c.get("esi"), 4))
c.add("esi", 4); c.add("eax", ("m", c.get("esi"), 4))
c.add("esi", 4); c.add("eax", ("m", c.get("esi"), 4))
assert c.get("eax") == 0x60000
# 인덱스 피연산자 (더블워드판 포함)
c = CPU(); d = Data(c)
d.word("arrayW", 0x1000, 0x2000, 0x3000); d.dword("arrayD", 0x10000, 0x20000, 0x30000)
c.mov("esi", 0); c.mov("ax", d.m("arrayW", c.get("esi"))); assert c.get("ax") == 0x1000
c.add("esi", 2); c.add("ax", d.m("arrayW", c.get("esi"))); assert c.get("ax") == 0x3000
c.add("esi", 2); c.add("ax", d.m("arrayW", c.get("esi"))); assert c.get("ax") == 0x6000
c.mov("esi", 0); c.mov("eax", d.m("arrayD", c.get("esi")))
c.add("esi", 4); c.add("eax", d.m("arrayD", c.get("esi")))
c.add("esi", 4); c.add("eax", d.m("arrayD", c.get("esi")))
assert c.get("eax") == 0x60000
# 인덱스에 TYPE 곱하기
c = CPU(); d = Data(c)
d.byte("arrayB", 0, 1, 2, 3, 4, 5); d.word("arrayW", 0, 1, 2, 3, 4, 5); d.dword("arrayD", 0, 1, 2, 3, 4, 5)
c.mov("esi", 4)
c.mov("al", d.m("arrayB", c.get("esi") * d.sym["arrayB"]["type"])); assert c.get("al") == 0x04
c.mov("bx", d.m("arrayW", c.get("esi") * d.sym["arrayW"]["type"])); assert c.get("bx") == 0x0004
c.mov("edx", d.m("arrayD", c.get("esi") * d.sym["arrayD"]["type"])); assert c.get("edx") == 0x00000004
for i in range(6): # 모든 칸에서 i번째 원소가 나온다
for name, reg in (("arrayB", "al"), ("arrayW", "bx"), ("arrayD", "edx")):
c.mov(reg, d.m(name, i * d.sym[name]["type"])); assert c.get(reg) == i
# 곱하지 않으면: arrayW[4]는 2번 원소, arrayD[4]는 1번 원소
c.mov("bx", d.m("arrayW", 4)); assert c.get("bx") == 2
c.mov("edx", d.m("arrayD", 4)); assert c.get("edx") == 1
# 포인터 변수
c = CPU(); d = Data(c)
d.word("arrayW", 0x1000, 0x2000, 0x3000); d.dword("ptrW", d.offset("arrayW"))
c.mov("esi", d.m("ptrW")); assert c.get("esi") == d.offset("arrayW")
c.mov("ax", ("m", c.get("esi"), 2)); assert c.get("ax") == 0x1000
# 카드 C1 추적
c = CPU(); d = Data(c, base=0x00404000)
d.dword("arr", 5, 7, 9)
c.mov("esi", d.offset("arr")); c.mov("eax", ("m", c.get("esi"), 4)); assert (c.get("esi"), c.get("eax")) == (0x00404000, 5)
c.add("esi", 4); c.add("eax", ("m", c.get("esi"), 4)); assert (c.get("esi"), c.get("eax")) == (0x00404004, 12)
c.add("esi", 4); c.sub("eax", ("m", c.get("esi"), 4)); assert (c.get("esi"), c.get("eax")) == (0x00404008, 3)
# 카드 C3: WORD 배열에서 inc esi
c = CPU(); d = Data(c)
d.word("arrayW", 0x1234, 0x5678)
assert d.bytes_of("arrayW") == [0x34, 0x12, 0x78, 0x56]
c.mov("esi", d.offset("arrayW")); c.inc("esi"); c.mov("ax", ("m", c.get("esi"), 2)); assert c.get("ax") == 0x7812
print("ALL CHECKS PASSED")