사람이 작성한 고급 언어(C)의 소스코드를 GCC C 컴파일러는 어셈블리 코드 로 만들고, 어셈블러와 링커를 통해 기계어 코드를 생성한다.

고급 언어의 높은 수준의 추상화로 인하여, 밑단에서 일어나는 동작들을 알 수 없다.
그래서 개발자들은 어셈블리어를 이해하고 활용해야하는데, 그 이유는 다음과 같다
- 최적화 성능 확인 및 튜닝 : 작성한 코드의 컴파일러를 통해 어셈블리 코드를 확인하고, 컴파일러의 성능 및 소스코드의 효율성 분석 및 소스코드의 변경을 통한 성능 극대화
- 보안 취약점 분석 : 런타임 제어 정보 저장방식 약점을 활용한 버퍼 오버플로우 같은 공격에 대한 보호
3.2 프로그램의 인코딩
linux> gcc -Og -o p p1.c p2.c
코드 설명
- -Og : 최적화 수준 지정 (학습목적 상 더 높은 최적화 적용시, 코드 구조의 변경 우려)
- -O0, -O1, -O2, -O3, -Og:
- -O0: 최적화를 전혀 하지 않음. 디버깅에 유리함.
- -O1: 기본적인 최적화 적용, 간단한 인라이닝, 상수 전파, 불필요한 코드 제거 등 수행함.
- -O2: -O1의 최적화에 추가로 루프 언롤링, 더욱 공격적인 인라이닝 등 대부분의 최적화 기법들을 적용함.
- -O3: -O2의 최적화에 더해, 벡터화와 같은 고급 최적화를 수행함.
- -Og: 디버깅에 적합한 최적화 옵션으로, 디버깅 정보를 잘 유지하면서 어느 정도 최적화를 적용함.
- -O0, -O1, -O2, -O3, -Og:
- -o p : p1.c, p2.c를 컴파일 하여 생성될 실행파일의 명칭을 p로 지정
실행 결과

3.2.1 기계수준 코드
컴파일러는 추상화된 언어로 표현된 프로그램을, 프로세서가 실행하는 매우 기초적인 명령어(인스트럭션)들로 변환
명령어는 ISA(Instruction set architecture)에 의해 정의
- ISA : 프로세서 상태, 명령어 형식, 명령어 실행에 따른 프로세서 상태변화 등에 대한 정의 (명세서)
- 기본적인 명령어 필드 형태

- CPU 제조사마다 설계가 다름

x86-64 명령어 포맷(가변 길이 명령어 형식)
- Prefix (옵션): 세그먼트, 락(LOCK), operand-size, REX 등 다양한 목적의 프리픽스가 붙을 수 있음
- Opcode: 연산 종류 지정
- ModR/M / SIB: 레지스터·메모리 오퍼랜드 지정 방식, 주소 계산 시 Base, Index, Scale 정보 포함
- Displacement: 메모리 주소 계산 시 사용되는 오프셋
- Immediate: 즉시값(상수) 지정
- → 복잡하고 유연한 인코딩 구조로, x86 호환성과 다양한 기능(세그먼트, 64비트 확장 등)을 지원하기 위해 계속 확장됨

