코드를 기계어 코드로 어셈블하는 단계를 시각화하는 쉬운 방법이 있습니까?
예를 들어 메모장에서 바이너리 파일에 대해 열면 기계 코드의 텍스트 형식 표현이 표시됩니다. 나는 당신이 보는 각 바이트 (기호)가 그것의 이진 값에 대응하는 ASCII 문자라고 가정한다.
그러나 우리는 어떻게 어셈블리에서 이진으로 갈 것인가, 뒤에서 무슨 일이 일어나고 있는가 ??
답변
명령어 세트 문서를 보면 그림 마이크로 컨트롤러 각 명령어 :
“encoding”줄은 그 명령어가 바이너리에서 어떻게 보이는지. 이 경우, 항상 5 개의 1로 시작하고, 그 다음에는 don “t care 비트 (1 또는 0 일 수 있음),”k “는 추가하려는 리터럴을 나타냅니다.
The 처음 몇 비트는 “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
필드 인 . -
modr/m
바이트는 따라서0b11000000
또는 . 출력합니다. - 다음은
ib,s
입니다. 이것은 부호있는 즉시 바이트를 지정합니다. 피연산자를 살펴보면 즉시 값을 사용할 수 있습니다. 서명 된 바이트로 변환하여 출력합니다 (42
=>0x2A
).
따라서 조립 된 완전한 명령어는 다음과 같습니다. 0x83 0xC0 0x2A
. 어떤 바이트도 메모리 참조를 구성하지 않는다는 메모와 함께 출력 모듈로 보냅니다 (출력 모듈은 해당하는 경우).
모든 지침에 대해 반복합니다. 레이블을 추적하여 참조 할 때 무엇을 삽입해야하는지 알 수 있습니다. 개체 파일 출력 모듈에 전달되는 매크로 및 지시문에 대한 기능을 추가합니다. 이것이 기본적으로 어셈블러가 작동하는 방식입니다.
댓글
- 감사합니다. 좋은 설명이지만 ' " 0x83 0xC0 0x2A "이어야합니다. id = “126a74f238″>
0x83 0xB0 0x2A " 0b11000000 = 0xC0이기 때문에
$ 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 as ( binutils 의 일부) 및 nasm . 그런 다음 소스 코드를 연구합니다. BTW, Linux를 해당 목적으로 사용하는 것이 좋습니다 (개발자에게 매우 친숙하고 무료 소프트웨어 친화적 인 OS입니다).
어셈블러가 생성 한 개체 파일에는 특히 코드 세그먼트가 포함되어 있습니다. 및 재배치 지침. 운영 체제에 따라 잘 문서화 된 파일 형식으로 구성됩니다. Linux에서 해당 형식 (객체 파일, 공유 라이브러리, 코어 덤프 및 실행 파일에 사용됨)은 ELF 입니다. 이 개체 파일은 나중에 링커 (마지막으로 실행 파일을 생성 함)에 입력됩니다. 재배치는 ABI 에 의해 지정됩니다 (예 : x86-64 ABI ). 자세한 내용은 Levine의 책 링커 및 로더 를 읽어보세요.
이러한 개체 파일의 코드 세그먼트에는 구멍이있는 기계어 코드 (링커에 의해 재배치 정보의 도움으로 채워짐) 어셈블러에서 생성 된 (재배치 가능) 기계어 코드는 분명히 명령어 세트에 고유합니다. 아키텍처 . x86 또는 x86-64 (대부분의 노트북 또는 데스크탑 프로세서에서 사용됨) ISA는 끔찍합니다. 세부 사항이 복잡합니다. 그러나 y86 또는 y86-64라고하는 단순화 된 하위 집합이 교육 목적으로 발명되었습니다. 슬라이드 를 읽어보세요. 이 질문에 대한 다른 답변도 이에 대해 설명합니다. 좋은 컴퓨터 아키텍처에 관한 책 을 읽을 수 있습니다.
대부분의 어셈블러는 두 패스 , 두 번째 패스는 재배치를 방출하거나 첫 번째 패스의 출력 일부를 수정합니다. 이제는 일반적인 파싱 기술을 사용합니다 (따라서 The Dragon Book 참조).
실행 파일이 OS 커널 에서 시작되는 방법 (예 : Linux에서 execve
시스템 호출이 작동하는 방법) )는 다른 (그리고 복잡한) 질문입니다. 일반적으로 일부 가상 주소 공간 을 설정합니다 ( 프로세스 에서 execve (2) …) 그런 다음 프로세스 내부 상태 ( 사용자 모드 레지스터 포함)를 다시 초기화합니다. 동적 링커 (예 : Linux의 ld-linux.so (8) )는 런타임에 참여해야합니다. 운영 체제 : 쉬운 세 가지 와 같은 좋은 책을 읽으십시오. OSDEV 위키도 유용한 정보를 제공합니다.
PS. 귀하의 질문은 너무 광범위하여 이에 관한 여러 권의 책을 읽어야합니다. 나는 몇 가지 (매우 불완전한) 참조를 제공했습니다. 더 많은 정보를 찾을 수 있습니다.
댓글
- 초보자를위한 개체 파일 형식 관련 ' d는 NASM에서 생성 한 RDOFF 형식을 살펴볼 것을 권장합니다. 이것은 가능한 한 현실적으로 간단하고 다양한 상황에서 여전히 작동하도록 의도적으로 설계되었습니다. NASM 소스에는 형식에 대한 링커와 로더가 포함됩니다. (전체 공개-이 모든 것을 디자인하고 작성했습니다.)