어셈블리에서 기계어 코드로 어떻게 이동합니까 (코드 생성)

코드를 기계어 코드로 어셈블하는 단계를 시각화하는 쉬운 방법이 있습니까?

예를 들어 메모장에서 바이너리 파일에 대해 열면 기계 코드의 텍스트 형식 표현이 표시됩니다. 나는 당신이 보는 각 바이트 (기호)가 그것의 이진 값에 대응하는 ASCII 문자라고 가정한다.

그러나 우리는 어떻게 어셈블리에서 이진으로 갈 것인가, 뒤에서 무슨 일이 일어나고 있는가 ??

답변

명령어 세트 문서를 보면 그림 마이크로 컨트롤러 각 명령어 :

addlw 명령어 예시

“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 진수의 리터럴 바이트입니다. 출력합니다.
  • 다음은 /0modr/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이기 때문에

  • @Kamran-$ 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 소스에는 형식에 대한 링커와 로더가 포함됩니다. (전체 공개-이 모든 것을 디자인하고 작성했습니다.)

    답글 남기기

    이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다