본문 바로가기
프로그래밍/SYSTEM HACKING

[SYSTEM HACKING] 어셈블리 명령어( shift 연산<shl, shr, sal, sar>, 논리연산<and, or, not>, 형변환<movzx, movsx>)

by B T Y 2017. 10. 21.
반응형

어셈블리 명령어( shift 연산<shl, shr, sal, sar>, 논리연산<and, or, not>, 형변환<movzx, movsx>)에 대해서 정리한다.



1. 컴파일 과정


2. 실행중인 프로세스의 메모리 구조


3. 어셈블리 프로그래밍: 사칙연산


---------------------------------------------



1. 비트연산: shift 연산


1). 부호가 없는 연산 ( Logical Shift )


  - 왼쪽으로 이동: shl

  - 오른쪽으로 이동: shr


ex).


  eax = 4;

  eax << 1 <- 8 ( 4 * 2 )

  eax << 2 <- 16 ( 8 * 2 )

  eax << 3 <- 32 ( 16 * 2 )


  -> 4 * 2 * 2 * 2 ...


  0000 0100

  0000 1000 <-- 왼쪽으로 한 칸 이동한 경우

  0001 0000 <-- 왼쪽으로 한 칸 이동한 경우


    * 부호가 없는 연산은 시프트 연산을 했을때 해당 메모리의 범위가 넘어가면 값을 잃어버리게 된다.




( shl을 이용해서 4의 비트인 0100을 왼쪽으로 두 번 shift 연산을 한다.. 

그러면 비트가 왼쪽으로 2번 밀려서 01 0000이 되는걸 알 수 있다... )



( 왼쪽으로 shift 연산을 한번씩 할 때마다 정수 값은 해당 값의 * 2한 결과가 나오게 된다... )


* 반대로 오른쪽으로 shift 연산을 한번씩 할 때마다 정수 값의 / 2 한 결과가 나오게 된다.



( eax의 경우 32bit 크기인데 4의 비트 값인 0010에서 왼쪽으로 30번 shift를 하게 되면 32bit의 범위를

넘어가기 때문에 값에 대한 손실이 일어나게 된다... )








( unsigned shift 연산을 이용해서 오른쪽으로 비트를 밀어주게 되면 

상위 비트의 값은 0으로 채워지게 되므로 부호비트에 0이 채워지면서 -4가 아닌

의도하지 않은 다른 값이 나오게 된다... )



( usigned shift 연산 shr을 사용해서 오른쪽으로 shift 연산하게 되면 상위 비트를 무조건 0으로 채워주기 때문에

음수의 값이 였다면 비트 부호가 0으로 채워져 전혀 다른 값이 나오게 된다... )



( 피제수 ax를 제수 dl로 나누게 되면 몫은 al, 나머지는 ah에 들어가게 된다...

그러므로 나머지를 가져오려면 상위 8bit에 있는 값을 오른쪽으로 8번 shift 연산을 해서

가져와야 정상적인 나머지 값을 가져올 수 있게 된다.. )



( 10을 4로 나누게 되면 몫과 나머지 모두 2가 나오게 되는데 이 경우 몫이 들어가는 al에 0000 0010이 들어가고

나머지가 들어가는 ah에는 0000 0010이 들어가 ax에는 0000 0010 0000 0010이라는 값이 들어가게 된다...

ax에 들어있는 값을 보면 나머지를 구하려면 오른쪽으로 8bit 만큼 비트를 밀어주면 

정상적인 값을 구할수 있다는걸 알 수 있다.. )





2). 부호가 있는 연산( Alethmetic Shift - MSB를 유지 )


  - 왼쪽으로 이동: sal

  - 오른쪽으로 이동: sar


ex). 


  eax = -4

  eax << 2

  


  -4의 보수: 0000 0100 -> 1111 1011 ( 비트를 반전 시킨다 ) -> 1111 1100 ( 2의 보수 )

  

        1111 1100


 11   1111 0000


        0011 1100



  eax = 0


  0의 보수: 0000 0000 -> 1111 1111 -> 1 0000 0000




    * 1의 보수는 비트를 반전시켜주고 2의 보수는 1의 보수에 비트에 +1을 해준다.




( signed shift 연산인 sar을 이용해서 왼쪽으로 shift 연산을 두번 한 값을 다시 오른쪽으로 비트를 밀어주게 되면

signed shift 연산이기 때문에 상위비트를 부호 비트로 채우면서 부호 비트가 그대로 유지된다.. )



( unsigned shift 연산 shr을 이용해서 오른쪽으로 2번 shift 연산을 했을때는 부호 비트가 0으로 채워지면서

전혀 다른 값이 나왔지만 여기서는 부호 비트로 상위 비트를 채워주기 때문에 -4가 그대로 나오게 된다.. )




