Как адреса памяти размещаются в двоичных файлах? - PullRequest
0 голосов
/ 22 октября 2018

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

По сути, как и когда адреса помещаются в разделы и как они загружаются как в ОС, так и в ПЗУ или ОЗУ во встроенной системе.

1 Ответ

0 голосов
/ 22 октября 2018

В конкретной операционной системе есть определенный набор правил или, возможно, несколько наборов правил, для которых можно загрузить совместимую программу.Набор инструментов, включающий скрипт компоновщика по умолчанию (думаю, gcc hello.c -o hello), созданный для этой платформы, соответствует этим правилам.

Так, например, я решил создать операционную систему для платформы с MMU.Поскольку он имеет MMU, я могу создать операционную систему так, чтобы каждая программа видела одно и то же (виртуальное) адресное пространство.Поэтому я могу решить, что для приложений в моей операционной системе пространство памяти начинается с 0x00000000, но точка входа должна быть 0x00001000.Поддерживается двоичный формат файла: s-запись Motorola, скажем.

, поэтому возьмите простую программу с простым скриптом компоновщика

MEMORY
{
    ram : ORIGIN = 0x1000, LENGTH = 0x10000
}
SECTIONS
{
    .text : { *(.text*) } > ram
}

Разборка моей простой программы

00001000 <_start>:
    1000:   e3a0d902    mov sp, #32768  ; 0x8000
    1004:   eb000001    bl  1010 <main>
    1008:   e3a00000    mov r0, #0
    100c:   ef000000    svc 0x00000000

00001010 <main>:
    1010:   e3a00000    mov r0, #0
    1014:   e12fff1e    bx  lr

И «двоичный» файл оказывается читаемым человеком:

S00F00006E6F746D61696E2E737265631F
S3150000100002D9A0E3010000EB0000A0E3000000EF1E
S30D000010100000A0E31EFF2FE122
S70500001000EA

, и вы можете или не можете заметить, что адрес действительно находится в двоичном файле, описывающем, куда идут вещи.

Будучи программой на основе операционной системы, которая загружается в оперативную память, нам не нужно играть слишком много игр с памятью, мы можем предположить, что все оперативные памяти (чтение / запись) одинаковы, поэтому при наличии .data, .bss и т. Д. Все это может бытьупакован там.

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

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

Теперь, когда вы говорите, что встроенные и используютслова ROM и RAM Я предполагаю, что вы имеете в виду baremetal, как, например, микроконтроллер, но даже если вы имеете в виду загрузку x86 или полноразмерного ARM или что-то подобное.В случае MCU разработчики микросхем, возможно, в соответствии с правилами процессора или по своему выбору определили правила для области памяти.Также как операционная система будет диктовать правила.Мы немного обманываем, так как многие инструменты, которые мы используем сегодня (на основе gnu), на самом деле не предназначены для baremetal, но, поскольку универсальный компилятор является универсальным компилятором и, что более важно, инструментальная цепочка подходит для такого рода переносимости, мы можем использоватьтакие инструменты.В идеале использование кросс-компилятора означает, что машинный код вывода не обязательно предназначен для запуска на компьютере, генерирующем этот машинный код.Основное различие, которое имеет значение, состоит в том, что мы хотим управлять связыванием и библиотеками, не связывать их в библиотеках на основе операционной системы хоста и позволять нам контролировать или для этого набора инструментов иметь скрипт компоновщика по умолчанию, который нацелен на наш MCU.Допустим, у меня есть MCU на базе ARM7TDMI, и разработчики чипов говорят, что мне нужен двоичный файл, чтобы ПЗУ начиналось с адреса 0x00000000 и имело некоторый размер, а ОЗУ начиналось с 0x40000000 и имело некоторый размер.Будучи ARM7, процессор начинает выполнение, выбирая инструкцию по адресу 0x00000000, и разработчики микросхем сопоставили этот 0x00000000 с ПЗУ.

Итак, теперь моя простая программа

unsigned int xyz;
int notmain ( void )
{
    xyz=5;
    return(0);
}

, связанная так

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
}

, дает разборку этого

Disassembly of section .text:

00000000 <_start>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   eb000000    bl  10 <notmain>
   c:   eafffffe    b   c <_start+0xc>

00000010 <notmain>:
  10:   e3a02005    mov r2, #5
  14:   e59f3008    ldr r3, [pc, #8]    ; 24 <notmain+0x14>
  18:   e3a00000    mov r0, #0
  1c:   e5832000    str r2, [r3]
  20:   e12fff1e    bx  lr
  24:   40000000    andmi   r0, r0, r0

Disassembly of section .bss:

40000000 <xyz>:
40000000:   00000000    andeq   r0, r0, r0

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

Прежде всего, если вы не укажете _start, цепочка инструментов выдает предупреждение, но все равно работает нормально.(хм, на самом деле не предупреждал, что время, интересно)

arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin

А теперь у вас проблема с загрузкой.Каждый MCU отличается от того, как вы загружаете его, какие инструменты доступны и / или вы создаете свои собственные инструменты.Ihex и srec были популярны для программистов выпускного вечера, когда вы сказали, что отдельный диск рядом с вашим процессором и / или сквозное отверстие mcu будет подключено к программисту выпускного.Необработанные двоичные изображения тоже работают, но могут быстро стать большими, как будет показано за секунду.Как написано выше, есть .bss, но нет .data, поэтому

ls -al notmain.bin
-rwxr-xr-x 1 user user 40 Oct 21 22:05 notmain.bin

40 байтов.Но если я сделаю это для демонстрационных целей, даже если это не будет работать правильно:

unsigned int xyz=5;
int notmain ( void )
{
    return(0);
}

с

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
    .data : { *(.data*) } > ted
}

дает