ARM 명령어 포맷(고정 길이 명령어 형식)
- 32비트(또는 16비트 Thumb 모드 등) 고정 길이 명령어.
- Cond: 조건 코드(조건부 실행)
- Immdediate Operand : 명령어에 상수값을 직접 입력
- Opcode: 연산 종류
- Set condition code(S 비트) : 플래그로, 이 명령어 실행 결과에 따라 조건 플래그를 업데이트. 이후의 조건부 분기 명령어들이 실행 여부를 결정하는데 사용
- Rn, Rd: 원본 레지스터, 결과 레지스터 등
- Operand2: 즉시값이거나 다른 레지스터 + 쉬프트·로테이트 등으로 구성.
- → RISC 원칙(고정 길이, 단순 디코딩)을 따르므로, 디코더가 비트 필드를 일정 위치에서 바로 해석할 수 있음.
- 요소 (참고 https://twojun-space.tistory.com/99)
- 연산코드 (operation code; opcode)
어떤 작업을 수행할지 결정하는 코드- 매우 기초적인 연산만 수행
- 레지스터의 두 숫자를 더하는 등 수학적 연산
- 메모리와 레지스터 간 데이터를 전송
- 조건부 분기를 통해 새로운 명령어 주소로 이동(분기처리)
- 각 opcode는 고정된 비트패턴으로 표현. 이는 CPU가 해석하여 실행
- 원천 오퍼랜드 참조(source operand reference)
- 명령어가 연산 수행 시 사용되는
데이터의 원본 위치나 값을 지정 - 값은 레지스터, 메모리주소 또는 상수일 수 있음
- 두 개의 숫자를 더하는 명령어라면, 덧셈의 첫 번째 피연산자(operand)를 의미
- 명령어가 연산 수행 시 사용되는
- 결과 오퍼랜드 참조(result operand reference)
- 연산의
결과가 저장되거나 출력되는 위치를 지정 - 보통 레지스터나 메모리 주소를 가리키며, 연산 후 결과값을 기록
- 덧셈 명령어에서 두 숫자의 합이 저장될 레지스터나 메모리 위치를 뜻함
- 연산의
- 다음 명령어 참조(next instruction reference)
- 명령어 실행 후, CPU가 다음에 실행할 명령어의 주소를 지정
- 기본적으로 모든 명령어에 명시적으로 포함되진 않고, PC(프로그램 카운터)에 의해 자동적으로 순차실행되어 다음 명령어 주소를 참조
분기나 점프명령어에서는 이 값이 수정되어프로그램의 흐름 제어를 가능케함
- 연산코드 (operation code; opcode)
기계 수준 프로그램에서는 가상주소를 메모리 주소로 사용
- 기계어는 메모리를 단순히 큰 바이트 주소 배열로 본다 (추상화)
- 프로그램은 실제 다양한 데이터 타입이나 구조체로 메모리를 다루지만, 기계어 수준에서는
메모리를 하나의 큰 연속된 배열(바이트 단위로 지정된 주소공간)으로 인식- 주소 추상화 / 공간 분리 : 각 프로세스는 가상 주소 공간을 가져, 독립실행됨. 이는 메모리 보호와 프로세스 간 격리를 가능케 함
- 메모리 보호 : 가상 주소 공간은 페이지 테이블을 통해 물리 주소에 매핑, 페이지마다 접근권한을 설정하여 실수 또는 악의적으로 다른 프로세스의 메모리 침범 불가하도록 함
- 효율적 관리 : 페이지 테이블과 MMU(메모리 관리 유닛)를 통해, 필요하지 않은 페이지는 디스크의 스왑 영역으로 이동시켜 물리 메모리를 효율적으로 관리
- 시스템 안정성 및 보안 : 메모리 접근 오류나 버퍼 오버플로우로 인한 시스템 충돌을 방지
- 프로그램은 실제 다양한 데이터 타입이나 구조체로 메모리를 다루지만, 기계어 수준에서는
프로그램 메모리 구성
- 실행 파일로 포함된
기계어 코드 운영체제에 필요한 정보- 프로시저 호출과 반환을 관리하는
런타임 스택 - malloc과 같은 함수를 통해 할당된
사용자 메모리 블록
기계 수준 프로그램의 메모리와 프로세서 (코드의 CPU 조작)
- 프로그램 카운터(PC, %rip) : 실행될
다음 명령어의 메모리 주소를 나타냄 - 정수 레지스터 파일
- 16개의 64비트 레지스터로 구성,
주소(포인터)나 정수 데이터를 저장 - 일부 레지스터는 함수의 인자, 지역 변수, 반환 값 등 중요한 데이터를 임시 저장
- 16개의 64비트 레지스터로 구성,
- 조건 코드 레지스터 : 최근 수행된 산술 또는 논리
연산의 결과 상태를 저장하며,조건부 제어 흐름(if, while 등)을 구현하는데 활용 - 벡터 레지스터 :
여러 정수 또는 부동 소수점 값을 동시 저장
3.2.2 코드 예제
// C 파일 mstore.c
long mult2(long, long);
void multstroe(long x, long y, long *dest) {
long t = mul2(x, y);
*dest = t;
}
어셈블리 코드 생성 linux> gcc -Og -S mstore.c
-S 는 소스 파일을 어셈블리 코드로 변환 후, 어셈블리 파일(.s) 생성. (어셈블러 호출 직전 종료)
; Assembly 파일 mstore.s
multstore: ;label, 함수의 시작 주소
pushq %rbx ;레지스터 %rbx의 현재 값을 스택에 저장
movq %rdx, %rbx ;%rdx 값을 %rbx로 복사
call mult2 ;mult2 함수 호출
movq %rax, (%rbx) ; %rax 값을 %rbx가 가리키는 메모리 주소에 저장
popq %rbx ; 스택에서 값을 꺼내 %rbx 레지스터에 복원
ret
- pushq %rbx: 함수 호출 시, %rbx와 같은 칼에 저장된 값이 나중에 복원되어야 할 때 사용함. (함수 내에서 %rbx를 임시 데이터 저장 용도로 사용하므로, 원래 값을 보존함)
- movq %rdx, %rbx: 이 함수에서는 세 번째 인수(dest 포인터)가 %rdx에 전달되는데, 이후에 이 포인터를 사용하기 위해 %rbx에 저장함.
- call mult2: mult2 함수에서 두 값의 곱을 계산한 후, %rax에 결과를 반환하기 위함임.
- movq %rax, (%rbx): mult2 함수가 반환한 결과(곱의 값, %rax에 있음)를 dest 포인터(%rbx가 가리킴)를 통해 저장함.
- popq %rbx: 함수 호출 전에 pushq로 저장했던 원래의 %rbx 값을 복원하여, 호출자에게 영향을 주지 않도록 함.
- ret: multstore 함수의 실행을 마치고 호출한 함수로 복귀함.
바이너리 코드 생성 linux> gcc -Og -c mstore.c
-c 소스 파일 컴파일 후, 링크 과정 생략하고 오브젝트 코드(.o)만 생성
Object 파일 mstore.o
53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3
바이너리 코드의 역 어셈블러 수행 linux> objdump -d mstore.o

역 어셈블(disassembly)를 통해 처음 어셈블리 코드와 거의 흡사하게 생성
'CS:APP' 카테고리의 다른 글
| 오퍼랜드 식별자(operand specifier 어셈블리의 시작과 끝 - CS:APP 3.4.1 (0) | 2025.04.07 |
|---|---|
| 레지스터가 뭔데 - CS:APP 3.4 (0) | 2025.04.07 |
| 암달의 법칙 (Amdahl's law) (0) | 2025.04.07 |
| 캐시와 저장장치의 상관관계 (0) | 2025.03.21 |
| 1.4 프로세서는 메모리에 저장된 인스트럭션을 읽고 해석한다 (0) | 2025.03.20 |