본문 바로가기
개인공부/Rookiss C++ 프로그래밍 입문

Chapter 1 - 어셈블리 언어 입문

by 하고싶은건많은놈 2023. 3. 29.

데이터 기초

어셈블러(asembler) : =번역기.
비트(bit - binary digit) : 0 또는 1의 두가지 값만 가질 수 있는 측정 단위
바이트(Byte) : 여덟개의 비트로 구성된 데이터의 양을 나타내는 단위

  • 음수를 나타낼 때는 2의 보수를 사용함
  • 0x를 붙인 16진수를 유용하게 사용함

8 bit = 1 byte
16 bit = 2 byte = 1 word
32 bit = 4 byte = 2 word = 1 dword(double-word)
64 bit = 8 byte = 4 word = 1 qword(quad-word)


레지스터 기초

레지스터 : 데이터를 임시적으로 저장하는 역할
CPU 내에 있기 때문에 거리가 짧아 처리속도가 매우 빠름

  • rax = 64비트 / eax = 32비트 / ax = 16비트 / ah, al = 8비트
  • mov 레지스터명, 값 : 레지스터에 값을 저장
  • mov 레지스터1, 레지스터2 : 레지스터2에 저장되어있는 값을 레지스터1에 저장

변수와 레지스터

data 영역에는 초기화 된 데이터를 사용

  • [변수이름] [크기] [초기값]
  • ex) a db 0x11, c dd 0x33333333
  • db = 1, dw = 2, dd = 4, dq = 8 (바이트기준)

bss 영역에는 초기화되지 않은 데이터를 사용

  • [변수이름] [크기] [개수]
  • ex) e resb 10, f resq 64
  • resb = 1, resw = 2, resd = 4, resq = 8 (바이트기준)

data영역과 bss영역이 나뉘어져있는 이유 : 초기화되지 않은 변수는 초기값을 저장할 필요가 없으므로 실행파일의 크기가 작아짐

mov 레지스터 변수명으로 사용시 변수의 주소값이 레지스터에 저장됨
mov 레지스터, [변수명]으로 사용시 변수 자체의 값이 레지스터에 저장됨

  • 단, 크기를 지정하지 않았을시 설정한 레지스터와 같은 크기만큼 읽어들임
  • 원하는 크기만큼 읽고싶을 때는 레지스터 크기를 조절하는 방법을 사용할 수 있음

