У меня путаница при различении исходного кода, кода объекта, кода сборки и машинного кода

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

  1. Откуда получается ассемблерный код, если компиляторы напрямую преобразуют исходный код в машинный код?
  2. В чем разница между объектным кодом и машинным кодом?
  3. Кто преобразует исходный код в код сборки?
  4. Что такое язык высокого и низкого уровня, как их различать?
  5. Ассемблерный код и объектный код высокого уровня или низкого уровня?

2

Решение

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

  1. Обычно для компиляторов, использующих ассемблер, серверная часть создает временный файл asm, который ассемблер преобразует в объектный код. Если у вас есть доступ к GCC, вы можете увидеть цепочку команд, которые он использует с -v вариант. Например, для источника C

    int main(){ return 1; }

    команда

gcc -v -o test test.c

выходы (и я много фильтровал)

cc1 test.c -o /tmp/cc9Otd7R.s
as -v --64 -o /tmp/cc5KhWEM.o /tmp/cc9Otd7R.s
collect2 --eh-frame-hdr -m elf_x86_64 -o test /tmp/cc5KhWEM.o
  1. Для меня объектный код — это двоичный код, выводимый в формате, необходимом для машины и архитектуры ОС. Например, это может быть в формате ELF, размещенном в разделах. Машинный код — это просто двоичное представление ассемблера. Например, этот бит разборки

48 83 ec 10 sub rsp, 0x10

Первые четыре слова — это 4 байта машинного кода, за которыми следует ассемблер.

  1. Согласно пункту 1, это будет серверная часть компилятора.

  2. и 5. Это несколько субъективно, но сборка на низком уровне. Обычно вы не изменяете объектный код вручную (я иногда делал это с помощью шестнадцатеричного редактора, но такие изменения, как правило, очень малы)

3

Другие решения

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

векторы на ассемблере

.thumb

.globl _start
_start:
.word 0x20001000
.word reset
.word foo
.word foo
.word foo
.word foo
.word foo
.word foo

.thumb_func
reset:
bl fun
.thumb_func
foo:
b foo

.globl dummy
dummy:
bx lr

собрать, а затем разобрать

arm-none-eabi-as vectors.s -o vectors.o
arm-none-eabi-objdump -D vectors.o > vectors.list

связанная часть разборки

Disassembly of section .text:

00000000 <_start>:
0:   20001000
...

00000020 <reset>:
20:   f7ff fffe   bl  0 <fun>

00000024 <foo>:
24:   e7fe        b.n 24 <foo>

00000026 <dummy>:
26:   4770        bx  lr

.Words не являются инструкциями, это способ поместить данные в двоичный файл / вывод. В этом случае я создаю таблицу векторов. Дизассемблер пока не все показывает, остальное мы увидим. Ассемблер оставил заполнители, которые мы вскоре увидим для заполнения компоновщиком. Так вот как выглядит объект, сборка превратилась в машинный код. сборка bx lr, машинный код 0x4770

Из этого правила есть исключения, как правило, по определенным причинам, но, как правило, нет смысла компилировать компилятор непосредственно в машинный код. У вас должен быть ассемблер для цели, так что он уже есть, используйте его. Разработчику компилятора гораздо проще отлаживать код сборки, чем отлаживать машинный код. Есть некоторые исключения, есть «просто потому, что я хочу» вроде как почему вы взобрались на гору вместо того, чтобы ходить вокруг, «потому что она была там». И тут есть причина как раз вовремя, и некоторые другие. JIT должен быстрее перейти к машинному коду и / или с одним инструментом / библиотекой / драйвером / и т. Д. Итак, вы можете заметить, что пропустить этот шаг сложнее в разработке. часто вы можете проверить эту теорию, переименовав свой ассемблер (хотя нужно нажать на правильный двоичный файл, хотя тот, который вы запускаете в командной строке, может быть передовым для реального, на самом деле в случае с gcc, я думаю, что gcc программа, которую мы используем, просто фронт для cc1 и, возможно, другой программы или двух, а также ассемблер и компоновщик, все они созданы из gcc, если вы не скажете этого).

поэтому мы берем нашу простую программу ввода

#define FIVE 5
unsigned int more_fun ( unsigned int );
void fun ( void )
{
more_fun(FIVE);
}

компилировать

arm-none-eabi-gcc -mthumb -save-temps -O2 -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o > fun.list

первый временный процессор — это препроцессор, который берет #defines и #include и в основном избавляется от них, создавая файл, который будет отправлен компилятору

# 1 "fun.c"# 1 "<built-in>"# 1 "<command-line>"# 1 "fun.c"

unsigned int more_fun ( unsigned int );
void fun ( void )
{
more_fun(5);
}

