기계어

섬네일|WDC 65816 단일 기판 컴퓨터의 기계어 모니터가 코드의 역어셈블리된 모습 및 프로세서 레지스터, 메모리 덤프를 보여주고 있다.

기계어(機械語, )는 컴퓨터의 중앙 처리 장치(CPU)를 제어하는 데 사용되는 명령어 집합으로 구성된 컴퓨터 코드이다. 기존의 이진 컴퓨터의 경우, 기계어는 컴퓨터가 실제로 읽고 해석하는 컴퓨터 프로그램의 이진 표현이다. 기계어 프로그램은 기계어 명령어들의 시퀀스(데이터와 혼합될 수 있음)로 구성된다.

각 기계어 명령어는 CPU가 특정 작업을 수행하도록 한다. 이러한 작업의 예는 다음과 같다.
 메모리에서 워드를 CPU 레지스터로 로드
 하나 이상의 레지스터 또는 메모리 위치에서 산술 논리 장치(ALU) 연산 실행
 다음 명령어가 아닌 다른 명령어로 점프 또는 건너뛰기

일반적으로 각 아키텍처 제품군(예: X86, ARM)은 자체 명령어 집합(ISA)을 가지며, 따라서 자체적인 특정 기계어 언어를 가진다. VAX 아키텍처는 PDP-11 명령어 집합의 선택적 지원을 포함하고, IA-64 아키텍처는 IA-32 명령어 집합의 선택적 지원을 포함하며, PowerPC 615 마이크로프로세서는 파워PC와 x86 명령어 집합을 모두 기본적으로 처리할 수 있는 예외도 있다.

기계어는 엄격하게 숫자로 구성된 언어이며, 프로그래머를 위한 CPU의 가장 낮은 수준의 인터페이스이다. 어셈블리어는 숫자로 된 기계어와 사람이 읽을 수 있는 니모닉 사이에 직접적인 매핑을 제공한다. 어셈블리에서는 숫자로 된 기계어 명령 코드와 피연산자가 니모닉과 레이블로 대체된다. 예를 들어, X86 아키텍처에는 0x90 명령 코드가 있으며, 이는 어셈블리 소스 코드에서 NOP로 표현된다. 프로그램을 직접 기계어로 작성하는 것이 가능하지만, 개별 비트를 관리하고 숫자로 된 메모리 주소를 계산하는 것은 번거롭고 오류가 발생하기 쉽다. 따라서 프로그램은 직접 기계어로 거의 작성되지 않는다. 그러나 어셈블리 소스 코드를 사용할 수 없는 경우 기존 기계어 프로그램을 편집할 수 있다.

오늘날 대부분의 프로그램은 고급 언어로 작성된다. 고급 프로그램은 컴파일러에 의해 기계어로 번역될 수 있다.

명령어 집합

모든 프로세서 또는 프로세서 제품군에는 자체 명령어 집합이 있다. 기계어 명령어는 특정 동작을 지정하는 비트 패턴이다. 명령어 집합은 명령어 형식으로 설명된다. 명령어 형식이 다를 수 있는 몇 가지 방법은 다음과 같다.
 모든 명령어의 길이가 같거나 명령어 길이가 다를 수 있다.
 명령어 수가 적거나 많을 수 있다.
 명령어는 아키텍처의 워드 길이와 정렬될 수도 있고 그렇지 않을 수도 있다.

프로세서의 명령어 집합은 컴퓨터의 디지털 논리 레벨 회로를 실행해야 한다. 디지털 수준에서 프로그램은 컴퓨터의 레지스터, 버스, 메모리, ALU 및 기타 하드웨어 구성 요소를 제어해야 한다. 컴퓨터의 컴퓨터 구조 기능을 제어하기 위해 기계어 명령어가 생성된다. 기계어 명령어를 사용하여 제어되는 기능의 예:
 세그먼트 레지스터
 보호 주소 모드
 이진화 십진법(BCD) 연산

명령어 형식의 기준은 다음과 같다.
 가장 자주 사용되는 명령어는 드물게 사용되는 명령어보다 짧아야 한다.
 기본 하드웨어의 메모리 전송 속도가 메모리 페치 명령어의 유연성을 결정한다.
 주소 필드의 비트 수는 특별히 고려해야 한다.

주소 필드의 크기를 결정하는 것은 공간과 속도 사이의 선택이다. 일부 컴퓨터에서는 주소 필드의 비트 수가 모든 물리적 메모리에 접근하기에 너무 작을 수 있다. 또한 가상 주소 공간을 고려해야 한다. 또 다른 제약은 주소를 구성하는 데 사용되는 레지스터의 크기에 대한 제한일 수 있다. 더 짧은 주소 필드는 명령어가 더 빠르게 실행되도록 허용하지만, 명령어 형식을 설계할 때 다른 물리적 속성도 고려해야 한다.

