Какова фактическая связь между сборкой, машинным кодом, байт-кодом и кодом операции?

какова фактическая связь между сборкой, машинным кодом, байт-кодом и кодом операции?

Я прочитал большинство вопросов SO о сборке и машинном коде, таких как этой, но они слишком высокого уровня и не показывают примеры фактического кода сборки, преобразуемого в машинный код. В результате, я до сих пор не понимаю, как это работает на более глубоком уровне.

идеальный ответ на этот вопрос покажет конкретный пример некоторым код сборки, например приведенный ниже фрагмент, и способ сопоставления каждой инструкции сборки с машинным кодом, байт-кодом и/или кодом операции. Такой ответ был бы очень полезен будущим людям, изучающим сборку, потому что до сих пор за последние несколько дней я не нашел четкого резюме.

основные вещи, которые я ищу:

  1. фрагмент ассемблерного кода
  2. фрагмент машины код
  3. сопоставление между фрагментом сборки и машинным кодом (как сделать это отображение или, по крайней мере, некоторые общие примеры, и как вы знаю как это сделать, где вся эта информация в интернете)
  4. как интерпретировать машинный код (например, коды операций как-то связаны, и где вся информация в интернете о том, что все эти числа mean)

Примечание: у меня нет опыта в области компьютерных наук, поэтому я просто медленно спускался на более низкий уровень в течение последних нескольких лет и теперь дошел до того, чтобы захотеть понять сборку и машинный код.

связь между сборкой и машинным кодом

мое текущее понимание заключается в том ,что "ассемблер" (например, NASM) принимает код сборки и создает машинный код из он.

так при компиляции сборки, например эта example.asm:

global main
section .text

main:
  call write

write:
  mov rax, 0x2000004
  mov rdi, 1
  mov rsi, message
  mov rdx, length
  syscall

section .data
message: db 'Hello, world!', 0xa
length: equ $ - message

(скомпилируйте его с помощью nasm -f macho64 -o example.o example.asm). Он выводит example.o объектный файл:

cffa edfe 0700 0001 0300 0000 0100 0000
0200 0000 0001 0000 0000 0000 0000 0000
1900 0000 e800 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
2e00 0000 0000 0000 2001 0000 0000 0000
2e00 0000 0000 0000 0700 0000 0700 0000
0200 0000 0000 0000 5f5f 7465 7874 0000
0000 0000 0000 0000 5f5f 5445 5854 0000
0000 0000 0000 0000 0000 0000 0000 0000
2000 0000 0000 0000 2001 0000 0000 0000
5001 0000 0100 0000 0005 0080 0000 0000
0000 0000 0000 0000 5f5f 6461 7461 0000
0000 0000 0000 0000 5f5f 4441 5441 0000
0000 0000 0000 0000 2000 0000 0000 0000
0e00 0000 0000 0000 4001 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0200 0000 1800 0000
5801 0000 0400 0000 9801 0000 1c00 0000
e800 0000 00b8 0400 0002 bf01 0000 0048
be00 0000 0000 0000 00ba 0e00 0000 0f05
4865 6c6c 6f2c 2077 6f72 6c64 210a 0000
1100 0000 0100 000e 0700 0000 0e01 0000
0500 0000 0000 0000 0d00 0000 0e02 0000
2000 0000 0000 0000 1500 0000 0200 0000
0e00 0000 0000 0000 0100 0000 0f01 0000
0000 0000 0000 0000 0073 7461 7274 0077
7269 7465 006d 6573 7361 6765 006c 656e
6774 6800 

(это все содержимое example.o). Когда вы затем "связать", что с помощью ld -o example example.o, это дает вам больше машинного кода:

cffa edfe 0700 0001 0300 0080 0200 0000
0d00 0000 7803 0000 8500 0000 0000 0000
1900 0000 4800 0000 5f5f 5041 4745 5a45
524f 0000 0000 0000 0000 0000 0000 0000
0010 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 1900 0000 9800 0000
5f5f 5445 5854 0000 0000 0000 0000 0000
0010 0000 0000 0000 0010 0000 0000 0000
... 523 lines of this

