어셈블러? -> 번역기
[레지스터 기초]
주로 a,b,c,d를 자주 사용함
rax(64bit)-eax(32bit)-ax(16bit)-ah/al(8bit)
하위비트 범위의 값을 바꿔도 상위비트의 값엔 영향을 미치지 않는다.
[변수와 레지스터]
변수는 그냥 데이터를 저장하는 바구니
data 섹션: 초기화 된 데이터. db(1바이트)-dw(2바이트)-dd(4바이트)-dq(8바이트)
bss 섹션: 초기화 안 된 데이터. resb-resw-resd-resq
mov를 통해 데이터를 레지스터에 넣는것은 값이 아닌 주소를 넣는 것이다.
mov rax, a : a라는 바구니의 주소값을 rax에 복사
mov rax, [a] : a리는 바구니 안의 값을 rax에 복사
[문자와 엔디안]
저장된 데이터는 변하지 않는데 어떻게 분석하느냐의 차이이다.
리틀 엔디안: 숫자가 바이트 단위로 역순으로 저장(ex: 0x12345678 -> 0x78, 0x56, 0x34, 0x12)
장점 - 캐스팅에 유리함
빅 엔디안: 숫자가 정순으로 저장(ex: 0x12345678 -> 0x12, 0x34, 0x56, 0x78)
장점 - 숫자 비교에 유리함
우리가 사용하는 시스템은 보통 리틀 엔디안 방식이다.
[사칙 연산]
add a, b : a = a + b
a는 레지스터 or 메모리 / b는 레지스터 or 메모리 or 상수
a, b 모두 메모리는 안됨
곱셈과 나눗셈은 지정된 레지스터를 사용하고 조금 까다롭다
[시프트 연산과 논리 연산]
산술 시프트: 최상위 부호 비트는 유지된다
시프트로 밀려난 비트는 분실된다
시프트 연산은 왜 하는걸까?
곱셈, 나눗셈이 빠름
ex) 게임서버에서 ObjectID를 만들 때
응용 사례
bitflag: 예를들어 플레이어의 상태를 비트마다 의미를 부여함. and연산으로 해당 비트가 1인지 0인지 판단할 때 쓸 수 있음
동일한 값으로 xor을 두번 하면 원래 값으로 돌아오게 됨. 암호학에서 유용함. (value xor key)
자기 자신을 xor 하면 0이 나옴
[분기문]
특정 조건에 따라서 코드 흐름을 제어하는 것
cmp와 jmp를 이용한다.
; 분기문 (if)
; CMP dst, src (dst 기준)
; 비교를 한 결과물을 flag register 저장
; JMP [label] 시리즈
; 두 숫자가 같으면 1, 아니면 0을 출력하는 프로그램
mov rax, 10
mov rbx, 10
cmp rax, rbx
je LABEL_EQUAL
mov rcx, 0
jmp LABEL_EQUAL_END
LABEL_EQUAL:
mov rcx, 1
LABEL_EQUAL_END:
PRINT_HEX 1, rcx
NEWLINE
mov ax, 100
mov bl, 2
div bl
cmp ah, 0
je L1
mov rcx, 0
jmp L2
L1:
mov rcx, 1
L2:
PRINT_HEX 1, rcx
NEWLINE
[반복문]
cmp, jmp를 이용해서 구현할 수도 있고 loop를 사용할 수도 있다
mov ecx, 10
LABEL_LOOP:
PRINT_STRING msg
NEWLINE
dec ecx ; sub ecx, 1과 동일
cmp ecx, 0
jne LABEL_LOOP
; 연습 문제) 1에서 100까지의 합을 구하는 프로그램
mov ecx, 0
mov eax, 0
LABEL_LOOP2:
inc ecx
add eax, ecx
cmp ecx, 100
jne LABEL_LOOP2
PRINT_DEC 4, eax
NEWLINE
; loop [라벨]
; - ecx에 반복 횟수
; - loop 할 때 마다 ecx 1 감소. 0이면 빠져나감. 아니면 라벨로 이동
mov ecx, 100
xor ebx, ebx
LABEL_LOOP_SUM:
add ebx, ecx
loop LABEL_LOOP_SUM
PRINT_DEC 4, ebx
NEWLINE
[배열과 주소]
; 배열과 주소
; 배열: 동일한 타입의 데이터 묶음
; - 배열을 구성하는 각 값을 배열 요소(element)라고 함
; - 배열의 위치를 가리키는 숫자를 인덱스(index)라고 함
; 주소
; [시작 주소 + 인덱스 * 크기]
mov rax, a
; 연습문제: a배열의 모든 데이터 출력해보기
xor ecx, ecx
LABEL_PRINT_A:
;PRINT_HEX 1, [a+ecx]
;NEWLINE
inc ecx
cmp ecx, 5
jne LABEL_PRINT_A
xor ecx, ecx
LABEL_PRINT_B:
PRINT_HEX 2, [b+ecx*2]
NEWLINE
inc ecx
cmp ecx, 5
jne LABEL_PRINT_B
xor rax, rax
ret
section .data
msg db 'Hello World', 0x00
a db 0x01, 0x02, 0x03, 0x04, 0x05 ; 5*1 = 5바이트
b times 5 dw 1 ; 5*2 = 10바이트
[함수 기초]
; 함수 (프로시저 procedure 서브루틴 subroutine)
;call PRINT_MSG
mov eax, 10
mov ebx, 15
call MAX
PRINT_DEC 4, ecx
NEWLINE
xor rax, rax
ret
PRINT_MSG:
PRINT_STRING msg
NEWLINE
ret
; ex) 두 값중 더 큰 값을 반환하는 max
; 근데 두 값을 어떻게 넘겨받지? 반환은 어떻게?
; eax와 ebx 입력값을 ecx에 반환하면 어떨까
MAX:
cmp eax, ebx
jg L1
mov ecx, ebx
jmp L2
L1:
mov ecx, eax
L2:
ret
; 만약 인자가 10개라면 어떻게 할까?
; eax, ebx에 이미 중요한 값이 있으면 어떻게 할까?
; .data .bss 사용하면?
; 인자를 도대체 몇개를 할당해야 하지?
; 다른 메모리 구조가 필요하다
; - 꿈이 유효한 동안에는 그 꿈을 유지시켜야 함(유효 범위의 개념)
; - 꿈이 끝나면 그 꿈을 부셔버려도 됨 (정리의 개념)
; - 꿈에서도 또 꿈을 꿀 수 있다는 것을 고려해야 한다 (유동적으로 유효 범위가 확장 가능)
; [!] 스택(stack)이라는 메모리 영역을 사용
; 함수가 사용하는 일종의 메모장
; - 매개 변수 전달
; - 돌아갈 주소 관리
section .data
msg db 'Hello World', 0x00
[스택 메모리]
스택은 높은 주소에서 낮은 주소로 사용함.
call - ret 으로 실행 위치로 돌아갈 수 있는 이유는 스택에 다음에 실행해야 할 위치를 스택에 저장하기 때문이다.
BP는 SP를 잠시 고정시켜서 상대 주소를 계산하는데 유용하다.
BP를 관리하는 일련의 과정을 스택 프레임이라고 한다.
스택은 사용했으면 깔끔하게 정리도 해야 한다. 임의 조작으로 스택 위치가 어긋나면 크래시가 난다.
; 스택 메모리, 스택 프레임
; 레지스터는 다양한 용도로 사용
; -- ip (instruction pointer) : 다음 수행 명령어의 위치
; -- sp (stack pointer) : 현재 스택 top 위치 (일종의 cursor)
; -- bp (base pointer) : 스택 상대주소 계산용
push rax
push rbx
push 5
push 2
call MAX
PRINT_DEC 8, rax
NEWLINE
add rsp, 16 ; push를 두번 했으므로 8+8을 더해줘서 sp의 위치를 돌려놓는
pop rbx
pop rax
xor rax, rax
ret
MAX:
push rbp
mov rbp, rsp
mov rax, [rbp+16]
mov rbx, [rbp+24]
cmp rax, rbx
jg L1
mov rax, rbx
L1:
pop rbp
ret
'C++ > Rookiss C++' 카테고리의 다른 글
[포인터] #2/2 (0) | 2022.08.27 |
---|---|
[포인터] #1/2 (0) | 2022.08.26 |
[함수] (0) | 2022.08.26 |
[코드의 흐름 제어] (0) | 2022.08.26 |
[데이터 갖고 놀기] (0) | 2022.08.25 |