명령어는 두 가지 유형으로 나눌 수 있다: 범용 및 특수 용도. 특수 용도 명령어는 컴퓨터에 고유한 아키텍처 기능을 활용한다. 범용 명령어는 모든 컴퓨터에 공통적인 아키텍처 기능을 제어한다.

범용 명령어는 다음을 제어한다:
 한 곳에서 다른 곳으로의 데이터 이동
 결과를 생성하기 위해 하나의 피연산자를 가지는 단항 연산
 결과를 생성하기 위해 두 개의 피연산자를 가지는 이항 연산
 비교 및 조건부 점프
 프로시저 호출
 루프 제어
 입출력

어셈블리어

섬네일|319x319px|어셈블리어에서 기계어로의 변환
기계어를 훨씬 더 사람이 읽기 쉽게 표현한 어셈블리어는 명령어의 숫자 값을 직접 사용하는 대신 니모닉 코드를 사용하여 기계어 명령어를 참조하고, 심볼릭 이름을 사용하여 저장 위치 및 때로는 레지스터를 참조한다. 예를 들어, 자일로그 Z80 프로세서에서 B 일반 목적 레지스터를 감소시키는 기계어 00000101은 어셈블리어로 DEC B로 표현된다.

예시
IBM 709x
IBM 704, 709, 704x 및 709x는 각 명령어 워드에 하나의 명령어를 저장한다. IBM은 비트를 왼쪽에서 S, 1, ..., 35로 번호 매긴다. 대부분의 명령어는 두 가지 형식 중 하나를 가진다.

일반 형식
S,1-11
12-13 플래그, 일부 명령어에서는 무시됨
14-17 사용되지 않음
18-20 태그
21-35 Y

TSX를 제외한 인덱스 레지스터 제어
S,1-2 명령 코드
3-17 감소
18-20 태그
21-35 Y

IBM 7094 및 7094 II를 제외한 모든 장비에는 A, B, C로 지정된 세 개의 인덱스 레지스터가 있다. 태그에 여러 1 비트가 있는 인덱싱은 선택된 인덱스 레지스터들의 논리합을 빼고, 태그에 여러 1 비트가 있는 로딩은 선택된 모든 인덱스 레지스터들을 로드한다. 7094 및 7094 II에는 7개의 인덱스 레지스터가 있지만, 전원이 켜지면 다중 태그 모드에 있어 이전 장비와 호환되는 방식으로 3개의 인덱스 레지스터만 사용하며, 나머지 4개의 인덱스 레지스터에 접근하려면 Leave Multiple Tag Mode(LMTM) 명령어가 필요하다.

유효 주소는 일반적으로 Y-C(T)이며, 여기서 C(T)는 태그가 0일 때 0이거나, 다중 태그 모드에서 선택된 인덱스 레지스터들의 논리합이거나, 다중 태그 모드가 아닐 때 선택된 인덱스 레지스터이다. 그러나 인덱스 레지스터 제어 명령어의 유효 주소는 Y에 불과하다.

두 비트가 모두 1인 플래그는 간접 주소를 선택한다. 간접 주소 워드에는 태그와 Y 필드가 모두 있다.

전송(분기) 명령어 외에도 이 장비에는 한두 워드를 조건부로 건너뛰는 스킵 명령어가 있다. 예를 들어, Compare Accumulator with Storage(CAS)는 3방향 비교를 수행하고 결과에 따라 NSI, NSI+1 또는 NSI+2로 조건부로 건너뛴다.

MIPS
MIPS 아키텍처는 명령어가 항상 32비트 길이인 기계어에 대한 특정 예를 제공한다. 명령어의 일반적인 유형은 가장 상위 6비트인 op(연산) 필드로 주어진다. J-유형(점프) 및 I-유형(즉시) 명령어는 op에 의해 완전히 지정된다. R-유형(레지스터) 명령어는 정확한 연산을 결정하기 위한 추가 필드 funct를 포함한다. 이러한 유형에서 사용되는 필드는 다음과 같다:

 6 5 5 5 5 6 비트
 [ op | rs | rt | rd |shamt| funct] R-유형
 [ op | rs | rt | 주소/즉시] I-유형
 [ op | 목표 주소 ] J-유형

rs, rt, rd는 레지스터 피연산자를 나타내고; shamt는 시프트 양을 제공하며; 주소 또는 즉시 필드는 직접 피연산자를 포함한다.