но как он перешел от инструкций по сборке к этим номерам? Есть ли какая-то стандартная ссылка, в которой перечислены все эти числа, и что они означают для любой архитектуры, на которой вы находитесь (я использую x86-64 через NASM на OSX), и как каждый набор чисел сопоставляется с каждой инструкцией по сборке?

я понимаю, что машинный код для каждой машины, и есть десятки, если не сотни разных видов машин. Поэтому в настоящее время я не ищу, как сборка преобразуется в каждую (это было бы сложно). Меня просто интересует пример, который иллюстрирует, как трансформация работает, и примером может служить любая архитектура. И с этого момента я могу пойти и исследовать конкретную архитектуру, которая меня интересует, и найти карту.

отношение между сборкой и байт-кодом (или это называется "код операции"?)

Итак, из моего чтения до сих пор сборка преобразуется в машинный код, как показано выше.

но теперь я запутался. Я вижу, что люди говорят о байт-коде, например в этом так ответ, показывая такие вещи, как это:

void myfunc(int a) {
  printf("%s", a);
}

сборка для этой функции будет выглядеть так:

OP Params OpName     Description
13 82 6a  PushString 82 means string, 6a is the address of "%s"
                     So this function pushes a pointer to "%s" on the stack.
13 83 00  PushInt    83 means integer, 00 means the one on the top of the stack.
                     So this function gets the integer at the top of the stack,
                     And pushes it on the stack again
17 13 88 Call        1388 is printf, so this calls the printf function
03 02    Pop         This pops the two things we pushed back off the stack
02       Return      This returns to the calling code.

так что я запутался. Делая некоторые раскопки, я не могу сказать, если каждый из этих двухзначных шестнадцатеричных чисел, как 13 82 6a каждый, индивидуально, называется "opcodes", и весь набор из них называется" байт-код " в качестве универсального термина. Кроме того, я не могу найти таблицу, в которой перечислены все эти 2-значные шестнадцатеричные числа и их отношение к машинному коду или сборке.

подводя итог, я с нетерпением жду примера, показывающего, как инструкции по сборке сопоставляются с машинным кодом, и это отношение к байт-коду и/или коду операции. (Я не ищу, как компилятор делает это, просто как работает общее сопоставление). Я думаю, что это прояснит это не только для меня, но и для многих людей, которые заинтересованы в том, чтобы узнать больше о голом металле.

еще одна причина, почему это будет полезно знать, так что можно понять, как компилятор LLVM генерирует машинный код. У них есть какой-то" полный список " 2-значных опкодов или машинный код 4-значных последовательностей и точно знают, как это сопоставляется с любой сборкой, специфичной для архитектуры? Откуда у них такая информация? Ответ на этот общий вопрос сделал бы гораздо более ясным, как LLVM реализовал свою генерацию кода.

обновление

обновление от @HansPassant по комментировать. На самом деле мне все равно, каковы фактические различия между словами, Извините, если это не было ясно. Я просто хочу знать следующее: как сборка сопоставляется с машинным кодом (и где можно начать поиск ссылок, содержащих эту информацию в интернете), и используются ли опкоды или байт-код в любом месте этого процесса? И если да, то как?

6 ответов


Да, каждая архитектура имеет ссылку на набор инструкций, который дает, как инструкции кодируются. Для x86, это Intel® 64 и IA-32 архитектуры руководство разработчика программного обеспечения Том 2 (2A, 2B & 2C): Справочник по набору инструкций, A-Z

большинство ассемблеров, в том числе nasm, можно создать файл листинга для вас. Подача кода образца в nasm -l, мы получим:

 1                                  global main
 2                                  section .text
 3
 4                                  main:
 5 00000000 E800000000                call write
 6
 7                                  write:
 8 00000005 B804000002                mov rax, 0x2000004
 9 0000000A BF01000000                mov rdi, 1