Затем вызывается сам компилятор, который компилируется в язык ассемблера.

    .cpu arm7tdmi
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 2
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.code   16
.file   "fun.c".text
.align  2
.global fun
.code   16
.thumb_func
.type   fun, %function
fun:
push    {r3, lr}
mov r0, #5
bl  more_fun
@ sp needed
pop {r3}
pop {r0}
bx  r0
.size   fun, .-fun
.ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

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

Disassembly of section .text:

00000000 <fun>:
0:   b508        push    {r3, lr}
2:   2005        movs    r0, #5
4:   f7ff fffe   bl  0 <more_fun>
8:   bc08        pop {r3}
a:   bc01        pop {r0}
c:   4700        bx  r0
e:   46c0        nop         ; (mov r8, r8)

Теперь bl 0 еще не является реальным, more_fun — это внешняя метка, поэтому компоновщик должен будет зайти и исправить это, как мы скоро увидим.

more_fun.c та же история

исходный код

#define ONE 1
unsigned int more_fun ( unsigned int x )
{
return(x+ONE);
}

вход компилятора

# 1 "more_fun.c"# 1 "<built-in>"# 1 "<command-line>"# 1 "more_fun.c"

unsigned int more_fun ( unsigned int x )
{
return(x+1);
}

вывод компилятора (ввод ассемблера)

    .cpu arm7tdmi
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 2
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.code   16
.file   "more_fun.c".text
.align  2
.global more_fun
.code   16
.thumb_func
.type   more_fun, %function
more_fun:
add r0, r0, #1
@ sp needed
bx  lr
.size   more_fun, .-more_fun
.ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

разборка объекта (вывод ассемблера)

Disassembly of section .text:

00000000 <more_fun>:
0:   3001        adds    r0, #1
2:   4770        bx  lr

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

arm-none-eabi-ld -Ttext=0x2000 vectors.o fun.o more_fun.o -o run.elf
arm-none-eabi-objdump -D run.elf > run.list
arm-none-eabi-objcopy -O srec run.elf run.srecDisassembly of section .text:

00002000 <_start>:
2000:   20001000
2004:   00002021
2008:   00002025
200c:   00002025
2010:   00002025
2014:   00002025
2018:   00002025
201c:   00002025

00002020 <reset>:
2020:   f000 f802   bl  2028 <fun>

00002024 <foo>:
2024:   e7fe        b.n 2024 <foo>

00002026 <dummy>:
2026:   4770        bx  lr

00002028 <fun>:
2028:   b508        push    {r3, lr}
202a:   2005        movs    r0, #5
202c:   f000 f804   bl  2038 <more_fun>
2030:   bc08        pop {r3}
2032:   bc01        pop {r0}
2034:   4700        bx  r0
2036:   46c0        nop         ; (mov r8, r8)

00002038 <more_fun>:
2038:   3001        adds    r0, #1
203a:   4770        bx  lr

компоновщик скорректировал внешнюю метку, в этом случае изменив инструкцию для правильного смещения.

   4:   f7ff fffe   bl  0 <more_fun>
202c:   f000 f804   bl  2038 <more_fun>

Формат файла elf — это один тип «двоичного» файла, он двоичный, в котором вы открываете его с помощью текстового редактора, вы видите текст, но в основном мусор. Существуют другие «двоичные» форматы файлов, такие как s-запись motorola, которая в данном случае включает только реальные данные, машинный код и любые данные, где у эльфа есть отладочная информация, такая как строки «fun», «more_fun» и т. Д., Которые дизассемблер использовался, чтобы сделать вывод немного красивее. Motorola S-Record и Intel Hex являются такими форматами файлов ascii:

S00B000072756E2E73726563C4
S113200000100020212000002520000025200000D1
S113201025200000252000002520000025200000A8
S113202000F002F8FEE7704708B5052000F004F858
S10F203008BC01BC0047C04601307047EA
S9032000DC

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

Еще до того, как компиляторы были доступны по разным причинам (как стоимость покупки и / или место для хранения программы на вашем компьютере, так и промежуточные данные и т. Д.), Ассемблеры можно было использовать для создания целой программы. Вы видите такие директивы, как .org 100h, с «цепочкой инструментов» ассемблер может иметь эту функцию, но как часть цепочки инструмент ассемблера должен перейти с языка ассемблера к формату объекта, большую часть преобразования в машинный код и другие данные. Конечно, возможно, что компилятор мог бы выполнить всю работу и вывести готовый двоичный файл, когда в рамках цепочки инструментов вменяемый метод в конечном итоге должен перейти из исходного кода на язык ассемблера. Инструменты компилятора, к которым мы привыкли, gcc, msvc, clang и т. Д., Если не указано иное, будут порождать для нас ассемблер и компоновщик, а также компилятор, создающий впечатление, что компилятор перешел от исходного кода к окончательному двоичному файлу за один магический шаг , Компоновщик берет отдельные объекты, у которых некоторые имеют неразрешенные внешние метки, и решает, куда в образе памяти, где в памяти они будут перемещаться, разрешая внешние по мере необходимости. То, как много делает компоновщик, является очень важной частью конструкции системы для этих инструментов. Конструкция может быть такой, что компоновщик не изменяет отдельные инструкции, он только размещает адреса в согласованных местах. Пример этого:

