드림핵에서 수강한 내용에 추가해서 문서화했다.
1. 컴퓨터 구조
컴퓨터 기능에 대한 설계(폰 노이만 구조, 하버드 구조, 수정된 하버드 구조)+(CPU가 처리해야 하는)명령어 집합 구조(ARM, MIPS, x86, x86-64)+ 마이크로 아키텍처(CPU의 하드웨어적 설계)+기타 하드웨어 및 컴퓨팅 방법 설계(직접 메모리 접근)
연산+제어+저장
연산과 제어는 CPU: 산술논리장치+제어장치+레지스터
저장은 기억장치(memory)가: 주기억장치(RAM) 혹은 보조기억장치(하드 드라이브)
데이터 및 신호 교환은 버스(bus): 데이터 버스, 주소 버스, 제어 버스(읽쓰 제어)

CPU의 구성
- ALU: 연산장치
- CU: 메모리에서 기계어 코드 읽고 실행
- 레지스터: CPU의 데이터 저장소
CPU의 동작
- CU에서 명령어 처리
- ALU에서 연산
- 데이터는 레지스터에 저장
- 외부 통신은 bus 사용
2. 레지스터
범용 레지스터, 세그먼트 레지스터, 명령어 포인터 레지스터, 플래그 레지스터 존재
레지스터 간에는 위와 같이 호환이 가능하다. x64의 경우에는 8 바이트, x32의 경우에는 4 바이트이다(그림에 ax는 2 바이트, ah al는 1바이트)
1. 범용 레지스터
8바이트까지 저장, unsigned 정수 2^64-1
| x64 | x32 | |
|---|---|---|
| rax (accumulator) | eax | 함수의 반환값 |
| rbx (base) | ebx | |
| rcx (counter) | ecx | 반복문의 반복 횟수, 연산 시행 횟수 |
| rdx (data) | edx | |
| rsi (source index) | esi | 데이터 옮길 때 원본 |
| rdi (destination index) | edi | 데이터 옮길 때 목적지 |
| rsp (stack pointer) | esp | 사용중인 스택 위치 |
| rbp (stack base pointer) | ebp | 스택의 바닥 |
2. 세그먼트 레지스터
cs, ss, ds, es, fs, gs 각각 16바이트 크기
cs(코드), ds(데이터), ss(스택)
3. 명령어 포인터 레지스터
CPU가 실행할 부분을 가리키는 레지스터: rip
8바이트 크기
4. 플래그 레지스터
프로세서 현재 상태 저장하는 레지스터
x64 아키텍처에서 RFLAGS라는 64비트 레지스터 존재 (32비트는 EFLAGS)
| 플래그 | |
|---|---|
| CF (Carry) | 부호 없는 수의 연산 결과 > 비트 범위 |
| ZF (Zero) | 연산 결과 0 |
| SF (Sign) | 연산 결과 음수 |
| OF (Overflow) | 부호 있는 수 연산 결과 > 비트 범위 |
3. 어셈블리어
x64 어셈블리 언어
기본 구조: 명령어(opcode) 연산자(operand)
연산자로 올 수 있는 것
- 상수
- 레지스터
- 메모리
크기 지정자+[] 형식: []의 데이터를 크기 지정자만큼 참조
참고: [rbx+8]이면 rbx부터 8바이트가 아니고 rbs+8부터 참조
명령어로 올 수 있는 것
1. 데이터 이동(Data Transfer)
mov dst, src : src 값을 dst에 대입
lea dst, src : src의 유효 주소를 dst에 저장
2. 산술 연산(Arithmetic)
add dst, src : dst에 src 더함
sub dst, src : dst에 src 뺌
inc op : op의 값 1 증가
dec op : op의 값 1 감소
mul dst, src:
div dst, src
3. 논리 연산(Logical)
and dst, src : and 연산, f는 그대로 0은 0
or dst, src : or 연산, f는 f 0은 그대로
xor dst, src : xor 연산
not op : 비트 반전, neg로도 쓰임
4. 비교 (Comparison)
피연산자 값 비교 후 플래그 설정
cmp op1, op2 : 두 피연산자를 빼서 비교
| 비교 | 플래그 레지스터.. |
|---|---|
| op1이 더 크다면 | AF=1 |
| op2와 같다면 | PF=1, AF=0, ZF=1 |
| op2가 더 크다면 | CF=1, PF=0, AF=1, ZF=0, SF=1 |
test op1, op2 : 두 피연산자를 AND연산해서 비교
5. 분기(Branch)
rip 이동시킴, 다양함
jmp addr : addr로 점프
je addr : 직전 비교한 두 피연산자 같으면 점프 jump if equal
jg addr : 직전 비교한 두 연산자 중 전자가 더 크면 점프 jump if greater
ja addr : CF=0 AND ZF=0이면 분기
jz addr: ZF=1이면 분기
jb addr: CF=1이면 분기
6. 스택(Stack)
운영체제 핵심 자료구조
push val : val을 스택 최상단에 쌓음
pop reg : 스택 최상단 값 꺼내서 reg에 대입
7. 프로시져(Procedure)
C언어 함수에 대응
반복되는 연산 -> 프로시저 호출로 대체 가능
프로시저를 호출하고 반환, call 다음 명령어 주소를 스택에 저장하고 rip로 이동시킴
call addr : addr에 위치한 프로시저 호출, push return_address(call 다음 주소임) -> jmp addr
여기까지 하면 스택 rsp에 리턴 주소(call 다음 명령어 주소) 저장됨
기존 스택 프레임 저장을 위해 rbp를 push함
그리고 새로운 스택 프레임 생성… rbp를 rsp로 옮김, 공간 확장을 위해 rsp를 빼줌(아래로 자라니까)
이제 새로운 스택 프레임이 생성되었으니 지역변수 할당하고 연산 처리
leave : 스택프레임 정리, mov rsp, rbp -> pop rbp
rbp를 꺼내서 원래의 스택 프레임으로 돌아감
ret : return address로 반환, pop rip
반환 주소 꺼내서 원래의 실행 코드로 돌아감
8. 반복