예를 들어, 레지스터 1과 2를 더하고 그 결과를 레지스터 6에 저장하는 것은 다음과 같이 인코딩된다.

 [ op | rs | rt | rd |shamt| funct]
 0 1 2 6 0 32 십진수
 000000 00001 00010 00110 00000 100000 이진수

레지스터 3에 있는 위치에서 68 셀 뒤의 메모리 셀에서 값을 레지스터 8로 로드한다.

 [ op | rs | rt | 주소/즉시]
 35 3 8 68 십진수
 100011 00011 01000 00000 00001 000100 이진수

주소 1024로 점프한다.

 [ op | 목표 주소 ]
 2 1024 십진수
 000010 00000 00000 00000 10000 000000 이진수

겹치는 명령어

가변 길이 명령어 집합을 가진 프로세서 아키텍처(예: 인텔의 X86 프로세서 제품군)에서는 크루스칼 카운트로 알려진 제어 흐름 재동기화 현상의 한계 내에서, 의도적으로 결과 코드를 두 코드 경로가 공통된 명령 코드 시퀀스 조각을 공유하도록 정렬하는 것이 때때로 가능하다. 이를 겹치는 명령어, 겹치는 명령 코드, 겹치는 코드, 겹친 코드, 명령어 분할 또는 명령어 중간으로 점프라고 한다.

1970년대와 1980년대에는 겹치는 명령어가 때때로 메모리 공간을 절약하기 위해 사용되었다. 한 예로 마이크로소프트의 알테어 베이직에서 오류 테이블 구현에 사용되었는데, 여기서 인터리빙된 명령어는 서로 명령어 바이트를 공유했다. 이 기술은 오늘날 거의 사용되지 않지만, 부트 로더의 구현과 같이 부트 섹터에 맞추어야 하는 바이트 수준에서 극단적인 크기 최적화가 필요한 영역에서는 여전히 필요할 수 있다.

또한 난독화 기술로 역어셈블리 및 변조에 대한 방지책으로 사용되기도 한다.

이 원리는 여러 명령어 집합 비호환 프로세서 플랫폼에서 실행되어야 하는 팻 바이너리의 공유 코드 시퀀스에서도 사용된다.

이 속성은 또한 기존 코드 저장소에서 의도치 않은 명령(unintended instruction)이라고 불리는 가젯(gadget)을 찾는 데 사용되며, return-to-libc 공격과 같은 익스플로잇을 위한 코드 인젝션의 대안으로 반환 지향형 프로그래밍에 사용된다.

마이크로코드와의 관계

일부 컴퓨터에서 아키텍처의 기계어는 마이크로코드라고 불리는 더 기본적인 하위 계층에 의해 구현되며, 이는 매우 다른 기본 데이터 흐름을 가진 컴퓨터의 다양한 모델 라인 또는 제품군에 걸쳐 공통적인 기계어 인터페이스를 제공한다. 이는 다른 모델 간에 기계어 프로그램을 이식하는 것을 용이하게 하기 위해 수행된다. 이러한 사용의 예로는 IBM의 System/360 컴퓨터 제품군 및 그 후속 제품이 있다.

바이트코드와의 관계
기계어는 일반적으로 바이트코드(p-코드라고도 함)와 다르며, 바이트코드는 인터프리터에 의해 실행되거나 더 빠른(직접) 실행을 위해 기계어로 컴파일된다. 예외는 자바 프로세서의 경우처럼 프로세서가 특정 바이트코드를 직접 기계어로 사용하도록 설계된 경우이다.

기계어와 어셈블리어는 언어 기능이나 라이브러리의 플랫폼 종속적인 부분을 참조할 때 때때로 네이티브 코드라고 불린다.

메모리 저장

CPU 관점에서 기계어는 RAM에 저장되지만, 성능상의 이유로 일반적으로 캐시 세트에도 유지된다. 아키텍처에 따라 명령어와 데이터를 위한 다른 캐시가 있을 수 있다.

CPU는 내부 프로그램 카운터를 기반으로 어떤 기계어를 실행해야 하는지 안다. 프로그램 카운터는 메모리 주소를 가리키며, 프로그래밍 방식의 분기를 유발할 수 있는 특별한 명령어에 따라 변경된다. 프로그램 카운터는 CPU가 처음 전원이 켜질 때 일반적으로 하드코딩된 값으로 설정되며, 따라서 해당 주소에 있는 기계어를 실행한다.