10 0000000F 48BE-                     mov rsi, message
11 00000011 [0000000000000000]
12 00000019 BA0E000000                mov rdx, length
13 0000001E 0F05                      syscall
14
15                                  section .data
16 00000000 48656C6C6F2C20776F-     message: db 'Hello, world!', 0xa
17 00000009 726C64210A
18                                  length: equ $ - message

вы можете увидеть сгенерированный машинный код в третьей колонке (первый номер строки, второй-адрес).

обратите внимание, что выход ассемблера является объектным файлом, а выход компоновщика-исполняемым файлом. Оба они имеют сложную структуру и содержат больше, чем просто машинный код. Вот почему ваш hexdump отличается от приведенного выше списка.

код операции обычно считается частью инструкции машинного кода, которая определяет операцию для выполнения. Например, в приведенном выше коде у вас есть B804000002 mov rax, 0x2000004. Там B8 - это опкод, 04000002 является непосредственным операндом.

байт-код обычно не используется в контексте сборки, его можно рассматривать как машинный код для виртуальной машины.


для пошагового руководства x86-очень сложная архитектура. Но ваш пример кода имеет простую инструкцию,syscall. Давайте посмотрим, как превратить это в машинный код. Откройте вышеупомянутый справочный pdf-файл и перейдите в раздел о syscall в главе 4. Вы сразу увидите его в списке как opcode 0F 05. Поскольку он не принимает никаких операндов, мы закончили, эти 2 байта являются машинным кодом. Как нам повернуть все вспять? Перейти к Appendix A: Opcode map. Раздел A.1 говорит нам: For 2-byte opcodes beginning with 0FH (Table A-3), skip any instruction prefixes, the 0FH byte (0FH may be preceded by 66H, F2H, or F3H) and use the upper and lower 4-bit values of the next opcode byte to index table rows and columns.. Ладно, пропустим 0F и разделить 05 на 0 и 5 и посмотрите, что в таблице A-3 в строке #0, столбец 5. Мы находим, что это syscall инструкция.


есть ли какая-то стандартная ссылка, в которой перечислены все эти числа и что они означают для любой архитектуры, в которой вы находитесь, и как каждый набор чисел сопоставляется с каждой инструкцией по сборке?

да, хотя они могут быть очень сложными. Кроме того, из-за распространенности ассемблеров и компиляторов их также трудно найти, потому что почти никто их не использует.

отношение между агрегатом и Байткод

  • машинный код-одно или несколько значений, считываемых в процессор. Каждый номер является "инструкцией" или "кодом операции" и может сопровождаться одним или несколькими параметрами для действия. В связанном коде 13 говорит процессору нажать строку в стек.
  • OpCode-значение для команды: в примере код операции для нажатия строки 13.
  • сборка-читаемые человеком инструкции для внутреннего машинного кода процессора. Почти всегда одна инструкция по сборке на инструкцию машинного кода. В моем коде, с которым вы связались, инструкция "сборка"PushString карты к инструкции машины 13.
  • байтовый код-поскольку каждый процессор использует другой машинный код, иногда программы компилируются в машинный код для воображаемой "виртуальной машины", а затем имеют программу, которая читает этот поддельный машинный код и выполняет его (либо через эмуляцию, либо JIT). Java и C# и VB все это делают. Это " подделка" машинный код называется "байтовым кодом", хотя термины часто используются взаимозаменяемо.

Я должен отметить, что инструкции байт-кода, используемые в этом посте и в моем другом посте, с которым вы связались, являются упрощенными извлечениями из проприетарного байтового кода, с которым я работаю в своей компании. У нас есть собственный язык программирования, который компилируется в этот байт-код, который интерпретируется нашим продуктом, и некоторые из значений, которые я упомянул, являются реальными байт-кодами, которые мы фактически используем. 13 is на самом деле pushAnything со сложными параметрами, но я держал вещи простыми для ответа.


вы явно сделали домашнее задание по этому вопросу, и я говорю хорошие вещи (и проголосовал за вас).

