5 minute read

컴퓨터 원리

코드를 작성해보자.

  • sasm을 통해 아래의 코드 HelloWorld.asm을 작성해본다.
  • 컴퓨터는 아래와 같은 영어코드를 읽는게 아니라 010101...과 같은 포맷인 기계어를 읽으므로
  • 어셈블러 혹은 컴파일러를 통해서 변환을 시켜줘야한다.
  • 컴퓨터 구조는 크게 CPU, 메모리, 하드디스크 이렇게 3개가 있다.
  • 흐름은 다음과 같다.
    • 하드디스크에서 프로그램(exe)를 꺼내와서 메모리에 파일 내용을 다 복사해서 시작, 이제 CPU와 메모리사이를 왔다갔다하면서 프로그램을 진행한다.
      %include "io64.inc"
    
      section .text
      global CMAIN
      CMAIN:
          ;write your code here
          ; 10
          PRINT_STRING msg
            
          xor rax, rax
          ret
            
      section .data
          msg db 'Hello World', 0x00
    

컴퓨터가 어떻게 데이터를 저장할까

  • 비트 : 0과 1 값만 가질수 있는 측정단위
  • 바이트 : 여덟개의 비트로 구성된 데이터의 양을 나타내는 단위
  • 8bit = 1byte
  • 16byte = 2byte = 1 word
  • 32bit = 4 byte = 2 word = 1 dword
  • 8 byte = 4 word = 1 qword

컴퓨터는 숫자를 어떻게 표현할 까

  • 바이트로 따지면
  • 0000 0011 : 3 과 같다. 윈도우에서 프로그래머계산기를 사용하면 숫자계산하기편리하다
  • 그렇다면 음수는?
    • 2의 보수
    • 최상위 비트는 음의 부호를 나타낸다.
    • 0111 1111은 가장 최상위 숫자인 127을 의미한다.
    • 그렇다면 거기서 한차례 증가시킨 1111 1111-1을 의미한다.
    • 그렇다면 양의 바이트 나열 (0010 1011, 43)에서 최상위 비트를 1로 바꾼값 1010 101143에서 128을 뺀 값인 -85가 된다.
    • 음수로 만들고 싶으면 0010 1011에서 모든 비트를 뒤집는다. (1101 0100) 에서 1을 더해준다. 1101 0101-43을 의미한다.
  • 컴퓨터는 보통 이진수로 숫자를 이해한다. 이진수는 보통 숫자 앞머리에 0b를 붙인다.
  • 16진수는 0x 사용
    • 0b100101010x로 표기해보자.
    • 1001 0101을 끊어서생각하자. 10019 01015를 의미하므로
    • 0x95가 된다.
  • 컴퓨터 친화적으로 표기하고픈데? 공간이 별로 없다? 그렇다면 16진수를 사용하면 편리하다.

레지스터

  • CPU 안에 내장되어있는 엄청 가까운 메모리
  • CPU가 중간중간에 연산한걸 어딘가에 임시적으로 저장을 해야한다. 그걸 레지스터가 한다.
  • 저장할 수 있는 용량은 한계가 있다.
  • 레지스터는 하나가 아니라 여러개 세트로 있다. 보통 자주 사용하는건 EAX, EBX, ECX, EDX 이다.
  • 얘를 또 어떻게 쪼개서 사용할 건지 지정을 해줘야한다.
  • 레지스터의 가장 큰 크기는 기본적으로 64비트이다.
  • 하나를 다 사용하면 rax라고 하고, 절반만(32비트) 사용하면 eax, 또 절반은 ax , ah al 로 명명된다. 그러니까 rbx의 절반은 ebx ,.. bl로 간다는거구만..!
  • 명령어는 아래와 같다.

      ; cst 값을 reg1으로 넣어주세요.
      mov reg1, cst
      ; reg2에 있는 값을 reg1으로 복사해주세요.
      mov reg1, reg2
    
  • 아래의 코드를 빌드해보면 오류가 발생한다. 이유가 무엇일까?

      %include "io64.inc"
    
      section .text
      global CMAIN
      CMAIN:
          ; rax에서 하위 32비트만 사용하겠다. 
          mov eax, 0x1234
          ; rbx 64비트를 통으로 사용하겠다.
          mov rbx, 0x12345678
          ; rcx에서 최하단 1바이트만 사용하겠다.
          mov cl, 0xfffffffff
          xor rax, rax
          ret
    
  • cl 은 최하단 1바이트만 사용하는데 넣는 값이 이를 초과했기 때문이다.

  • 아래와 같이 코드를 짠다면 rax에 어떤게 들어갈까?

      %include "io64.inc"
    
      section .text
      global CMAIN
      CMAIN:
          mov rbp, rsp; for correct debugging
    
          mov eax, 0x1234
    
          mov rbx, 0x12345678
    
          mov cl, 0xff
            
          mov al, 0x00
          mov rax, rdx
            
            
          xor rax, rax
          ret
    
  • rax에는 0x1234 였다가 mov al, 0x00 이후에 1byte(8bit)를 00으로 밀어버려서 0x1200으로 바뀐다.

