전공/시스템 프로그래밍

9 -1 강 Conditional Loop Instructions (LOOPZ,LOOPE) Conditional structure(조건부 구조 예시코드!)

뜨거운 개발자 2023. 5. 15. 19:58

Conditional Loop Instructions(조건부 루프 명령)

오늘은 조건부 loop에 대해 다뤄보겠다.

loop 복습해보면 : ecx레지스터를 카운터로 사용해서 하나씩 줄여가면서 반복을 진행했다.

1. LOOPZ and LOOPE Instructions

  • LOOPZ : 제로플래그가 세팅 되어있을떄만 Loop 명령처럼 동작하는 명령어이다.
  • LOOPE : LOOPZ와 완전히 동일한 옵코드를 공유한다. (JUMPZ와 JUMPE가 같은것과 같은 개념이다.)

두 명령의 특징

  • 이 명령은 Loop명령 처럼 작동하지만 Zero flag를 확인해서 0일때만 레이블로 점프를 진행합니다.
  • ECX가 0이 아니고, 제로플래그가 세팅되어있을 때 루프 실행한다.
  • 만약 제로플레그가 0이 아니거나 ECX 레지스터가 0이 아닌 경우에는 점프가 발생하지 않고 다음 명령어로 제어권이 넘어간다.
  • 두 명령은 동일한 opcode를 공유한다.
  • 이 명령은 status flag를 바꾸지 않는다.

2. LOOPNZ and LOOPNE Instructions

  • loop not zero의 의미로 LOOPZ와 대응(반대)되는 명령어다.
  • LOOPZ와 LOOPE가 있듯 LOOPNZ와 LOOPNE가 있고 역시 동일한 옵코드를 공유한다.
  • zero 플래그가 clear 되어있을때 루프가 동작한다. (당연하게 ECX레지스터는 0이 아니어야 한다.)

예시) 첫 번째로 등장 하는 양수값 찾기

.data
array SWORD -3,-6,-1,-10,10,30,40,4
sentinel SWORD 0
sentinel SWORD 0
.code
	mov esi, OFFSET array
	mov ecx, LENGTHOF array
L1 : test WORD PTR [esi], 8000h ;해당 주소에 접근해서 2바이트 단위로 검사
		 pushfd 
		 add esi, TYPE array
		 popfd
		 loopnz L1
		 jnz quit
		 sub esi,TYPE array
quit:
  • test연산(이전에 Boolean 게시물에서 다룬 명령이다.)
    • and를 수행하지만 대상 피연산자를 수정하지 않는다.
    • 즉 overflow 및 캐리 플래그를 지운다.
    • AND명령과 동일한 방식으로, sign, zero, parity flag를 지운다.
    • 2개의 피연산자간의 and연산의 결과로 설정이 된다는 것이다.(다만 변경은 안됨)
  • test WORD PTR [esi], 8000h (msb가 0인지 1인지 판단)
    • 들어간 숫자가 음수인지 양수인지 판단!!
    • PTR연산자 (이전 6강에서 데이터 관련 연산자 게시물에서 다뤘다.)
      • PTR연산자는 원래 사이즈를 overide해주는 역활이다.
      • 즉 이 코드에서는 WORD라고 가정하고 esi의 레지스터에 접근하는 것이다.
    • [] : indirect opeand(역시 이것도 6강 게시물에서 다뤘다.)
      • 이건 주소값으로 esi를 사용하고 있기 때문에 그곳을 역참조 하는 것이다.
      • 즉 이 코드에서는 array의 첫번째 위치를 참조하는 것이라고 보면된다.
  • add esi, TYPE array : esi를 늘려준다.(다음 값으로 이동)
    • TYPE는 sizeof연산자랑 비슷하다.(6강에 다뤘음)
  • pushfd : 플레그레지스터에 있는 값을 스택에 넣어두겠다.
    • 즉 모든 플래그를 보존할 때 사용한다. (7강)
  • popfd : 다시 이전의 결과 플래그를 복원한다.
  • jnz quit : 전부 다 돌았는데도 음수를 못 찾았다면 빠져나가라.
    • jz : zero flag가 세팅되어 있는 경우(ZF = 1) 해당 레이블로 점프
    • jnz는 zero flag가 세팅되어 있지 않은 경우(ZF = 0) 점프 명령이 수행된다.
  • sub esi,TYPE array add랑 같은 느낌으로 배열하나 포인터를 하나 빼준다고 생각하기.