хорошо, во-первых, когда вы сталкиваетесь со словом "байт-код" , просто закройте окно и прекратите чтение, потому что вы находитесь на неправильном пути; вероятно, касательная в лучшем случае, и в худшем случае вы можете читать кого-то, кто пытается казаться умнее, чем он на самом деле, бросая технические звучащие модные слова в его написании.

теперь, что касается слова "код операции", да, они действительно существуют, но понимают, что эти цифры на самом деле символические, для людей, чтобы понять концептуально. В реальной жизни, они супер-ультра-крошечные переключатели.

Если вам действительно нравится история, и технологии перед интернетом (или цветное телевидение, если на то пошло) посмотрите фразы, как переключатели бабочки, вакуумные трубки, девушки бабочки и я забыл другие слова. Это было раньше. транзисторы существовали. Первоначально огромные компьютеры использовали вакуумные трубки и производили достаточно тепла, чтобы согреть целый этаж (или два или три) офисного здания в разгар зимы. Притяжение электрического тока было поразительным.

дело в том, чтобы держать в уме обо всем этом, что эти компьютеры были "запрограммированы" индивидуально щелкая переключателями бабочки ("ручки летучей мыши" были другим термином, иногда используемым), которые подключали и отключали отдельные линии от отдельные трубки, и я забыл, что еще.

Перемотка Вперед На Сегодня...

когда вы пишете код операции 90h, (я думаю, что это NOP в x86, кто-то исправит меня, и я это исправлю), вы делаете (с сегодняшним высокотехнологичным wowee-zowee) то же самое, что делали девушки-бабочки в каменном веке компьютеры.

в частности, вы "бросаете" эти "переключатели бабочки"...

  • 7-ON
  • 6-OFF
  • 5-OFF
  • 4-ON
  • 3-OFF
  • 2-OFF
  • 1-OFF
  • 0-OFF

вот большая разница (и часть сегодняшнего hi-tech wowee-zowee)...

они должны были бросить именно эти переключатели ровно в одном месте на этаж. Вы будете листать их везде, где вы хотите. Три другие программы будут сотрудничать и принимать эти решения за вас.

эти три программы - ассемблер - компоновщик - Заряжающий!--10-->

затем (я надеюсь), что это помогло заложить основу для вас, чтобы понять, что код - это мысленное представление группы маленьких переключателей, которые будут "открыты" или "закрыты".

(на самом деле, высокотехнологичный wowee-zowee сделал еще один шаг вперед, но это тот же эффект, что и переключатели бабочек предыдущих гнераций.)

в любом случае, как это работает.

люди решили, что будет инструкция ничего не делать; называется NOP

Итак, вы печатаете буквы NOP в вашем текстовом редакторе, как это

  NOP           ;This is a No operation instruction

затем сохраните файл.

затем вы просите ассемблер собрать этот файл

когда ассемблер видит NOP он создает 90 (в hex) в объект файл, который он создает для компоновщика.

Компоновщик использует объектный файл и создает исполняемый файл

загрузчик помещает этот исполняемый файл туда, куда он хочет. (Заметьте, в старые времена микрокомпьютеров автор программного обеспечения должен был решить, куда поместить этот исполняемый файл; это была приманка для конфликтов, как вы не поверите.)

все равно NOP стал 90 в каком-то месте в EXE файл и загрузчик застрял в хорошей области для вас, основываясь на 179 правилах, о которых вам больше не нужно беспокоиться.

затем загрузчик выходит из изображения и позволяет вашей программе иметь процессор.

CPU получает вашу первую инструкцию и начинает повиноваться.

когда CPU попадает в байт, содержащий 90 это будет то же самое, что бабочка переключается из прошлых поколений.

пока ток не будет путешествовать по пучку длинных проводов на полу, он будет делать очень похожие (и функционально эквивалентные) вещи внутри ASIC.

теперь со всем, что написано (Спасибо, если вы все еще читаете), вы можете понять, что это сводится к одной строке объяснения того, что такое код операции на самом деле...

код операции-это парадигматическое представление переключателей-бабочек старых дней.

теперь для вашего второго вопроса о том, что машинный код.

машинный код-это куча опкодов

