답변:
명령어 세트 문서를 보면 각 명령어에 대한 그림 마이크로 컨트롤러 에서 이와 같은 항목을 찾을 수 있습니다 .
"인코딩"줄은이 명령어가 바이너리로 어떻게 보이는지 알려줍니다. 이 경우 항상 5로 시작하고 비트를 신경 쓰지 않는 비트 (1 또는 0 일 수 있음), "k"는 추가하는 리터럴을 나타냅니다.
처음 몇 비트는 "opcode"라고하며 각 명령어마다 고유합니다. CPU는 기본적으로 opcode를보고 어떤 명령어인지 확인한 다음 "k"를 추가 할 숫자로 해독합니다.
지루하지만 인코딩 및 디코딩하기가 어렵지는 않습니다. 시험에서 수작업으로해야하는 학부생 수업이있었습니다.
실제로 전체 실행 파일을 만들 려면 운영 체제에 따라 메모리 할당, 분기 오프셋 계산 및 ELF 와 같은 형식 으로 저장해야합니다.
어셈블리 opcode는 대부분 기본 기계 명령어와 일대일로 대응됩니다. 따라서 어셈블리 언어로 각 opcode를 식별하고 해당 기계 명령어에 매핑하고 해당 파라미터 (있는 경우)와 함께 기계 명령어를 파일에 작성하면됩니다. 그런 다음 소스 파일의 각 추가 opcode에 대해 프로세스를 반복하십시오.
물론 운영 체제에서 올바로로드 및 실행되는 실행 파일을 작성하는 데는 그 이상이 필요하며, 대부분의 알맞은 어셈블러에는 기계 명령 (예 : 매크로)에 대한 opcode의 간단한 매핑 외에 몇 가지 추가 기능이 있습니다.
가장 먼저 필요한 것은 이 파일 과 같습니다 . 이것은 NASM 어셈블러에서 사용하는 x86 프로세서 용 명령 데이터베이스입니다 (실제로 명령을 번역하는 부분은 아니지만 작성에 도움이 됨). 데이터베이스에서 임의의 행을 선택할 수 있습니다.
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
이것이 의미하는 것은 명령을 설명한다는 것 ADD
입니다. 이 명령어에는 여러 변형이 있으며 여기에서 설명하는 특정 변형은 32 비트 레지스터 또는 메모리 주소를 사용하고 즉각적인 8 비트 값 (즉, 명령어에 직접 포함 된 상수)을 추가하는 변형입니다. 이 버전을 사용하는 예제 어셈블리 명령은 다음과 같습니다.
add eax, 42
이제 텍스트 입력을 가져 와서 개별 명령어와 피연산자로 구문 분석해야합니다. 위의 명령어의 경우 명령어 ADD
및 피연산자 배열 (레지스터 EAX
및 값에 대한 참조) 이 포함 된 구조가 될 수 있습니다 42
. 이 구조를 가지면 명령 데이터베이스를 실행하여 명령 이름과 피연산자 유형 모두와 일치하는 행을 찾습니다. 일치하는 항목을 찾지 못하면 사용자에게 제시해야하는 오류입니다 ( "오 코드 및 피연산자의 잘못된 조합"또는 이와 유사한 것이 일반적인 텍스트입니다).
데이터베이스에서 줄을 얻었 으면 세 번째 열을 살펴 보겠습니다.이 열은 다음과 같습니다.
[mi: hle o32 83 /0 ib,s]
다음은 필요한 기계어 명령어를 생성하는 방법을 설명하는 명령어 세트입니다.
mi
피연산자에 대한 설명입니다. 하나는 modr/m
(레지스터 또는 메모리) 피연산자입니다 (즉 modr/m
, 명령의 끝에 바이트를 추가해야 함을 의미합니다 ). 명령 설명에 사용됩니다. hle
입니다. 이것은 우리가 "잠금"접두사를 처리하는 방법을 식별합니다. 우리는 "잠금"을 사용하지 않았으므로 무시합니다.o32
입니다. 이것은 16 비트 출력 형식의 코드를 어셈블하는 경우 명령에 피연산자 크기 재정의 접두사가 필요하다는 것을 알려줍니다. 16 비트 출력을 생성하는 경우 접두사를 지금 생성 0x66
하지만 ( ) 그렇지 않은 것으로 가정합니다.83
입니다. 16 진 리터럴 바이트입니다. 출력합니다.다음은 /0
입니다. 이것은 modr / m bytem에 필요한 몇 가지 추가 비트를 지정하고이를 생성하게합니다. modr/m
바이트 인코딩 레지스터 또는 메모리 간접 참조로 사용된다. 하나의 피연산자 인 레지스터가 있습니다. 레지스터에는 다른 데이터 파일에 지정된 번호가 있습니다 .
eax REG_EAX reg32 0
우리 reg32
는 원본 데이터베이스의 명령 크기에 동의 하는지 확인합니다 ( 그렇습니다). 는 0
레지스터의 번호입니다. modr/m
바이트는 프로세서에 의해 지정된 데이터 구조 등이 해당 보인다 :
(most significant bit)
2 bits mod - 00 => indirect, e.g. [eax]
01 => indirect plus byte offset
10 => indirect plus word offset
11 => register
3 bits reg - identifies register
3 bits rm - identifies second register or additional data
(least significant bit)
레지스터로 작업하기 때문에 mod
필드는 0b11
입니다.
reg
필드는 우리가 사용하는 레지스터의 번호입니다.0b000
rm
필드를 무언가 로 채워야 합니다. 그것이 추가 데이터에 지정된 /0
것이므로 rm
필드에 넣습니다 0b000
.modr/m
바이트는 0b11000000
또는 0xC0
입니다. 이것을 출력합니다.ib,s
입니다. 부호있는 즉시 바이트를 지정합니다. 피연산자를보고 즉시 사용할 수있는 값이 있습니다. 부호있는 바이트로 변환하여 출력합니다 ( 42
=> 0x2A
).따라서 전체 조립 지침은 다음과 같습니다 0x83 0xC0 0x2A
. 바이트 중 어느 것도 메모리 참조를 구성하지 않는다는 메모와 함께 출력 모듈로 보내십시오 (출력 모듈이 알고 있는지 알아야 할 수도 있음).
모든 지시에 대해 반복하십시오. 참조 할 때 삽입 할 내용을 알 수 있도록 레이블을 추적하십시오. 오브젝트 파일 출력 모듈로 전달되는 매크로 및 지시문에 대한 기능을 추가하십시오. 이것이 기본적으로 어셈블러의 작동 방식입니다.
$ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003
-... 네, 당신 말이 맞아요. :)
연습에서는, 어셈블러는 일반적으로 생성하지 않는 직접 일부 바이너리 실행 파일을 하지만, 일부 오브젝트 파일 합니다 (나중에 공급되는 링커 ). 그러나 예외가 있습니다 (일부 어셈블러를 사용하여 일부 이진 실행 파일을 직접 생성 할 수 있음).
첫째, 많은 어셈블러가 오늘날 무료 소프트웨어 프로그램 이라는 점에 유의하십시오 . 따라서 컴퓨터에서 GNU 의 소스 코드 ( binutils 의 일부 )와 nasm 의 소스 코드를 다운로드하여 컴파일하십시오 . 그런 다음 소스 코드를 연구하십시오. BTW, 나는 그 목적을 위해 Linux를 사용하는 것이 좋습니다 (매우 개발자 친화적이고 자유 소프트웨어 친화적 인 OS입니다).
어셈블러에서 생성 한 객체 파일에는 특히 코드 세그먼트 및 재배치 명령이 포함되어 있습니다 . 운영 체제에 따라 잘 문서화 된 파일 형식으로 구성됩니다. Linux에서 해당 형식 (오브젝트 파일, 공유 라이브러리, 코어 덤프 및 실행 파일에 사용)은 ELF 입니다. 해당 객체 파일은 나중에 링커에 입력됩니다 (최종 실행 파일이 생성됨). 재배치는 ABI에 의해 지정됩니다 (예 : x86-64 ABI ). 자세한 내용은 Levine의 책 링커 및 로더 를 참조하십시오.
이러한 객체 파일의 코드 세그먼트에는 구멍이있는 머신 코드가 포함됩니다 (링커가 재배치 정보를 사용하여 채울 수 있음). 어셈블러에 의해 생성 된 (연관 가능한) 머신 코드는 분명히 명령어 세트 아키텍처에 따라 다릅니다 . 86 또는 - 64 (대부분의 노트북이나 데스크탑 프로세서에서 사용) ISA들은 세부 사항에 대단히 복잡하다. 그러나 교육 목적으로 y86 또는 y86-64라고하는 단순화 된 하위 집합이 개발되었습니다. 슬라이드 를 읽으십시오 . 이 질문에 대한 다른 답변들도 약간 설명합니다. 컴퓨터 아키텍처에 관한 좋은 책 을 읽고 싶을 수도 있습니다 .
대부분의 어셈블러는 두 단계 로 작업하며, 두 번째 단계는 재배치하거나 첫 번째 단계의 출력 중 일부를 수정합니다. 그들은 이제 일반적인 파싱 기술을 사용합니다 (아마도 The Dragon Book ).
OS 커널 이 실행 파일을 시작하는 방법 (예 : execve
Linux 에서 시스템 호출이 작동 하는 방식)은 다르고 복잡한 질문입니다. 일반적으로 execve (2) ...를 수행하는 프로세스 에서 가상 주소 공간을 설정 한 다음 프로세스 내부 상태 ( 사용자 모드 레지스터 포함)를 다시 초기화합니다 . 동적 링커 로에는 Alert ld-linux.so (8) Linux- 런타임에 관여 할 수있다. Operating System : Three Easy Pieces 와 같은 좋은 책을 읽으십시오 . OSDEV의 위키는 또한 유용한 정보를 제공하고 있습니다.
추신. 귀하의 질문은 너무 광범위하여 이에 관한 몇 권의 책을 읽어야합니다. 나는 (매우 불완전한) 참조를 주었다. 더 많은 것을 찾아야합니다.