전체 흐름 요약

  • 2바이트 단위로 esi를 검사하는데 esi에는 array의 시작점이 있다. MSB를 계속해서 검사하는데 AND연산을 하면 MSB가 1이어야만 zero flag가 세팅이 안된다. 즉 음수여야만 세팅이 안되는 것이다.
  • 만약 제로플래그가 세팅이 안되었다면 esi만큼 계속해서 루프를 돌 것이고, 제로 플래그가 세팅이 되는 순간(양수가 있는 순간) quit으로 나갈 것이다.
  • 하지만 루프가 그냥 종료가 됐다면 양수는 없는 것 이기 때문에 늘린 esi를 다시 하나 줄여줘서 이상한 곳을 참조하는게 아닌 마지막 value를 가리키게 만들었다.

Conditional structure

조건부 구조

  • 서로 다른 논리 분기 사이에서 선택을 실행하는 하나 이상의 조건부 표현식
  • 각 브랜치는 서로 다른 명령어 시퀀스를 실행한다.
  • 즉 쉽게 생각해서 if else를 생각해봅시다.

1. Block-Structured IF Statements

high level 언어

  • if문 이라고 생각하면 된다.

어셈블리 언어

  • CPU 상태 플래그중 하나가 영향을 받는 방식으로 bool-expression을 평가한다.
  • 관련 cpu상태 플래그의 값에 따라서 두개의 문 목록으로 제어권을 이전하는 일련의 점프를 구성한다.

예시 1

  • high level 버전 코드
    if (op1 == op2)
    {
    	x = op1=op2;
    }
  • CMP명령을 사용해서 if문 사용하는 어셈블리 코드
    	mov eax, op1
    	cmp eax, op2
    	jne L1
    	mov X,1
    	mov Y,2
    L1 :
    • cmp연산(8강 내용)
      • 주어진 피연산자를 빼기 해보는 연산자 이지만 실제로 빼기를 하는건 아니고 비교만 하는 연산자 이다.
    • op1과 op2를 비교해서 같지 않으면 L1으로 가서 명령을 실행하고 만약 0이라면 아래 명령을 실행한다.
  • JE를 사용해서 == 연산자를 구현하는 방법 (코드가 덜 간결하다.)
    	mov eax, op1
    	cmp eax, op2
    	je L1
    	jmp L2
    L1 :  mov X,1
    			mov Y,2
    L2 : 
    • 이건 다들 이해 할꺼라고 믿습니당. 만약 모르시면 제 게시물에서 모르는 명령을 검색해보면서 찾아가시죠.
  • 둘 다 결과는 똑같다 다만 명령어의 갯수가 다르다.
  • 퍼포먼스가 위에 것이 더 좋다.

예시 2

  • high level 언어 버전
    clusterSize = 8192;
    if terrabytes < 16
    	clusterSize = 4096;	
  • 어셈블리 언어 버전
    	mov clusterSize, 8192 
    	cmp terrabytes, 16
    	jae next
    	mov clusterSize,4096
    next :
    • jae (8-2강)
      • left ≥ right 일 때 점프하는 명령어
    • 즉 terrabyte가 16보다 같거나 큰 경우 jump를 하고 아닌경우 4096을 넣은 코드

Jump를 두번하는 것을 방지하기 위해서 컨디션을 반대로 뒤집어 준 것이다.(jB가 아니라 jae를 해줬기 때문)

어느 경우 컨디션의 조건을 뒤집어 주어야하는가?

  • If 만 있고 else는 없는 경우!!!