스터디에서 배운 이미지를 가져왔다.
1: eax에 10 넣기
2: INC로 10+1 해주기
3: CMP로 비교.. 20이 더 큼 op2가 더 크면 CF=1
4: CF=1이면 jmp하므로 00401015로 점프
5: EAX가 20이 될 때까지 반복될 것
4. 디버거 사용법

x32dbg를 처음 열면 위와 같은 화면이 뜬다. 좌측 상단 큰 화면은 어셈블리 코드가 실행되는 주화면이다. 해당 부분에 코드가 적혀있으므로 메인창으로 사용한다.
이제 어떤 함수의 매개변수나 반환값은 호출규약에 의해 레지스터에 저장된다. 레지스터는 우측에서 확인할 수 있다. 해당 화면 스크롤을 내려보면 범용 레지스터뿐 아니라 플래그 등 모든 레지스터를 확인할 수 있다.
덤프 아래 화면은 hex 코드를 보여준다. 레지스터나 메모리 등 덤프1,2,3,4,5에서 확인할 수 있다.
우측 하단 작은 화면은 stack 메모리를 보여준다.
단축키는 아래와 같다.
| 단축키 | |
|---|---|
| F9 | 프로그램 실행 (중단점이 있다면 중단점까지) |
| Ctrl + F9 | 함수 내로 들어가서 그 함수 ret 이전까지 코드 실행 |
| F2 | 중단점 설정 |
| Ctrl + F2 | 프로그램 재시작 |
| F7 | call된 함수로 들어가서 한 줄 실행 |
| F8 | call된 함수로 들어가지 않고 한 줄 실행 |
| Enter | 코드 미리 보기 (dst로 이동) |
| Ctrl + G | 사용자가 입력한 주소로 이동 |
| Ctrl + E | 사용자가 지정한 위치에 있는 데이터 수정 |
| ; | 주석 |
| -, + | 명령어 위치(커서) 이동, 실제로 실행하지는 않음 |