어셈블리에서 머신 코드로 이동하는 방법 (코드 생성)


16

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

예를 들어 메모장에서 이진 파일을 열면 텍스트 형식의 기계 코드 표현이 나타납니다. 나는 당신이 보는 각 바이트 (기호)가 이진 값에 해당하는 ASCII 문자라고 가정합니까?

그러나 어셈블리에서 바이너리로 어떻게 이동합니까?

답변:


28

명령어 세트 문서를 보면 각 명령어에 대한 그림 마이크로 컨트롤러 에서 이와 같은 항목을 찾을 수 있습니다 .

addlw 명령어 예

"인코딩"줄은이 명령어가 바이너리로 어떻게 보이는지 알려줍니다. 이 경우 항상 5로 시작하고 비트를 신경 쓰지 않는 비트 (1 또는 0 일 수 있음), "k"는 추가하는 리터럴을 나타냅니다.

처음 몇 비트는 "opcode"라고하며 각 명령어마다 고유합니다. CPU는 기본적으로 opcode를보고 어떤 명령어인지 확인한 다음 "k"를 추가 할 숫자로 해독합니다.

지루하지만 인코딩 및 디코딩하기가 어렵지는 않습니다. 시험에서 수작업으로해야하는 학부생 수업이있었습니다.

실제로 전체 실행 파일을 만들 려면 운영 체제에 따라 메모리 할당, 분기 오프셋 계산 및 ELF 와 같은 형식 으로 저장해야합니다.


10

어셈블리 opcode는 대부분 기본 기계 명령어와 일대일로 대응됩니다. 따라서 어셈블리 언어로 각 opcode를 식별하고 해당 기계 명령어에 매핑하고 해당 파라미터 (있는 경우)와 함께 기계 명령어를 파일에 작성하면됩니다. 그런 다음 소스 파일의 각 추가 opcode에 대해 프로세스를 반복하십시오.

물론 운영 체제에서 올바로로드 및 실행되는 실행 파일을 작성하는 데는 그 이상이 필요하며, 대부분의 알맞은 어셈블러에는 기계 명령 (예 : 매크로)에 대한 opcode의 간단한 매핑 외에 몇 가지 추가 기능이 있습니다.


7

가장 먼저 필요한 것은 이 파일같습니다 . 이것은 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. 바이트 중 어느 것도 메모리 참조를 구성하지 않는다는 메모와 함께 출력 모듈로 보내십시오 (출력 모듈이 알고 있는지 알아야 할 수도 있음).

모든 지시에 대해 반복하십시오. 참조 할 때 삽입 할 내용을 알 수 있도록 레이블을 추적하십시오. 오브젝트 파일 출력 모듈로 전달되는 매크로 및 지시문에 대한 기능을 추가하십시오. 이것이 기본적으로 어셈블러의 작동 방식입니다.


1
감사합니다. 위대한 설명하지만 0b11000000 = 0xc0과 때문에 오히려 "0x83 0xB0 0x2A"이 아닌 "0x83 0xc0과 0x2A"안
캄란

@ 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-... 네, 당신 말이 맞아요. :)
Jules

2

연습에서는, 어셈블러는 일반적으로 생성하지 않는 직접 일부 바이너리 실행 파일을 하지만, 일부 오브젝트 파일 합니다 (나중에 공급되는 링커 ). 그러나 예외가 있습니다 (일부 어셈블러를 사용하여 일부 이진 실행 파일을 직접 생성 할 수 있음).

첫째, 많은 어셈블러가 오늘날 무료 소프트웨어 프로그램 이라는 점에 유의하십시오 . 따라서 컴퓨터에서 GNU 의 소스 코드 ( binutils 의 일부 )와 nasm 의 소스 코드를 다운로드하여 컴파일하십시오 . 그런 다음 소스 코드를 연구하십시오. BTW, 나는 그 목적을 위해 Linux를 사용하는 것이 좋습니다 (매우 개발자 친화적이고 자유 소프트웨어 친화적 인 OS입니다).

어셈블러에서 생성 한 객체 파일에는 특히 코드 세그먼트재배치 명령이 포함되어 있습니다 . 운영 체제에 따라 잘 문서화 된 파일 형식으로 구성됩니다. Linux에서 해당 형식 (오브젝트 파일, 공유 라이브러리, 코어 덤프 및 실행 파일에 사용)은 ELF 입니다. 해당 객체 파일은 나중에 링커에 입력됩니다 (최종 실행 파일이 생성됨). 재배치는 ABI에 의해 지정됩니다 (예 : x86-64 ABI ). 자세한 내용은 Levine의 책 링커 및 로더 를 참조하십시오.

이러한 객체 파일의 코드 세그먼트에는 구멍이있는 머신 코드가 포함됩니다 (링커가 재배치 정보를 사용하여 채울 수 있음). 어셈블러에 의해 생성 된 (연관 가능한) 머신 코드는 분명히 명령어 세트 아키텍처에 따라 다릅니다 . 86 또는 - 64 (대부분의 노트북이나 데스크탑 프로세서에서 사용) ISA들은 세부 사항에 대단히 복잡하다. 그러나 교육 목적으로 y86 또는 y86-64라고하는 단순화 된 하위 집합이 개발되었습니다. 슬라이드 를 읽으십시오 . 이 질문에 대한 다른 답변들도 약간 설명합니다. 컴퓨터 아키텍처에 관한 좋은 을 읽고 싶을 수도 있습니다 .

대부분의 어셈블러는 두 단계 로 작업하며, 번째 단계는 재배치하거나 첫 번째 단계의 출력 중 일부를 수정합니다. 그들은 이제 일반적인 파싱 기술을 사용합니다 (아마도 The Dragon Book ).

OS 커널 이 실행 파일을 시작하는 방법 (예 : execveLinux 에서 시스템 호출이 작동 하는 방식)은 다르고 복잡한 질문입니다. 일반적으로 execve (2) ...를 수행하는 프로세스 에서 가상 주소 공간을 설정 한 다음 프로세스 내부 상태 ( 사용자 모드 레지스터 포함)를 다시 초기화합니다 . 동적 링커 로에는 Alert ld-linux.so (8) Linux- 런타임에 관여 할 수있다. Operating System : Three Easy Pieces 와 같은 좋은 책을 읽으십시오 . OSDEV의 위키는 또한 유용한 정보를 제공하고 있습니다.

추신. 귀하의 질문은 너무 광범위하여 이에 관한 몇 권의 책을 읽어야합니다. 나는 (매우 불완전한) 참조를 주었다. 더 많은 것을 찾아야합니다.


1
객체 파일 형식과 관련하여 초보자는 NASM에서 제작 한 RDOFF 형식을 살펴 보는 것이 좋습니다. 이것은 의도적으로 현실적으로 가능한 한 단순하게 설계되었으며 다양한 상황에서 여전히 작동합니다. NASM 소스에는 형식에 대한 링커 및 로더가 포함됩니다. (전체 공개-이 모든 것을 디자인하고 작성했습니다)
Jules
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.