예시 3

  • high level 언어 버전
    if (op1 > op2)
    	call Routine1;
    else 
    	call Routine2;
    endif	
  • 어셈블리 언어 버전
    	mov eax,op1
    	cmp eax,op2
    	jg A1
    	call Routine2
    	jmp A2
    A1 : call Routine1
    A2 : 
    • 변수를 비교할 때 레지스터에 넣고 cmp하는 것 잊지 맙시다.
    • 이 코드 보면 int 대충 4바이트라고 예측해볼 수 있다.
    • signed로 취급하고 있다.(eax에 넣는 행동과 jg는 signed comparisons중 하나이기 때문)
    • jg : left > right 인 경우 jump하는 명령
  • 교수님의 한마디 : 이거 되게 간단한데..! 직접 짜라고 하면 어려울 수 있어요~ 연습 해보시는걸 추천합니다.(if를 써봤을때 패턴들을 연습하시길 추천합니다.)

White Box Testing (방법론)

  • whiteBox testing에 대한 설명은 무시하셔도 됩니다.!!! (각 조건에 따라서 옳을 떄 옳지 않을때 등 truth table을 이용하는 것)
  • 그냥 중첩 if문 코드를 볼 때 어떻게 구현되는지 보고가세요!
  • high level 언어
  • 어셈블리 버전
    • 이 코드를 보면 시작 조건을 먼저 수행하고 내부 if else를 수행한다.
    • 딱히 어렵지는 않은 예시이다.

2. Compound Expressions

Logical AND Operator (논리연산자 AND)

원래 high level에서는 Short-Circuit Evaluation(회로 단락 평가)를 고려해서 작동한다.

  • Short-Circuit Evaluation 생각해야하는 이유
if ((A>B) AND (++c>D))

이렇게 되면 다 실행결과가 다르기 때문 생각해보자

  • high level언어
  • 어셈블리 코드
  • ja를 썻기 때문에 unsigned라고 예상할 수 있다.
  • 만약 signed였다면 jg를 했을 것이다.
  • 개선 어셈블리 코드 (if 문에서는 뒤집기)

Logical OR Operator

  • high level언어
  • 어셈블리 코드 (이것도 두번째 표현에는 jbe사용)

3. WHILE Loops

  • high level언어
  • 어셈블리 코드
    • while문은 loop가 쓰였다고 착각하기 쉬우나 조건을 사용했기 때문에 loop가 아니다.
    • 조건이 참이 되면 루프 조건을 반전 시켜서 종료 조건으로 이동하는 것이 편리하다.
    • 이 코드는 eax로 val1을 이동 시키고 eax와 val2를 비교해서 계속해서 값을 늘려가며 비교한다.
    • 그리고 끝날 때 eax에 있는 값을 var1으로 넣어준다.
    • 이렇게 하는 이유는 cmp연산자의 피연산자의 조합이 메모리 메모리는 안되고 레지스터가 필요하기 때문이다.
    • jnl을 보면 이게 signed임을 알 수가 있다.

3 -1 while문 안에 if중첩 예시

50보다 큰 값들을 합산하는 예시코드

  • high level언어
    int array[] = {10,60,20,33,72,89,45,65,72,18};
    int sample = 50;
    int ArraySize = sizeof array / sizeof sample;
    int index = 0;
    int sum = 0;
    while (index < ArraySize)
    {
    	if (array[index] > sample)
    	{
    		sum += array[index];
    	}
    	index++;
    }
  • 어셈블리 코드
    .data
    sum DWORD 0
    sample DWORD 50
    array DWORD 10,60,20,33,72,89,45,65,72,18
    ArraySize = ($ - array) / TYPE array
    
    .code
    main PROC
    	mov eax,0 ;sum
    	mov edx,sample 
    	mov esi,0 ;index
    	mov ecx,ArraySize
    L1: cmp esi, ecx ;if esi < ecx
    		jl L2
    		jmp L5
    L2: cmp array[esi*4], edx ; if array[esi] > edx
    		jg L3
    		jmp L4
    L3: add eax,array[esi*4]
    L4: inc esi
    		jmp L1
    L5: mov sum,eax
    • ArraySize 는 변수? 아니다!! 메크로다(Symbolic Constants -기호상수)
    • $ : 현재 위치 주소
    • $ - Array : Array 의 크기 (이걸 type으로 나누니까 갯수가 된다.) (5강 기호상수에 있습니다 복습!!)
    • cmp할 때 doubleWord PTR안 붙힌 이유 edx의 크기를 알고 있기 때문에.