2. 논리연산: and, or, not


     * 논리적으로 비트를 다루는 연산( 근본적으로는 비트연산과 차이가 없다 )


1). and


  1 and 1 = 1

  1 and 0 = 0

  0 and 1 = 0

  0 and 0 = 0


       * and 연산은 bit를 다룰때 특정 비트의 값을 추출하는 masking을 하는 경우에 많이 사용된다.



( 여기서는 and 명령 부분은 주석 처리 해놓고 우선 피제수 10을 제수 4로 나눈 결과를

출력해봤다... eax를 출력하게 되면 나머지와 ah와 몫 al의 값이 같이 출력되어

전혀 다른 값이 출력 될 것이다... )




( 여기서는 and 연산을 이용해서 하위 8비트를 모두 1로 준 값으로 ax에 대한 결과와 and 연산을 했다...

이렇게 되면 and 연산이 일어나면서 하위 8bit를 제외한 모든 bit를 0으로 만들고

하위 8비트에 있던 값만 남게 되므로 몫에 대한 값을 구할 수 있다.. )






( 어셈블리에서는 비트를 지원해준다는 점을 이용해서 and 연산을 할때 위와 같이

비트 값을 그대로 넣어서 and 연산을 진행해봤다...

비트를 이용한 방법이 처리 속도 측면에서도 더 빠르다고 한다.. )




2). or


  1 or 1 = 1

  1 or 0 = 1

  0 or 1 = 1

  0 or 0 = 0



2-1). xor


  1 xor 1 = 0

  1 xor 0 = 1

  0 xor 1 = 1

  0 xor 0 = 0




( xor 연산은 서로 비트가 다른 경우에만 1이 되고 서로 같은 경우에는 0이 된다는 특성이 있는데

이러한 특성을 이용하게 되면 레지스터 사용전에 초기화 시키는 경우에 위와 같은 방식으로도 사용할 수 있다.. )



( xor 연산을 이용해서 eax 레지스터 값이 제대로 초기화 되었기 떄문에

eax에 15라는 값을 mov로 할당 했을때 15라는 값이 그대로 나오는걸 확인 할 수 있다... )



3). not( 어셈블리 명령: neg )


not 1 = 0

not 0 = 1




( 어셈블리에서 not 연산을 담당하는 명령어는 neg라는 명령어이고 not이라는 명령어는 따로 존재하지 않는다..

그리고 컴퓨터는 양수를 음수로 변환할때 2의 보수를 취하기 때문에 해당 비트를 

모두 반전시킨뒤에 그 값에 1을 더해주게 된다.. )





3. 형변환


1). 작은 -> 큰


  - movzx( zero extend )

  - movsx( sign extend )




( 8비트 크기 레지스터에 할당된 값을 32비트 레지스터에 할당 하려고 하는데

이 경우에는 컴파일 과정에서 에러가 난다...

이유는 크기가 작은 곳에서 큰 곳으로 이동할 때 컴퓨터는 확장된 비트를 0으로 채워야 할지 1로 채워야할지

모르기 때문에 컴파일 과정시에 에러가 나게 된다.. )



( 형을 확장 해줄때는 movsx나 movzx 명령어를 이용해서 확장된 비트를 0으로 채우거나

부호 비트로 채워준다.. )









2). 큰 -> 작은


  - eax -> ax -> al


      * -> 작은 크기로 이동하는 상황은 특정 명령어가 존재하지 않고 메모리나 레지스터의 

       해당 크기를 줄여서 그 크기 만큼의 값만 가지고 온다.

       대신에 이 경우에는 상위 비트의 값을 잃게 되므로 값의 손실이 생길 수 있다!!!




[실습]


  - 사칙연산 프로그램 작성

  - 입력 최대크기는 32bit이다.



extern printf

extern scanf


section .data

prompt_output:        db        "%d", 10, 00

prompt_output2:      db        "edx: %d eax: %d", 10, 00

prompt_input:          db        "%d %d", 00


section .bss

num1:        resd        1

num2:        resd        1


section . text

global main


main:

push    num2

push    num1

push    prompt_input

call      scanf


xor      eax,    eax

xor      edx,    edx

mov    eax,    dword [num1]

mov    edx,    dword [num2]

add     eax,    edx


push    eax

push    prompt_output

call      printf


xor     eax,    eax

xor     edx,    edx

mov   eax,    dword [num1]

mov   edx,    dword [num2]

sub    eax,    edx


push   eax

push   prompt_output

call     printf


xor    eax,    eax

xor    edx,    edx

mov   eax,    dword [num1]

mov   ebx,    dword [num2]

mul    edx


push   eax

push   edx

push   prompt_output2

call     printf


xor    eax,    eax

xor    ebx,    ebx

mov   eax,   dword [num1]

mov   ebx,   dword [num2]

div     ebx


push   edx

push   eax

push   prompt_output2

call     printf




반응형

댓글