씨에스에이피피가 되
mstore.c
라는 C 코드 파일 작성했다고 가정
// mstore.c
long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
C 컴파일러가 생성한 어셈블리 코드 확인하고프다면 명령줄에 -s
옵션 사용
# bash
linux> gcc -Og -S mstore.c
이렇게 하면 gcc
가 컴파일만 해서 mtore.s
를 만들고 스탑한다
그렇게 나온 어셈블리 코드 파일에서 중요한 내용이다 ↓
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
들여쓰기된 각 줄은 하나의 기계어 명령어이다
이건 읽기 가능하다
예를 들어 pushq
명령은 %rbx
레지스터의 내용을 스택에 저장하라는 의미다
__
-c 옵션을 사용하면, gcc는 컴파일과 어셈블까지 수행한다
# bash
linux> gcc -Og -c mstore.c
이렇게 하면 바이너리 형식의 객체 파일 mstore.o
을 만드는데 이는 못읽는다…
53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3
이따구로 나오니 말이다
읽을 수 있다면 댓글로 알려주길 바란다
이로 얻을 수 있는 교훈
기계가 실행하는 프로그램은 단순히 명령어들을 인코딩한 바이트들의 연속이지
이 바이트들이 어떤 소스 코드에서 왔는지에 대한 정보는 거의 담겨 있지 않다
기계어 파일의 내용을 확인하고 싶다면 디스어셈블러를 사용해라
기계어같은 걸 우리가 읽을 수 있게 변환해준다
linux에선 obdjump로 가능하다고 한다
# bash
linux> objdump -d mstore.o
결과 : (줄 번호와 설명은 이해를 돕기 위해 추가했다고 한다)
Disassembly of function multstore in binary file mstore.o
1 0000000000000000 <multstore>:
2 0: 53 push %rbx
3 1: 48 89 d3 mov %rdx,%rbx
4 4: e8 00 00 00 00 callq 9 <multstore+0x9>
5 9: 48 89 03 mov %rax,(%rbx)
6 c: 5b pop %rbx
7 d: c3 retq
왼쪽엔 16진수 값이 나오고
오른쪽에 해당되는 어셈블리 명령어가 나온다
주목할 특징은
53
은 무조건 pushq %rbx
명령으로 해석됨
gcc
는 접미사 ‘q’를 붙이지만, objdump
는 대부분 생략하거나 선택적으로 붙임마지막엔 함수 추가해 실행 파일 만드는 법 나온다
대충 요약하면 작성 함수 뿐 아니라 운영체제 상호작용 및 프로그램 시작/종료 코드 때문에
파일 크기가 더 크게 나온다
실행 파일도 디스어셈블 가능하나 차이점 존재
주소값이 추가되며 명령어 호출 주소가 채워짐, 추가 명령어도 포함됨
gcc
가 생성한 어셈블리 코드는 사람이 읽기 어렵다 (진짜임)
영양가 1도 없는 정보랑 뭐하는지도 안알려주는 놈이기 때문이다
그렇기에 우린 더 좋은 버전을 쓸 것이다
지시문 같은 쓸모 없는 건 갖다 버리고 각 줄에 줄 번호와 설명 주석이 추가된 버전 말이다
예시)
// c
void multstore(long x, long y, long *dest)
x in %rdi, y in %rsi, dest in %rdx
1 multstore:
2 pushq %rbx # %rbx를 저장
3 movq %rdx, %rbx # dest를 %rbx로 복사
4 call mult2 # mult2(x, y) 호출
5 movq %rax, (%rbx) # 결과를 *dest에 저장
6 popq %rbx # %rbx 복원
7 ret # 반환
(따봉하는 개구리 짤)
이것이 일반적으로 어셈블리어 프로그래머들이 사용하는 형식이다