4. Table-Driven Selection

  • Look-up table을 만들어서 Data segment에 table을 만들어두고
  • 이 코드에서 Process가 나오는데 그건 Processor이름(주소) 이다.

예시) 다음은 단일 문자 조회값과 프로시저의 주소가 포함된 태이블의 일부이다.

.data
CaseTable BYTE 'A'
					DWORD Process_A
					BYTE 'B'
					DWORD Process_B
	(etc...)
  • Process_A, Process_B, Process_C, Process_D가 각각 주소 120h, 130h, 140h, 150h에 위치한다고 가정합니다

프로그램 설명

  • 사용자가 키보드에서 문자를 입력한다.
  • 루프를 사용해서 문자를 look-up table의 각 항목과 비교한다.
  • 첫번째 일치 항목이 발견되면 조회값 바로 뒤에 저장된 프로시저 오프셋에 대한 호출이 발생한다.
  • 각 프로시저는 루프 중에 표시되는 다른 문자열의 오프셋을 사용해서 EDX를 로드한다.
INCLUDE Irvine32.inc
.data
CaseTable BYTE 'A'
					DWORD Process_A
EntrySize = ($ - CaseTable)
					BYTE 'B'
					DWORD Process_B
					BYTE 'C'
					DWORD Process_C
					BYTE 'D'
					DWORD Process_D
;EntrySize = 상수(Symbolic Constants) (숫자 5)
NumberOfEntries = ($ - CaseTable) / EntrySize 
prompt BYTE "Press capital A,B,C,or D: " ,0

msgA BYTE "Process_A",0
msgB BYTE "Process_B",0
msgC BYTE "Process_C",0
msgD BYTE "Process_D",0

.code 
main PROC
	mov edx, OFFSET prompt ;ask your input
	call WriteString
	call ReadChar ;read character into AL
	mov ebx,OFFSET CaseTable ;point EBX to the table
	mov ecx,NumberOfEntries ;loop counter
L1:
	cmp al,[ebx] ; match found?
	jne L2 ; no : continue
	call NEAR PTR [ebx + 1] ; yes : call the procedure
	call WriteString ;display message
	call Crlf ; "\n"
	jmp L3 ;exit the search
L2:
	add ebx,EntrySize
	loop L1
L3:
	exit
main ENDP

Process_A PROC
	mov edx, OFFEST msgA
	ret
Process_A ENDP
Process_B PROC
	mov edx, OFFEST msgB
	ret
Process_B ENDP
Process_C PROC
	mov edx, OFFEST msgC
	ret
Process_C ENDP
Process_D PROC
	mov edx, OFFEST msgD
	ret
Process_D ENDP
END main
  • 프로시저의 주소를 초기화!
  • call NEAR PTR [ebx +1] 이해가 안 간다.
    • real address 모드가 사용되던 시절에는 메모리의 주소가 세그먼트와 오프셋으로 분리되어 표현되었습니다. 그 때, 호출하는 부분과 동일 세그먼트상의 프로시저를 Near, 다른 세그먼트상의 프로시저를 Far Label로 구분하여 호출을 해야 했었고 NEAR PTR의 near는 이를 의미합니다.
    • 하지만, Protected Mode로 오면서 Flat 메모리 모델을 사용하게 되었고 이제는 Near와 Far의 구분이 의미없어졌습니다. Legacy로서 남아있는 것이라 보시면 되고, call 명령에서 indirect한 프로시저 호출을 위해 사용되고 있다는 것 정도만 보시고 넘어가시면 됩니다.

장점

  • 이 방법의 장점 : if else 구조로 떡칠하지 않아도 된다.
  • 런타임에 태이블 재구성 가능,
  • 코드량 줄일 수 있다.
  • 많은 수의 비교를 처리할 수 있고
  • JUMP 및 CALL명령보다 더 쉽게 수정할 수 있다.

단점

  • 초기에 약간 오버헤드가 발생한다.(프로시저가 있기 때문에)
  • 다양한 오버헤드 발생. : 인스트럭션 포인터를 다루는 행동 + 프로시저에 들어갈 때 작업하고 있던 행동 레지스터 복사 등등

Application: Finite-State Machines

  • 유한상태 머신 (FSM)
  • 이 기계에 꽂혀서 저자가 만든겁니다..!(굳이 이것이 이해가 안가도 넘어가도 됨.
  • 여기서 설명하는 FSM(유한 상태 머신)(디지털 논리에 나왔을 것)에서 보면 된다.
  • 이동 못하면 (터미널 상태)

A finite-state machine (FSM)

  • FSM을 나타내는 그래프
  • 노드: 프로그램 상태
  • 엣지: 한 상태에서 다른 상태로 전환되는 지점
  • 하나의 노드가 초기 상태로 지정됩니다.
  • 하나 이상의 상태가 터미널 상태로 지정됩니다.
  • 종료 상태는 프로그램이 오류를 발생시키지 않고 중지될 수 있는 상태를 나타냅니다.
  • FSM은 방향성 그래프라고 하는 보다 일반적인 유형의 구조의 특정 인스턴스입니다.

1. Validating an Input String

입력 유효성 검사

  • 입력 스트림을 읽는 프로그램은 종종 입력의 유효성을 검사해야한다.
  • 입력 유효성 검사를 할 수가 있다.
  • 위 그림은 문자열이 x로 시작하고z 로 끝나야만 하는 상태이다.
  • 첫번째 문자와 마지막 문자 사이에는 0개 이상의 문자가 있을 수 있다.

2. Validating a Signed Integer

  • 입력은 선택 사항인 선행 기호와 그 뒤에 오는 일련의 숫자로 구성됩니다.
  • 다이어그램이 암시하는 최대 자릿수는 없습니다.
  • 우리가 살펴볼 것!! Signed Int를 input으로 받는 상황을 가정한다.
  • 우리는 FSM자체를 보는게 아니라 어떻게 코드로 짰는지를 봅시다.

상태 A코드

유산상태 머신 전체 코드

INCLUDE Irvine32.inc

ENTER_KEY = 13
.data
InvalidInputMsg BYTE "Invalid input", 13,10,0

.code 
main PROC
	call Clrscr ; 콘솔창 지우는 함수
StateA:
	call Getnext ;read next char into AL
	cmp al,'+' ;leading + sign?
	je StateB ; go to State B
	cmp al,'-' ;leading - sign?
	je StateB ;go to State B
	call IsDigit ;Zf = 1 if AL contains a digit
	jz StateC ;go to State C
	call DisplayErrorMsg ;invalid input found
	jmp Quit
StateB: 
	call Getnext ;read next char into AL
	call IsDigit ;Zf = 1 if AL contains a digit
	jz StateC
	call DisplayErrorMsg ;invaild input found
	jmp Quit

StateC:
;숫자인지 확인, 엔터인지 확인 그게 아니면 에러메세지 후 끝
	call Getnext ;read next char into AL
	call IsDigit ;Zf = 1 if AL contains a digit
	jz StateC
	cmp al,ENTER_KEY ; Enter key pressed?
	je Quit
	call DisplayErrorMsg ;no :invalid input found
	jmp Quit
Quit: 
	call Crlf
	exit
main ENDP

;Read a char from statdard input and return AL contain charactor
Getnext PROC
	call ReadChar ;input from keyboard
	call WriteChar ;echo on screen
Getnext ENDP

;Display an error msg 
DisplayErrorMsg PROC
	push edx
	mov edx,OFFSET InvalidInputMsg
	call WriteString
	pop edx
	ret
DisplayErrorMsg ENDP
END main
  • 짧은 복습 je와 jz : 둘은 완전 동일 한 명령이지만 비교를 할 때는 주로 je 0인지 아닌 지 확인 할 때는 jz를 쓰는게 더 직관적이다.
  • ReadChar : 저자가 만든 프로시저 (내가 입력한게 바로 출력이 되는게 아니라 읽는것)
  • WriteChar 스크린에 띄워준다. 해도 AL에는 내가 받은 게 들어있다.
  • InvaildInputMsg 쪽을 보면 13 10은 윈도우에서 개행을 의미한다.

저자가 만든 IsDigit코드

  • ‘0’은 아스키코드 0이 아니라 문자 0임…!
  • 마지막 빠져나올 때 제로 플래그 세팅을 해줬다!


Uploaded by N2T

728x90