문자와 엔디안

  • 16진수 두글자가 1 byte 의미

  • 변수 선언 및 초기화
    • 초기화 된 데이터 양식 : [변수이름] [크기] [초기값]
      • 크기 db 1 dw 2 dd 4 dq 8
    • 초기화 되지 않은 데이터 양식 : [변수이름] [크기] [개수]
      • 크기 resb 1 resw 2 resd 4 resq 8
    • a 메모리 내용을 확인해보면 0x110x220x220x33...으로 a는 시작 위치를 가리키키는 것으로 보인다.
      section .data 
          msg db 'Hello World', 0x00
          a db 0x11 ; 변수설정
          b dw 0x2222
          c dd 0x33333333
          d dq 0x4444444444444444
            
    
  • 메모리에 데이터가 올라갈때, 메모리에는 구분할 수 있도록 주소가 있다.

      mov rax, a ; a 라는 바구니의 주소값을 rax에 복사
      mov rax, [a] ; a 라는 바구니 안에든 값을 rax 에 복사
    
      mov [a], byte 0x55 ; a 라는 바구니의 내용물에 0x55 복사
    
    
  • 같은 데이터를 어떻게 분석하냐에따라서 해석 방법이 다르다.
  • 아래의 코드에서 msg의 출력문은 Hello World이지만, a의 는 아스키 코드가 있는 부분까지는 기호로 나와서 ""3333DDDDDDDD나온다.
    • 'Hello World'은 16진수 두글자가 한 문자를 표시하므로 22개의 16진수의 나열이다. msg2 db 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64 , 0x00 와 같은 의미이다.
    • 그러면 일단왜 0x11의 아스키값인  만 나온게 아니라 그 이후까지 나온걸까?
    • 0x00은 아스키 코드로 null 값으로 문자열의 끝을 표현한다. msg db 'Hello World', 0x00'Hello World'만 출력하는 이유이다.
  • 바이트 단위로 분석하면 아무 의미 없지만, 아스키 코드로 분석하면 의미가 생성되는 매직!

      ; 출력 결과 : Hello World""3333DDDDDDDD
        
          PRINT_STRING msg2 
          PRINT_STRING a
    
            
            
          xor rax, rax
          ret
            
    
    
      section .data 
          msg db 'Hello World', 0x00
          msg2 db 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64 , 0x00
          a db 0x11 ; 변수설정
          b dw 0x2222
          c dd 0x33333333
          d dq 0x4444444444444444
    
  • 리틀 앤디안과 빅 엔디안
    • 빅 엔디안 : 메모리에 값을 집어넣은 순서대로 저장된다. 0x12345678 -> 0x12 , 0x34 , 0x56 , 0x78
      • 장점 : 숫자 비교에 유리, 가장 앞머리 숫자부터 비교를 해야하니까 리틀은 맨 끝에서부터 비교, 빅은 바로 첫 바이트부터 비교
    • 리틀 엔디안 : 거꾸로 뒤집혀서 저장 0x12345678 -> 0x78 , 0x56 , 0x34 , 0x12
      • 장점 : 캐스팅에 유리하다. (데이터의 일부만 사용), 1 바이트만 추출하고 싶어 ~ 바로 앞 첫 바이트만 꺼내면 된다. 하지만, 빅 엔디안은 끝까지 가서 마지막 바이트를 꺼내야한다.
    • 대부분의 데스크 탑은 리틀 엔디안으로 저장이 된다.

