bp를 베이스로 쓰면 기본 세그먼트가 DS가 아니라 SS(스택 세그먼트)가 된다. 지금은 .MODEL TINY라서 DS=SS=CS가 전부 같은 세그먼트를 가리키기 때문에 문제가 안 된다.
d ss:0x0006 으로 덤프해보자
ss:0x0006에 F0ss:0x0007에 FE
레지스터에 읽어올 때는 FEF0으로 읽는다.
리틀 엔디언
[BP + 6]에서 값을 읽으면 FEF0인지 확인했다.
add nums[bx+di], dx
실행 전 레지스터의 상태는 다음과 같다.
BX: 0000
DI: 0003
nums의 주소는 어셈블 시점에 계산되어 011A로 바이너리에 적혔다.
d ds:0x011A 로 덤프해서 확인해보면
nums[0] ~ nums[1]: 0A 00
메모리에 리틀 엔디언으로 저장되니 원래 값을 복원하면 00 0A
십진수로 10이다.
nums[2] ~ nums[3]: 14 00
메모리에 리틀 엔디언으로 저장되니 원래 값을 복원하면 00 14
십진수로 20이다.
나머지는 생략한다.
덧셈 결과 CY를 주목하자. 캐리가 발생했다.
원래 nums + 3에 저장된 2바이트 값은 00 1E이다.
0x1E00 + 0xFEF5 결과 0x1CF5가 nums + 3에 저장되었다.
받아올림 발생
nums + 3에 F5
nums + 4에 FE
adc: 캐리를 이용한 덧셈
캐리 플래그로 받아올림하기 때문에 레지스터 크기보다 큰 숫자 덧셈에 사용된다.
실행해서 확인하기
TITLE Main.DOSSEG.8086.NO87.MODEL TINY.DATAm32 DW 1234h, 5678h.CODE.STARTUP ; 낮은 자리 덧셈 mov ax, 0FFFFh add m32[0], ax ; 높은 자리 덧셈 mov dx, 1111h adc m32[2], dxexit: mov ah, 4Ch xor al, al int 21hEND
TITLE Main.DOSSEG.8086.NO87.MODEL TINY.DATA num1 DW 0FF10h, 1234h, 0000h, 0000h ; 0x0000_0000_1234_FF10 num2 DW 0F000h, 0001h, 0000h, 0000h ; 0x0000_0000_0001_F000 result DW 0, 0, 0, 0.CODE.STARTUP mov cx, 4 ; 4개의 워드 단위로 덧셈, cx의 주용도는 반복 카운터 mov si, 0 ; si 레지스터는 배열의 인덱스 역할을 함 clcadd_loop: ; 워드 덧셈 mov ax, num1[si] ; num1의 현재 워드 읽기 adc ax, num2[si] ; num2의 현재 워드 더하기, 현재 ax에는 "num1의 워드 + num2의 워드"가 저장됨 mov result[si], ax ; 결과를 result 배열에 저장 ; 다음 워드로 이동 inc si inc si ; 루프 카운터 감소(캐리 안 건드림) dec cx ; 종료 조건 확인 및 점프 jnz add_loopexit: mov ah, 4Ch xor al, al int 21hEND
위를 실행하면 예상되는 결과는 DW에 0x0000_0000_1236_EF10이 들어가는 것이다.
첫번째 루프에서 mov result[si], ax까지 실행하고 덤프해서 result에 저장된 값을 확인해보자.
ds:0x012E를 덤프해보면, 10 EF를 확인할 수 있다.
result의 주소가 ds:0x012E
가장 낮은 자리 수 덧셈이 정상적인 것을 확인했다.
이제 캐리 플래그가 켜졌는지 확인해보자.
CY: Carry Yes
캐리 플래그가 켜졌다.
CX: 0004
최대 카운터 값 확인
SI: 0000
현재 인덱스 값 확인
이제 인덱스를 inc로 증가시킬 때 캐리 플래그가 변하는지 확인해보자.
변하지 않았다.
멀티 바이트 덧셈에서 인덱스 증가에 inc를 사용해야 한다.
캐리 플래그에 영향을 주지 않고 인덱스를 증가시킬 수 있기 때문이다.
" inc는 캐리 프래그를 변화시키지 않는다"를 유도할 때 멀티 바이트 덧셈을 생각하자.
inc 와 오버플로우 플래그, 부호 플래그
TITLE Main.DOSSEG.8086.NO87.MODEL TINY.DATAnum DB 7Fh.CODE.STARTUP ; 8bit overflow inc num ; Z=1, S=0, O=0, C=0 xor ax, ax ; 16bit overflow mov ax, 7FFFh inc axexit: mov ah, 4Ch xor al, al int 21hEND
위를 실행해 오버플로우 플래그가 언제 켜지는지 익히자.
부호 플래그의 변화도 확인하자.
inc num 실행 전 상태다.
NV: No Overflow
PL: Plus
inc num 실행 후
OV: Overflow
NG: Negative
d ds:0x0110으로 num의 값을 덤프했다.
80으로 증가했다. “signed” 관점에서 127에서 -128로 오버플로우가 발생했다.
부호도 음수가 되었다.
결과 비트의 MSB를 부호 플래그에 복사
Overflow는 signed 관점에서 signed 범위를 넘어서는가를 확인하면 된다.
xor ax, ax로 오버플로우 플래그를 초기화했다.
NV: No Overflow
PL: Plus
mov ax, 7FFFh는 데이터 전송 니모닉이라 플래그에 영향을 주지 않는다.
![[003. 데이터 전송 니모닉#mov-데이터를-이동|mov 데이터를 이동]]
inc ax를 실행하고 결과를 확인해보자.
AX: 8000
OV: OverFlow
NG: Negative
16비트 수를 inc로 증가시켰을 때 오버플로우가 발생함을 확인했다.
16비트 수도 결과의 MSB를 부호 플래그에 복사한다.
16비트 연산에서도 Overflow는 signed 관점에서 signed 범위를 넘어서는가를 확인하면 된다.
inc 와 제로 플래그
TITLE Main.DOSSEG.8086.NO87.MODEL TINY.DATAnum DB 0FFh.CODE.STARTUP ; 8bit inc num ; Z = 0 mov ax, 0 inc ax ; 16bit mov ax, 0FFFFh inc axexit: mov ah, 4Ch xor al, al int 21hEND
위 코드를 실행해 제로 플래그가 언제 켜지는지 확인해보자.
inc num을 실행하기 전 상태이다.
NZ: Not Zero
프로그램 시작 직후에 플래그의 기본값 0
ZR: Zero
inc num을 실행한 결과가 0x00이다.
0xFF → inc → 0x00
mov ax, 0 ; 여전히 제로 플래그 켜짐inc ax ; 제로 플래그 끔
mov는 [[003. 데이터 전송 니모닉#mov-데이터를-이동|mov 데이터를 이동]]라서 플래그를 바꾸지 않음
여전히 제로 플래그가 켜짐
NZ: Not Zero
0x00 → inc → 0x01 결과 제로 플래그가 꺼짐
; 16bitmov ax, 0FFFFhinc ax
mov는 [[003. 데이터 전송 니모닉#mov-데이터를-이동|mov 데이터를 이동]]라서 플래그를 바꾸지 않음
여전히 제로 플래그가 꺼진 상태
ZR: Zero
0xFFFF → inc → 0x0000 결과 제로 플래그가 켜짐
16비트도 결과에 따라 제로 플래그가 켜짐
결과의 모든 비트가 0이면 제로 플래그가 켜짐!
clc/stc: 캐리 플래그 변경
clc: CLear Carry
stc: Set Carry
sub: 뺄셈
sub al, [bx]
bx에 2바이트 주소가 저장되어 있음
그 주소가 가리키는 메모리에서 1바이트 값을 읽음
al에서 빼는 것을 MASM이 알기 때문에 BYTE PTR를 명시하지 않아도 1바이트임을 추론 가능
al에서 그 값을 빼서 결과를 al에 저장
sub nums[di], bl
유효 주소(nums + di)의 값 dst를 읽음
직접 메모리 피연산자 + 색인 연산자
dst - bl이 유효 주소에 저장
직접 메모리 피연산자와 색인 연산자
직접 메모리 피연산자 — table[4] 오프셋
table[4] 는 table 심볼의 시작 주소 + 4바이트 위치를 가리킨다.
어셈블 시점에 table의 주소 + 4 가 계산되어 바이너리에 16비트 오프셋으로 인코딩된다.
TITLE Main .DOSSEG .8086 .NO87 .MODEL TINY .DATA factor DD 80h .CODE .STARTUP mov ax, 0000h mul factorexit: ; 프로그램 종료 mov ah, 4Ch xor al, al ; 리턴값 0 int 21h END
Divides an implied destination operand by a specified source operand. Both operands are treated as unsigned numbers. If the source (divisor) is 16 bits wide, the implied destination (dividend) is the DX:AX register pair. The quotient goes into AX and the remainder into DX. If the source is 8 bits wide, the implied destination operand is AX. The quotient goes into AL and the remainder into AH.
div와 OF, SF, ZF, CF
모두 알 수 없음
div를 사용하고 이것들을 사용하려면 초기화 해야함
idiv: signed 나눗셈
부호를 고려한 div
조심해야할 포인트는 동일함
명시적 피연산자(src)의 크기에 따라 dst가 결정, src/dst의 몫과 나머지가 어디에 저장되는지 결정