move [변수명], 크기지정 값 또는 move [변수명], 레지스터명으로 변수에 값을 지정할 수 있음
ex) mov [a], byte 0x55, mov [a], word 0x6666, `mov [a], cl


문자와 엔디안

문자는 아스키코드를 활용해서 읽음
동일한 데이터라고 해도 어떻게 분석하고 받아들이냐에 따라 다른 의미가 될 수 있음

리틀 엔디안 : 대부분 사용, 메모리 주소가 증가함에 따라 변수가 거꾸로 저장됨

  • 캐스팅(데이터의 크기가 큰 자료형에서 작은 자료형으로 바꾸는 것)에 유리

빅 엔디안 : 변수가 메모리 주소 증가에 맞추어 순서대로 저장됨

  • 숫자 비교에 유리

사칙연산

더하기 : add a, b

  • a는 레지스터 또는 메모리
  • b는 레지스터 또는 메모리 또는 상수
  • 단, 메모리를 사용할 때는 []로 주소가 아닌 값을 받아와아하며 a와 b 모두 메모리일 수는 없음
  • 메모리에 상수를 더할 때는 더할 상수의 크기를 지정해줘야함

빼기 : sub a, b, add와 동일함

곱하기 : mul reg

  • 곱해지는 레지스터의 크기에 따라 작동이 달라짐
  • mul bl = al * bl, 연산 결과는 ax에 저장됨
  • mul bx = ax * bx, 연산 결과는 상위 16비트가 dx, 하위 16비트가 ax에 저장됨

나누기 : div reg

  • 나눠지는 레지스터의 크기에 따라 작동이 달라짐
  • div bl = ax / bl, 연선 결과는 몫이 al에, 나머지가 ah에 저장됨

시프트 연산과 논리 연산

산술 시프트(shift)에서는 부호를 나타내는 최상위비트는 그대로 유지됨
shl 레지스터, 값 또는 shr 레지스터, 값의 형태로 사용
시프트 연산으로 곱셈 및 나눗셈을 간편하게 할 수 있음
게임서버에서 ObjectID를 만들 때도 유용하게 사용

논리 연산에는 not, and, or, xor이 있음

  • not A : 하나에 조건에 대해 0이면 1, 1이면 0
  • A and B : 둘다 1이면 1, 나머지는 0
  • A or B : 둘중 하나라도 1이면 1, 아니면 0
  • A xor B : 둘다 1이거나 둘다 0이면 0, 아니면 1
  • and al, bl, not al 등의 형태로 사용

비트플래그(bitflag)에 사용
xor을 두번 사용시 원래 값으로 돌아오는 특성이 있으므로 암호학에서 유용하게 사용함
xor을 자기 자신으로 사용시 모든 값이 0으로 대입되기 때문에 오류 체크 등에 사용함


분기문

특정 조건에 따라서 코드 흐름을 제어

cmp dst, src의 형태로 사용, dst가 기준
비교를 한 결과물은 Flag Register에 저장
cmp 결과에 따라 Flag Register의 각 비트가 경우에 맞게 변환되고, 아래의 jmp 등의 연산자들이 그 비트를 참조하여 조건을 판별함

jmp/je/jne/jg/jge... [label]의 형태로 이동

  • jmp : 무조건 점프
  • je : JumpEqulas, 같으면 점프
  • jne : JumpNotEquals, 다르면 점프
  • jg : JumpGreater, 크면 점프
  • jge : JumpGreaterEquals, 크거나 같으면 점프
  • ...
mov ax, 100

mov bl, 2
div bl

cmp ah, 1
je LABEL_ODD
mov rcx, 1
jmp LABEL_EVEN

LABEL_ODD:
    mov rcx, 0
LABEL_EVEN:
    PRINT_HEX 1, rcx
    NEWLINE

반복문

특정 조건을 만족할때까지 반복해서 실행

mov ecx, 10
LABEL_LOOF:
    PRINT_STRING msg
    NEWLINE
    dec ecx
    cmp ecx, 0
    jne LABEL_LOOF
mov eax, 100
xor ebx, ebx ; = mov ebx, 0
xor ecx, ecx

LABEL_SUM:
    inc ecx, 1
    add ebx, ecx
    cmp ecx, eax
    jne LABEL_SUM

PRINT_DEC 4, ebx
NEWLINE

cmpjmp를 사용하여 반복문 생성 가능

mov ecx, 100
xor ebx, ebx
LABEL_LOOP_SUM:
    add ebx, ecx
    loop LABEL_LOOP_SUM

PRINT_DEC 4, ebx
NEWLINE

loop [라벨]을 사용하여 반복문 생성 가능
해당 구문을 만나면 ecx 레지스터가 하나 줄어드는 것을 이용해 반복


배열과 주소

배열 : 동일한 타입의 데이터 묶음

  • 배열을 구성하는 각 값을 배열 요소(element)라고 함
  • 배열의 위치를 가리키는 숫자를(index)라고 함
  • a db 0x01, 0x02, 0x03, 0x04, b times 5 dw 1, num resb 10등의 형태로 사용
xor ecx, ecx

LABEL_PRINT_LOOP:
    PRINT_HEX 1, [a+ecx]
    NEWLINE
    inc ecx, 1
    cmp ecx, 5
    jne LABEL_PRINT_LOOP

a db 0x01, 0x02, 0x03, 0x04
xor ecx, ecx

LABEL_PRINT_LOOP:
    PRINT_HEX 2, [b+ecx*2]
    NEWLINE
    inc ecx, 1
    cmp ecx, 5
    jne LABEL_PRINT_LOOP

b times 5 dw 1

시작 주소를 기준으로 인덱스 * 크기만큼을 이동하는 방식이 많이 사용됨


함수 기초

어셈블리에서는 함수보다 프로시저(procedure)라는 용어를 더 많이 사용함

CMAIN:
    call PRINT_MSG
    ret

PRINT_MSG:
    PRINT_STRING msg
    NEWLINE
    ret

함수로 하나의 기능 단위를 관리할 수 있음

MAX:
    cmp eax, ebx
    jg L1
    mov ecx, ebx
    jmp L2
L1:
    mov ecx, eax
L2:
    ret

인자의 개수가 늘어난다면 위와 같은 방법으로는 구현이 힘들어짐
레지스터에 이미 중요한 값이 저장되어있었다면 문제가 생길 수 있기 때문에 데이터 영역의 변수를 활용할 수도 있으나, 여전히 영구적인 메모리를 사용하기에 낭비가 발생함
즉, 함수에는 유효 범위 / 정리 / 유동적인 유효 범위 확장의 개념이 필요하기 때문에 이를 만족시킬 수 있는 스택(stack)이라는 메모리 영역을 사용

  • 함수가 사용하는 일종의 메모장
  • 매개변수 전달, 돌아갈 주소 관리에 사용됨

스택 메모리

CODE / DATA / BSS / HEAP / STACK

스택은 높은 주소에서 시작됨
함수 호출시 매개변수 / 반환 주소값 / 지역 변수가 스택에 쌓임

포인터 레지스터

  • ip(Instruction Pointer) : 다음 수행 명령어의 위치
  • sp(Stack Pointer) : 현재 스택의 top 위치
  • bp(Base Pointer) : 스택 상대주소 계산용

push로 데이터를 스택에 저장하고, pop으로 꺼내옴
단, pop의 경우 실제로 저장되어있는 데이터를 제거하지는 않고 top을 가리키는 포인터인 sp의 위치만 조정함

mov rbp, rsp

push 1
push 2
call MAX
PRINT_DEC 8, rax
NEWLINE
add rsp, 16

MAX:
    push rbp
    mov rbp, rsp

    mov rax, [rbp+16]
    mov rbx, [rbp+24]
    cmp rax, rbx
    jg L1
    mov ra, rbx
L1:
    pop rbp
    ret

rbp에는 sp의 주소를 고정시켜 상대주소를 계산하는데 용이하게 사용
bp를 관리하는 일련의 과정을 스택 프레임이라고 함
스택 프레임에는 넘겨준 인자 뿐만 아니라 리턴주소, 이전 함수가 사용하던 스택 프레임 공간을 알기 위한 bp값 등이 있음

'개인공부 > Rookiss C++ 프로그래밍 입문' 카테고리의 다른 글

Chapter 6 - 객체지향 여행  (0) 2023.03.29
Chapter 5 - 포인터  (0) 2023.03.29
Chapter 4 - 함수  (0) 2023.03.29
Chapter 3 - 코드의 흐름 제어  (0) 2023.03.29
Chapter 2 - 데이터 갖고 놀기  (0) 2023.03.29

댓글