사칙 연산

  • 더하기 연산 형식 : add a, b 으로 a = a + b 와 같은 의미
    • a는 레지스터 또는 메모리
    • b는 레지스터 또는 메모리 또는 상수
    • a,b 모두 메모리는 불가
      CMAIN:
      mov rbp, rsp; for correct debugging
        
      GET_DEC 1, al
      GET_DEC 1, num
        
      add al, 1
      PRINT_DEC 1, al
    
  • 곱하기 연산 형식 : mul reg
    • mul bl -> al * bl , ax 에 저장
    • mul bx -> ax * bx , dx(상위 16비트) , ax(하위 16 비트)에 저장

      ; 곱하기 연산
      mov ax, 0
      mov al, 5
      mov bl, 8
      mul bl
          
      PRINT_DEC 2, ax ; 결과값은 40
      
  • 나누기 연산
    • div reg -> ax / bl , al은 몫 ah는 나머지

      mov ax, 100
      mov bl, 3
      div bl
      PRTINT 1, al ; 33
      NEWLINE
      mov al, ah
      PRINT_DEC 1, ah ; 1
      

쉬프트 연산 및 논리 연산

  • 산술 시프트의 특징: 최상위비트(부호판단)를 제외한 나머지 비트만 시프트
  • 곱셈, 나눗셈 간편함
  • 논리 연산
    • not
    • and
    • or
    • xor
      • 동일한 값으로 xor을 두번하면 자기 자신으로 되돌아오는 특성 ( mov rax 0xor rax, rax와 같은 의미)

라벨

  • 분기 : 어떤 숫자(1~100)가 짝수면 1, 홀수면 0을 출력하는 프로그램

          CMAIN:
          mov rbp, rsp; for correct debugging
    
          ; 숫자 입력
          GET_DEC 2, ax
            
          ; 나눗셈
          mov bl, 2
          div bl
          mov al, ah
    
          ; ah와 1이 같다면 이동
          cmp ah, 1
          je LABEL_EQUAL
            
          ; 아니면 rcx에 1 저장
          mov rcx, 1
          jmp LABEL_EQUAL_END
            
      LABEL_EQUAL:
          mov rcx, 0
            
      LABEL_EQUAL_END:
          PRINT_HEX 1, rcx
          NEWLINE 
          ;PRINT_DEC 1, ah
    
          xor rax, rax
          ret
    
  • 반복

      mov ecx, 10
      LABEL_LOOP:
          PRINT_STRING msg
          NEWLINE
          dec ecx
          ; 0과 ecx 비교
          cmp ecx, 0
          ; 같지 않다면 LABEL_LOOP로 이동
          jne LABEL_LOOP
    
  • 연습 문제 : 1에서 100까지의 합을 구하는 프로그램

          mov eax, 100
          mov ebx, 0
          mov ecx, 0
            
      LABEL_LOOP:
          add ecx, 1
          add ebx, ecx
          cmp ecx, eax
            
          jne LABEL_LOOP
            
    
          PRINT_DEC 4, ebx
          NEWLINE 
    
          xor rax, rax
          ret
    
  • loop [label] : ecx에 반복횟수, loop 할때마다 ecx 1 감소, 0이면 빠져나가고 아니면 라벨로 이동

배열과 주소

  • 배열 : 동일한 타입의 데이터 묶음
    • 배열을 구성하는 각 값을 배열 요소라고함
    • 배열의 위치를 가리키는 값을 index라고 함
  • mov rax, a : a의 주소값이 rax에 복사
  • PRINT_HEX 1, [a] : a의 값을 출력
  • 만약에 a db 0x01, 0x02, 0x03, 0x04, 0x05 와같이 배열일때 두번째 원소를 출력하고 싶으면 어떻게 해야할까? : PRINT_HEX 1, [a+1]

Categories:

Updated: