ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 시스템프로그래밍 08 - 어셈블리어에서 비트 연산
    쾌락없는 책임 (공부)/시스템프로그래밍 2021. 6. 1. 22:52
    반응형

    본 포스트는 강동완 교수님의 '시스템프로그래밍' 강의를 듣고

    이해한 것들을 정리한 포스트입니다.

    - 강의자료는 올리지 않습니다


    Shift and Rotate Instruction

     shift / rotate는 2진수의 비트를 이용한 연산으로 아래 표를 보면 이해가 잘 됩니다.

    1 0 1 0
    왼쪽으로 쉬프트
    0 1 0 0
    1 0 1 0
    왼쪽으로 로테이트
    0 1 0 1

     위 표에서 나오는 연산이 기본적인 쉬프트와 로테이트고 이런 비트 단위의 연산들은 곱셈이나 나눗셈 등의 연산에 비해서 비용이 상당히 적은 편이라 비트 연산을 할 수 있을땐 비트를 이용하는게 좋습니다. 어셈블리어에서 쉬프트와 로테이트는 위 표의 연산을 기본으로 가져가지만 실제로는 살짝 다르게 작동을 합니다.

     

    - logical shift : 특정 방향으로 비트 쉬프트를 합니다. 밀려나간 비트는 CF에 저장이 되며 새로 들어오는 비트는 0이 들어옵니다.

    - arithmetic shift : 위와 동작은 유사하나 새로 들어오는 비트가 원래 있는 비트가 됩니다. 이는 부호 유지용으로 많이 사용됩니다.

     

    1. SHL / SHR (Shift Left / Right - Logical shift)

    SHL/SHR destination, count ;count 만큼 쉬프트

     

    * 그런데 이런 비트를 이동하게 되면 2진수의 원리에 따라 각각 X2, /2를 한 형태가 됩니다.

    예시)

    0010  (=2) 왼쪽으로 쉬프트 0100 (=4)

     이런식으로 2로 곱하기, 나누기 하는 연산은 쉬프트 연산을 통해 적은 비용으로 연산이 가능합니다.

     

    2. SAL/SAR (Shift Left / Right - Arithmetic shift)

     위와 같은 연산을 하지만 앞의 수를 signed로 생각으로 하고 비트 연산을 해 줍니다.

    ; ax, eax 구조를 이용해서 부호 유지하기
    mov ax, -128 ; eax = ???? FF80h
    shl eax, 16  ; eax = FF80 0000h
    shr eax, 16  ; eax = FFFF FF80h

     

    3. ROL / ROR

     새로 들어오는 비트가 빠져나간 비트인 로테이션 입니다.

    mov al, 00100000b
    rol al, 4			; al = 0000 0010, cf = 1

    위같이 사용하는걸 보면 원으로 순환하는 모습을 볼 수 있으며 비트 그룹을 바꾸는 일이 가능합니다.

     

    4. RCL, RCR

     위와 같은 로테이션이지만 CF를 추가로 사용해 마치 1개 비트가 더 있는 것처럼로테이션을 하게 됩니다.

    CF AL
    1 0111 0000
    rcl al, 1
    0 1110 0001

     

    * 쉬프트 / 로테이션에서 OF는 어떤 경우에 세팅이 될까?

     - 주어진 수를 signed라고 봤을 때 수의 표현 범위를 벗어난 경우 세팅이 됩니다.

     - 예시로 0111이 왼쪽으로 쉬프트되면 1110으로 signed라고 가정을 했을 때 양수에서 음수가 되었으니 OF=1이 됩니다.

     

    5. SHLD / SHRD

     위 명령어의 D는 double로 operand가 2개임을 나타내고 있습니다. 

    SHLD/SHRD  destination, source, count

    *source는 무조건 register가 되어야 합니다.

     SHLD의 경우 source가 destination 오른쪽에, SHRD의 경우 왼쪽에 있다고 가정하고 비트 쉬프트를 하게 됩니다. 대신 source의 값에는 영향이 없습니다.

     

     

     

    어셈블리어에서 곱셈/나눗셈

     

    1. MUL

     일반적인 곱셈을 하는 명령어로 unsigned용입니다.

    MUL reg/mem(8, 16, 32)

    operand의 비트 수 무엇이랑 곱하나? 어디에 저장되나?
    8 AL 레지스터 AX 레지스터
    16 AX 레지스터 DX : AX 레지스터
    32 EAX 레지스터 EDX : EAX 레지스터

     위 연산을 통해서 상위 반쪽이 0이 아니면 CF = 1, OF = 1 이 됩니다.

     

    2. IMUL

     위 MUL과는 다르게 signed 수들을 위한 연산으로 결과를 보호하기 용이합니다. 그리고 MUL과 다르게 operand를 1, 2, 3개 받아올 수 있으며 이 operand의 수에 따라 연산이 달라지게 됩니다.

    Operand 수 코드 설명
    1개 IMUL reg/mem MUL과 과정이 같음
    2개 IMUL reg16/32, reg/mem/imm16 첫 operand는 무조건 레지스터가 오게 됩니다.
    자리수가 넘치게 되는 경우 OF, CF가 1이 됩니다. (결과 유실 여부 체크 가능)
    3개 IMUL destination, op, op op X op가 des로 저장되게 됩니다.
    두번째 op에는 상수만 올 수 있습니다.
    자리수가 넘치게 되는 경우 OF, CF가 1이 됩니다. (결과 유실 여부 체크 가능)

     * OF의 경우 아래 반만 봐도 수가 같으면 0, 다르면 1이 된다고 생각하면 됩니다. 즉, 상위 반이 부호를 뜻하고 있으면 1로 됩니다.

        ( CPU의 경우 아래 반의 MSB로 상위 반쪽이 채워졌는가로 판단하고 있습니다, 같다면 OP가 0으로 클리어됩니다)

     

    3. DIV

     일반적인 나눗셈을 하는 연산자로 unsigned 수들에 해당하는 나눗셈입니다.

    DIV reg, mem(8, 16, 32)

    위에 나온 곱셈처럼 operand는 1개만 오며 operand는 divisor즉 분모가 됩니다.

    Dividend (분자) Divisor (분모) 나머지
    AX reg, mem 8 AL AH
    DX : AX reg, mem 16 AX DX
    EDX : EAX reg, mem 32 EAX EDX

     * 나눗셈을 할 때는 메모리에 저장되는 방식이 리틀 앤디안인걸 잊지 말아야 합니다.

     

    4. IDIV

     IDIV는 signed 수들에 해당하는 나눗셈입니다. 부호가 있는 수들을 나눠야 하다 보니 분자를 확당할때 최상위 비트가 부호에 해당하는 비트가 되어야 합니다. 때문에 DIV와 다르게 아래 3가지 명령어가 사용됩니다.

    CBW CWD CDQ
    byte의 수를 word 크기로 word크기 수를 double word로 double word수를 quard word 수로

     위 명령어를 사용하면 분자에 해당하는 값을 자동으로 확장을 해 줍니다.

    mov al, 8Fh
    cbw			; ax : FF 8Fh

     IDIV연산에서 나머지의 부호는 항상 분모와 같습니다.

     

    * 나눗셈에서 주의해야 할 건 Overflow입니다. 분모가 너무 작을 경우 몫을 담을 레지스터가 값을 다 담지 못하는 경우가 있어 이걸 주의해서 연산을 해야 합니다.

     - 이걸 개선할 방법중 하나는 값들을 크기가 더 큰 상위 레지스터에 넣어 나눗셈을 하는 것입니다. 물론 완벽하게 막아주는건 아니고 확률을 줄여줄 뿐입니다.

     

    * 0 으로 나누는경우가 없어야 합니다.

     - 나눗셈을 하기 전 0과 cmp를 해서 je 등을 이용해 예외처리를 해주면 됩니다.

     

     

    기본 제공 자료형을 넘어서

     지금까지 한 내용들은 2의 제곱대로 정해진 사이즈의 타입만을 사용했습니다. 그런데 이것들 외 128비트 등 더 큰 자료형이 필요할 수 있습니다.

     

    1. ADC (Add with Carry)

     위 내용에서 확장된 개념으로 기존의 ADD 에서 CF의 값을 destination에 더해주는 일을 합니다.

    ADC  (reg/mem), (reg/mem/imm)

     

    mov dl, 0
    mov al, 0FFh
    add al, 0FFh	; AL = FEh
    adc dl, 0		;DL/AL = 01FEh

     이런식으로 사용하게 되면 연산 결과에 따라 Carry가 난 1 비트를 잡아 연산 결과에 넣어줄 수 있습니다.

     

    2. SBB (Substract with Borrow)

     빼기에서 Carry Flag를 이용하는 연산입니다. 기존의 빼기를 수행한 후 CF의 값을 빼줍니다.

    mov edx, 7
    mov eax, 1	; edx : eax = 00000007 : 00000001
    sub eax, 2
    sbb edx, 0	; edx : eax = 00000006 : FFFFFFFF

     

    반응형

    댓글

Designed by Tistory.