если что-то из этого неясно, спросите в разделе комментариев, и я попытаюсь отредактировать этот ответ.


кратко:

"сборка" - это то, что вы пропускаете через"ассемблер". Ассемблер-это программа, которая считывает несколько колод перфокарт и" собирает " их в одну программу.

или, по крайней мере, что раньше. Теперь карты заменяются дисковыми файлами. Но данные на "картах" - это" машинный язык", который является числовыми значениями для инструкций машины.

но современные ассемблеры-это SAP-символические ассемблерные программы - так что вы можно заменить числовые значения символами - скажем, " LOD "для Инструкции по загрузке," R1 "для регистра 1 и" label5 " для адреса инструкции 26734.

"машинный язык" - это способ представления отдельных инструкций (или" заказов", если вы британец) для процессора. Для символического ассемблера у вас может быть "LOD R1, LOOPCOUNT", чтобы представить инструкцию для загрузки значения в слове loopcount в Регистр 1. "LOD", кстати, является "кодом операции" -- (символическим версия) числовое значение, которое сообщает компьютеру, что делать дальше. (И обратите внимание, что каждый другой компьютерный дизайн использует другой машинный язык, возможно, с разными символами для опкодов. Большая часть того, что вы найдете в интернете, - это та или иная версия машинного языка Intel, но вы обнаружите, что IBM 370 радикально отличается.)

"байт-код" - это другой вид" машинного языка", который работает на" виртуальной машине " вместо реального оборудования. Самый известный случай этого-виртуальная машина Java. "Байт-код" - это обозначение, похожее на обычный" машинный язык", но до некоторой степени идеализированное, поскольку работа на виртуальной машине освобождает ее от некоторых реалий реальной аппаратной среды.


отношения:

Assembler instruction (readable) ->  machine code (binary) 

machine code = opcode + operands

на сборщик инструкция читаемый человеком код, как:mov rax, 0x2000004

на код является частью машинного кода, который относится к инструкции, но с точки зрения процессора (так что это не просто MOV, но mov константа для регистрации). Например, см. здесь для i386 MOV opcodes:

  • MOV reg32, immediate value кодируется как B8 + код регистра (AX первый так это 0),
  • код операции сопровождается 0x20000004 операнд, который кодируется в маленькой логике прямым, как: 04 00 00 02

байт-код является эквивалентом машинного кода, но для виртуальных машин, таких как JVM. Термин байт-код коды из первых сред, которые использовали эту технологию (p-код из Сан-Диего компилятор pascal), который использовал байт для кодирования виртуальной инструкции. Вы можете найти например, маленький P-код insruction набор здесь, и более поздний и обширный байт-код JVM здесь

следует отметить: LLVM использует промежуточный формат (IF), который хранится в уплотненная форма также известный как байт-код. Это позволяет выполнять оптимизацию машинного нейтрального кода analysizs перед генерацией собственного кода


сборка: Читаемые человеком инструкторы к ассемблеру + байты данных + операторы

машинный код: Фактические битовые последовательности, которые понимает CPU.

содержит:

  • операции,
  • какие регистры использовать,
  • смещение от регистра ПК,
  • и аналогичная информация

байткод: Это код, прочитанный интерпретатором (большинство реализаций java на самом деле являются интерпретатором что считывает байт-код и использует его для выбора последовательность машинного кода для фактического выполнения CPU). Байт-код часто используется для того, чтобы один и тот же исходный код работал на нескольких разных ЦПУ.

опкод: Первый (или два) байта машинного кода. Он действует как селектор чтобы сообщить CPU, какую последовательность микрокода CPU он должен выполнить (что-то вроде оператора switch в C)

микрокода: Последовательности команд в процессоре, которые используются для выполнить машинный код.
Существует множество последовательностей микрокода, по крайней мере одна последовательность для каждой операции. В общем, остальная часть машинного кода - это просто параметры к последовательности микрокода, выбранной кодом операции каждая последовательность микрокода содержит инструкции к открыть / закрыть ворота, данные часов, передать информацию в / из аккумулятора и т. д.