프로세서가 각 명령어의 opcode와 피연산자를 결정하는 방법: VIM으로 바이너리 보기

프로세서가 각 명령어의 opcode와 피연산자를 결정하는 방법: VIM으로 바이너리 보기

FreeBSD 10.3에서 명령을 실행하면 view /bin/ls수정되지 않은 바이너리를 볼 수 있습니다.

여기에 이미지 설명을 입력하세요.

그런 다음 vim/view에서 명령을 실행하면 :%!xxd아래와 같이 16진수 형식의 파일을 볼 수 있습니다. 나는 페이지 하단에서 vim이 1708행이 추가되고 74행이 삭제되었다고 발표한 것을 보았습니다.

여기에 이미지 설명을 입력하세요.

명령어를 통해 vim을 닫았다 :q!가 다시 열어서 view /bin/lsvim 명령어를 실행 :%!xxd -b하면 아래와 같이 바이너리 형식의 파일이 보입니다. 페이지 하단에는 4555줄이 추가되었고 74줄이 삭제되었다고 나와 있습니다.

여기에 이미지 설명을 입력하세요.

이제 나는 알고 싶습니다:

  • vim에서 명령을 실행할 :%!xxd때 일부 줄이 추가되고 일부 줄이 삭제되는 이유는 무엇입니까?:%!xxd -b

  • 16진수 형식의 경우, 즉 %!xxd명령을 실행할 때 줄 주소는 00000000, 00000010, 00000020, 00000030 등입니다. 이는 각 줄에 16바이트가 포함되어 있으므로 0x10 증분이 의미가 있기 때문인 것 같습니다.

  • 바이너리 형식의 경우, 즉 %!xxd -b명령을 실행할 때 줄 주소는 00000000, 00000006, 0000000c, 00000012 등입니다. 이는 각 줄에 6바이트가 포함되어 있으므로 0x06 증분이 의미가 있기 때문인 것 같습니다.

  • 이전에는 각 바이너리가 한 줄의 각 프로세서 명령어를 포함하고 각 줄의 시작 부분에 해당 명령어의 상대 주소가 있으며 첫 번째 명령어는 0으로 시작한다고 생각했습니다. 그러나 vim에서 바이너리를 관찰한 결과, 이는 사실이 아닙니다. 이제 나는 알고 싶다프로세서가 각 명령어의 opcode와 피연산자를 결정하는 방법, 지시어가 바이너리 파일에서 한 줄씩 형식화되지 않은 경우.


고쳐 쓰다:

16진수 형식의 마지막 5줄은 다음과 같습니다.

00006a70: 0100 0000 3000 0000 0000 0000 4862 0000  ....0.......Hb..
00006a80: 3e03 0000 0000 0000 0000 0000 0100 0000  >...............
00006a90: 0100 0000 0100 0000 0300 0000 0000 0000  ................
00006aa0: 0000 0000 8665 0000 d500 0000 0000 0000  .....e..........
00006ab0: 0000 0000 0100 0000 0000 0000 0a         .............

이진 형식의 마지막 5줄은 다음과 같습니다.

00006aa4: 10000110 01100101 00000000 00000000 11010101 00000000  .e....
00006aaa: 00000000 00000000 00000000 00000000 00000000 00000000  ......
00006ab0: 00000000 00000000 00000000 00000000 00000001 00000000  ......
00006ab6: 00000000 00000000 00000000 00000000 00000000 00000000  ......
00006abc: 00001010                                               .

그래서 16진수 형식과 2진수 형식의 총 바이트 수가 같다고 생각합니다. 즉, 코드의 마지막 바이트 주소가 둘 다 동일하다는 뜻입니다 0x6abc.

답변1

vim에서 :%!xxd 및 :%!xxd -b 명령을 실행할 때 일부 줄이 추가되고 일부 줄이 삭제되는 이유는 무엇입니까?

vim줄 바꿈이 계산 0x0a되고 바이너리에 이러한 내용이 포함되어 있기 때문에 (귀하의 버전에서는 74줄 ls...) 바이너리의 이 74줄은 원래 바이너리와 다른 형식을 교환할 때 제거되고 새 행이 삭제됩니다. (더 자세한) 16진수 표시를 위해 추가되었습니다. vim단지 보이는 것을 계산할 뿐입니다 0x0a.

이제 프로세서가 각 명령어에 대한 opcode와 피연산자를 어떻게 결정하는지 알고 싶습니다.

마법! 그것은 복잡하고 이 주제에 대해 쓰여진 책이 많이 있습니다. 즉, 링커(또는 이에 상응하는) 특정 바이너리 형식(귀하의 경우 ELF, 다른 형식도 존재하지만 a.out, Mach-O 등)은 시작 주소를 나타냅니다.

$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x37f0
...

프로그램이 메모리로 승격된 후 opcode 실행이 시작됩니다. 시작 주소는 일반적으로 .text바이너리 파일 섹션 어딘가에 있습니다(아마도 아닐 수도 있음).

$ objdump -DS /bin/ls | less -p .text
...

내 OpenBSD 시스템에는 다음이 표시됩니다.

Disassembly of section .text:

00000000000037f0 <revnamecmp-0x460>:
    37f0:       49 89 e4                mov    %rsp,%r12
    37f3:       48 83 ec 08             sub    $0x8,%rsp
    37f7:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    37fb:       48 83 c4 08             add    $0x8,%rsp
...

살펴볼 가치가 있는 책으로는 Jeff Duntemann의 "Assembly Language Step-by-Step"과 Ryan O'Neill의 ELF "Learning Linux Binary Analysis"가 있습니다.

관련 정보