vectors.s

.globl _start
_start:
bl fun
b .
.global hello
hello: .word 0

fun.c

#define FIVE 5
extern unsigned int hello;
void fun ( void )
{
hello+=FIVE;
}

разборка fun.o

Disassembly of section .text:

00000000 <fun>:
0:   e59f200c    ldr r2, [pc, #12]   ; 14 <fun+0x14>
4:   e5923000    ldr r3, [r2]
8:   e2833005    add r3, r3, #5
c:   e5823000    str r3, [r2]
10:   e12fff1e    bx  lr
14:   00000000    andeq   r0, r0, r0

так что мы можем видеть, что он загружает из смещения / адреса 0x14 число в r2
затем этот номер используется в качестве адреса, чтобы получить привет, то, что было прочитано
имеет 5 добавленных к нему, то адрес в r2 используется для сохранения привет назад
объем памяти. Таким образом, то, что находится в 0x14, является местозаполнителем, оставленным компилятором, чтобы компоновщик мог поместить туда адрес hello, который мы видим, как только он будет связан

Disassembly of section .text:

00002000 <_start>:
2000:   eb000001    bl  200c <fun>
2004:   eafffffe    b   2004 <_start+0x4>

00002008 <hello>:
2008:   00000000    andeq   r0, r0, r0

0000200c <fun>:
200c:   e59f200c    ldr r2, [pc, #12]   ; 2020 <fun+0x14>
2010:   e5923000    ldr r3, [r2]
2014:   e2833005    add r3, r3, #5
2018:   e5823000    str r3, [r2]
201c:   e12fff1e    bx  lr
2020:   00002008    andeq   r2, r0, r8

0x2020 теперь содержит адрес hello, компилятор построил программу так, чтобы этот адрес мог легко заполнить компоновщик, а компоновщик заполнил его. Конечно, это можно сделать с помощью адресов переходов / переходов, а также разных цепочек инструментов или других цели из одних и тех же инструментов будут давать разные решения, это обычно связано с набором инструкций. У вас есть один с ближним (относительным) и дальним (абсолютным) вызовами, компилируете ли вы внешние с дальним, чтобы он всегда работал? Или вы рискуете и готовитесь к ближайшему вызову и рискуете, что линкер должен поставить батут?

Не совсем то, но я могу заставить gcc сделать это для большого пальца / руки довольно легко.

.thumb
.globl _start
_start:
bl fun
b .
.global hello
hello: .word 0#define FIVE 5
extern unsigned int hello;
void fun ( void )
{
hello+=FIVE;
}

разборка связанного бинарного файла

00002000 <_start>:
2000:   f000 f812   bl  2028 <__fun_from_thumb>
2004:   e7fe        b.n 2004 <_start+0x4>

00002006 <hello>:
2006:   00000000    andeq   r0, r0, r0
...

0000200c <fun>:
200c:   e59f200c    ldr r2, [pc, #12]   ; 2020 <fun+0x14>
2010:   e5923000    ldr r3, [r2]
2014:   e2833005    add r3, r3, #5
2018:   e5823000    str r3, [r2]
201c:   e12fff1e    bx  lr
2020:   00002006    andeq   r2, r0, r6
2024:   00000000    andeq   r0, r0, r0

00002028 <__fun_from_thumb>:
2028:   4778        bx  pc
202a:   46c0        nop         ; (mov r8, r8)
202c:   eafffff6    b   200c <fun>

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

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

1

Все, кроме исходного кода, являются языками низкого уровня.

Я считаю, что объект и машинный код относятся к одному и тому же.

Прямого преобразования исходного кода в ассемблерный не существует, поскольку исходный код обычно преобразуется непосредственно в машинный код. Ассемблер может использоваться для преобразования ассемблерного кода в машинный код (язык ассемблера имеет соотношение 1: 1 с машинным кодом). Компилятор используется для преобразования исходного кода непосредственно в машинный код.

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

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

Низкоуровневый язык — это язык, в котором практически отсутствует абстракция из набора команд компьютера.

-1
По вопросам рекламы [email protected]