Disassembly of section .text:

00000000 <notmain-0x10>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   eb000000    bl  10 <notmain>
   c:   eafffffe    b   c <notmain-0x4>

00000010 <notmain>:
  10:   e3a00000    mov r0, #0
  14:   e12fff1e    bx  lr

Disassembly of section .data:

40000000 <xyz>:
40000000:   00000005    andeq   r0, r0, r5

и

-rwxr-xr-x  1 user user 1073741828 Oct 21 22:08 notmain.bin

ОЙ!0x40000004 байта, что и ожидалось, я попросил образ памяти, который я определил для одного адреса (машинный код) и несколько байтов для другого (0x40000000), поэтому необработанный образ памяти должен быть целым диапазоном.

hexdump notmain.bin 
0000000 d101 e3a0 da01 e38d 0000 eb00 fffe eaff
0000010 0000 e3a0 ff1e e12f 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
*
40000000 0005 0000                              
40000004

Вместо этого можно было бы просто использовать файл elf, сгенерированный цепочкой инструментов, или, возможно, ihex или srecord.

S00F00006E6F746D61696E2E737265631F
S3150000000001D1A0E301DA8DE3000000EBFEFFFFEA79
S30D000000100000A0E31EFF2FE132
S3094000000005000000B1
S70500000000FA

всю необходимую мне информацию, но не огромный файл для стольких байт.

Не жесткое и быстрое правило, но перемещение данных сегодня легче (чем дискета с одного компьютера на другой с программистом выпускного вечера).И особенно, если у вас есть интегрированная среда разработки, в которой поставщик, вероятно, использует формат по умолчанию для цепочек инструментов, но даже если не поддерживаются elf и другие подобные форматы, и вам не нужно идти по пути простого двоичного файла, ihex или srec.Но это все еще зависит от инструмента, который берет «двоичный файл» и программирует его в ПЗУ (/ FLASH) на MCU.

Теперь я обманул, чтобы продемонстрировать описанную выше проблему с большими файлами, вместо этого вам придется выполнять больше работы, когда это не только система оперативной памяти.Если вы чувствуете необходимость иметь .data или хотите обнулить .bss, то вам нужно написать или использовать более сложный скрипт компоновщика, который поможет вам определить местоположение и границы.И этот скрипт компоновщика состоит в браке с загрузчиком, который использует информацию, сгенерированную компоновщиком для выполнения этих задач.В основном, копия .data должна быть сохранена в энергонезависимой памяти (ROM / FLASH), но она не может существовать там во время выполнения .data для чтения / записи, поэтому в идеале / обычно вы используете язык / магию сценариев компоновщика, чтобы утверждать, что.пространство для чтения / записи данных - бла, и пространство флэш-памяти увеличено по этому адресу и такому размеру, так что загрузчик может копировать с флэш-памяти по этому адресу для такого количества данных в оперативную память.А для .bss сценарий компоновщика генерирует переменные, которые мы сохраняем во флэш-памяти, которые сообщают начальной загрузке нулевой памяти от этого адреса до этого адреса.

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

РЕДАКТИРОВАТЬ -------------

основы набора инструментов ...

mov sp,#0x40000000
orr sp,sp,#0x1000
bl notmain
b .


unsigned int xyz;
int notmain ( void )
{
    xyz=5;
    return(0);
}

MEMORY
{
    bob : ORIGIN = 0x1000, LENGTH = 0x1000
    ted : ORIGIN = 0x2000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
}

мой загрузчик, основная программа и скрипт компоновщика

arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -save-temps -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin

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

Начальная загрузка создает объект, который мы можем разобрать.

00000000 <.text>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   ebfffffe    bl  0 <notmain>
   c:   eafffffe    b   c <.text+0xc>

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

сборка, сгенерированная компилятором для кода C

    .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
    .file   "notmain.c"
    .text
    .align  2
    .global notmain
    .type   notmain, %function
notmain:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    mov r2, #5
    ldr r3, .L2
    mov r0, #0
    str r2, [r3]
    bx  lr
.L3:
    .align  2
.L2:
    .word   xyz
    .size   notmain, .-notmain
    .comm   xyz,4,4
    .ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

, который собирается в объект, который мы также можем разобрать.

Disassembly of section .text:

00000000 <notmain>:
   0:   e3a02005    mov r2, #5
   4:   e59f3008    ldr r3, [pc, #8]    ; 14 <notmain+0x14>
   8:   e3a00000    mov r0, #0
   c:   e5832000    str r2, [r3]
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

Сейчас не показан, но этот объект также содержит информацию для глобальной переменной xyz и ее размера.

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

Disassembly of section .text:

00001000 <notmain-0x10>:
    1000:   e3a0d101    mov sp, #1073741824 ; 0x40000000
    1004:   e38dda01    orr sp, sp, #4096   ; 0x1000
    1008:   eb000000    bl  1010 <notmain>
    100c:   eafffffe    b   100c <notmain-0x4>

00001010 <notmain>:
    1010:   e3a02005    mov r2, #5
    1014:   e59f3008    ldr r3, [pc, #8]    ; 1024 <notmain+0x14>
    1018:   e3a00000    mov r0, #0
    101c:   e5832000    str r2, [r3]
    1020:   e12fff1e    bx  lr
    1024:   00002000    andeq   r2, r0, r0

Disassembly of section .bss:

00002000 <xyz>:
    2000:   00000000    andeq   r0, r0, r0

Я сделал этот скрипт компоновщика, чтобы вы могли видеть как .data, так и .bss.Компоновщик заполнил весь .text в адресное пространство 0x1000 и внес исправления в вызов notmain (), а также в способ получения xyz.Он также выделил / определил пространство для переменной xyz в адресном пространстве 0x2000.

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

...