마찬가지로, 프로그램 카운터는 유효한 기계어가 아니더라도 임의의 주소에 있는 기계어를 실행하도록 설정될 수 있다. 이는 일반적으로 아키텍처별 보호 오류를 트리거한다.

CPU는 페이징 기반 시스템의 페이지 권한에 의해 현재 페이지에 실행 비트가 있는 기계어가 실제로 포함되어 있는지 여부를 통보받는다. 페이지에는 다양한 관리 기능을 위한 여러 권한 비트(읽기 가능, 쓰기 가능 등)가 있다. 예를 들어, 유닉스 계열 시스템에서는 시스템 호출로 메모리 페이지를 실행 가능하도록 전환할 수 있으며, 윈도우에서는 를 사용하여 유사한 결과를 얻을 수 있다. 실행 불가능한 페이지에서 기계어를 실행하려는 시도가 있을 경우, 일반적으로 아키텍처별 오류가 발생한다. 다양한 기술을 사용하여 데이터를 기계어로 취급하거나 기존 기계어를 사용하는 새로운 방법을 찾는 것은 일부 보안 취약점의 근간이 된다.

마찬가지로, 세그먼트 기반 시스템에서는 세그먼트 디스크립터가 세그먼트에 실행 가능한 코드가 포함될 수 있는지 여부와 해당 코드가 어떤 보호 링에서 실행될 수 있는지를 나타낼 수 있다.

프로세스 관점에서 코드 공간은 실행 중인 코드가 저장되는 가상 주소 공간의 일부이다. 다중 작업 시스템에서는 프로그램의 코드 세그먼트와 일반적으로 공유 라이브러리를 포함한다. 다중 스레딩 환경에서는 한 프로세스의 여러 스레드가 데이터 공간과 함께 코드 공간을 공유하여 프로세스 전환에 비해 문맥 교환 오버헤드를 상당히 줄인다.

사람의 가독성
기계어는 명령어를 컴퓨터가 읽을 수 있도록 하는 전기적 펄스의 집합으로 볼 수 있으며, 사람이 읽을 수 없다. 더글러스 호프스태터는 이를 DNA 분자의 원자를 조사하는 것에 비유했다. 그러나 기계어를 사람이 읽을 수 있는 소스 코드로 디코딩하는 다양한 도구와 방법이 존재한다. 그러한 방법 중 하나는 역어셈블리로, 어셈블리어가 기계어에 일대일 매핑을 형성하기 때문에 해당 어셈블리어 소스 코드로 쉽게 디코딩할 수 있다.

기계어는 두 가지 조건 하에서 고급 언어로도 디코딩될 수 있다. 첫 번째 조건은 소스 코드의 난독화된 읽기를 수용하는 것이다. 기계어가 소스 언어의 역컴파일러로 전송되면 난독화된 버전의 소스 코드가 표시된다. 두 번째 조건은 기계어에 소스 코드에 대한 정보가 인코딩되어 있어야 한다. 이 정보에는 디버그 심볼을 포함하는 심볼 테이블이 포함된다. 심볼 테이블은 실행 파일 내부에 저장되거나 별도의 파일로 존재할 수 있다. 그러면 디버거가 심볼 테이블을 읽어 프로그래머가 대화형으로 디버그하는 데 도움을 줄 수 있다.

 IBM 709, IBM 7090, IBM 7094 컴퓨터를 위한 SHARE 운영 체제(1959)는 SQUOZE라는 로드 가능한 코드 형식을 허용했다. SQUOZE는 어셈블리어 코드의 압축된 이진 형식이며 심볼 테이블을 포함했다.
 Z/OS와 같은 최신 IBM 메인프레임 운영체제는 연결된 데이터(ADATA)라는 심볼 테이블을 사용할 수 있다. 이 테이블은 IBM High-Level Assembler(HLASM), IBM의 코볼 컴파일러, 및 IBM의 PL/I 컴파일러에 의해 별도의 SYSADATA 파일로 또는 Generalized object output file(GOFF)에 ADATA 레코드로 생성될 수 있다.
 마이크로소프트 윈도우는 프로그램 데이터베이스(.pdb) 파일에 저장된 심볼 테이블을 사용할 수 있다.
 대부분의 유닉스 계열 운영체제는 stabs 및 DWARF라는 심볼 테이블 형식을 사용할 수 있다. MacOS 및 기타 다윈 기반 운영체제에서는 디버그 심볼이 DWARF 형식으로 별도의 .dSYM 파일에 저장된다.

 같이 보기 
 RISC
 엔디언
 어셈블리어

내용주

 각주 

출처
 
 
 

추가 문헌
 
 
 

 
분류:어셈블리어
분류:저급 프로그래밍 언어