10.2.4 Загрузка файла bFLT

Предыдущая  Содержание  Следующая V*D*V

Загрузка простого ("flat") файлового формата обрабатывается в функции load_flat_file. Шаги в процедуре загрузки следующие:

 

1.Сначала функция читает разные поля заголовка и рассчитывает необходимую память и тип требующейся загрузки.
 
text_len  = ntohl(hdr->data_start);
data_len  = ntohl(hdr->data_end) - ntohl(hdr->data_start);
bss_len   = ntohl(hdr->bss_end) - ntohl(hdr->data_end);
stack_len = ntohl(hdr->stack_size);
 
if (extra_stack) {
 stack_len += *extra_stack;
 *extra_stack = stack_len;
}
relocs = ntohl(hdr->reloc_count);
flags  = ntohl(hdr->flags);
rev    = ntohl(hdr->rev);
   …
   …
/*
* рассчитываем дополнительное пространство,
* необходимое для отображения
*/
extra = max(bss_len + stack_len, relocs *
       sizeof(unsigned long));

 

2.Затем загрузчик отображает разделы файла в оперативную память или берёт их из флеш-памяти на основе установленных флагов в заголовке файла. Например, если файл имеет разделы со сжатыми данными или текстом, то загрузчик должен сначала считать такой файл в память и распаковать эти разделы. Распечатка 10.1 показывает каждый логический шаг в этом процессе.
 

3.Загрузчик настраивает структуру задачи процесса, указывая информацию о местонахождении стека, данных и текста.
 
current->mm->start_code = start_code;
current->mm->end_code = end_code;
current->mm->start_data = datapos;
current->mm->end_data = datapos + data_len;

 

4.Наконец, он выполняет модификацию адресов, как показано в Распечатке 10.2.

 

Проверка на наличие GOT производится с помощью переменной flags, полученной ранее из заголовка файла bFLT. Также отметим, что datapos является началом смещением таблицы GOT после отображения файла в память. Теперь загрузчику необходимо модифицировать каждую запись, присутствующую в GOT. Функция calc_reloc делает необходимое исправление адреса, как показано в следующем фрагменте:

 

static unsigned long

calc_reloc(unsigned long r, struct lib_info *p, int curid, int internalp)

{

    …

    …

  if (r < text_len) /* В текстовом сегменте */

    addr = r + start_code;

  else /* В сегменте данных */

    addr = r - text_len + start_data;

 

  return addr;

}

 

Расчёт прост. Если адрес, который должен быть модифицирован, находится в пределах текстового сегмента, то к нему добавляется начальный адрес текстового сегмента. В противном случае это адрес в сегменте данных и, следовательно, модификация адреса осуществляется на основе начального адреса сегмента данных. После модификации записей GOT, записи таблицы переадресации проходят и приводятся в порядок через функцию calc_reloc. Чтобы понять тонкости перемещения, рассмотрим пример.

 

Случай FRB (полностью перемещаемых бинарных файлов)

 

Создадим файл sample.c с пустой функцией main.

 

Sample.c

 

main {}

 

Компилируем и создаём файл типа "flat". О том, как компилировать файлы FRB с корректными параметрами компилятора, смотрите Раздел 10.7.1. Пусть выходной файл называется sample, а файл символов будет symbol.gdb. Заголовок файла  "flat"  выведен с использованием программы flthdr.

 

#flthdr sample

 

  Magic:        bFLT

  Rev:          4

  Entry:        0x48

  Data Start:   0x220

  Data End:     0x280

  BSS End:      0x290

  Stack Size:   0x1000

  Reloc Start:  0x280

  Reloc Count:  0x1c

  Flags:        0x1 ( Load-to-Ram )

 

Вывод может быть напрямую соотнесён с заголовком bFLT, описанным выше. Поскольку это перемещаемый бинарный файл, обратите внимание на установленный в заголовке флаг Load-to-RAM (загрузка в память). Также Reloc Count равен 0x1C, то есть для перемещения пустого файла main было создано 28 записей. Чтобы разгадать эту тайну, давайте посмотрим на файл символов, symbol.gdb.

 

# nm sample.gdb

  00000004 T _stext

  00000008 T _start

  00000014 T __exit

  0000001a t empty_func

  0000001a W atexit

  0000001c T main

  00000028 T __uClibc_init

  0000004a T __uClibc_start_main

  000000ba T __uClibc_main

  000000d0 T exit

    …

    …

  00000214 D _errno

  00000214 V errno

  00000218 D _h_errno

  00000218 V h_errno

  0000021c d p.3

    …

    …

  00000240 B __bss_start

  00000240 b initialized.10

  00000240 B _sbss

  00000240 D _edata

  00000250 B _ebss

  00000250 B end

 

Эта распечатка показывает различные символы, находящиеся в файле. Поскольку любое приложение должно быть связано с libc, то в этой распечатке символов все выведенные символы, кроме 0x1c T main(), взяты из libc, в данном случае это uClibc. Данные и текст libc имеют в себе точки для перемещения, указанные в таблице переадресации: 28 записей для перерасчёта.

Теперь в файл sample.c добавим код.

 

Sample.c

 

int x=0xdeadbeef;

int *y=&x;

 

main () {

  *y++;

}

 

Снова скомпилируем и распечатаем заголовок файла.

 

#flthdr sample

 

  Magic:        bFLT

  Rev:          4

  Entry:        0x48

  Data Start:   0x220

  Data End:     0x280

  BSS End:      0x290

  Stack Size:   0x1000

  Reloc Start:  0x280

  Reloc Count:  0x1f

  Flags:        0x1 ( Load-to-Ram )

 

Обратите внимание на увеличение счётчика записей переадресации с 0x1c до 0x1f; созданы три дополнительные записи переадресации. В новом sample.c в разделе данных создана запись для перерасчёта адреса y, указывающего на x. Также увеличение содержимого y создаёт запись для перемещения в текстовом разделе.

Используя nm для данных sample.gdb получаем следующие адреса x и y:

 

  …

00000200 D x

00000204 D y

  …

 

В таблицу переадресации добавлена запись для y. Таблица содержит запись 204, которая должна быть перерасчитана. Также должен быть перерасчитан адрес, указываемый в 204 (то есть 200). Это то, что выполняется в Распечатке 10.2. Ниже приводится простая интерпретация кода перерасчёта адресов.

Сделаем распечатку sample с помощью od. Мы выводим здесь только таблицу переадресации, начиная с байта со смещением 280, как указано в заголовке этого файла.

 

#od –Ax –x sample –j280

 

  000280 0000 1e00 0000 2400 0000 2c00 0000 3600

  000290 0000 4000 0000 4600 0000 6400 0000 6c00

  0002a0 0000 7600 0000 7e00 0000 8c00 0000 9e00

    …

 

Теперь обратим внимание на вторую ненулевую запись 0x2400. Шаги, выполняемые для перерасчёта, следующие:

 

Шаг 1:
 
relval = ntohl(reloc[i]);
addr = relval;
 
0x2400 должно быть преобразовано в машинную последовательность байт и это 0x0024.

 

Шаг 2:
 
rp = (unsigned long *) calc_reloc(addr, libinfo, id, 1);
 
Это означает, что запись 0x0024 должна быть перерасчитана. calc_reloc вернёт перерасчитанный адрес, который будет  в момент выполнения, на основе начального адреса текста (поскольку 0x24 < text_len).

 

Шаг 3:
 
addr = *rp;
 
Содержимое (start_of_text + 0x0024) для файла sample на самом деле адрес y 0x204, который должен быть перерасчитан.

 

Шаг 4:
 
addr = calc_reloc(addr, libinfo, id, 0);
*rp=addr;
 
Вызов calc_reloc для 0x204 и запись в память актуального значения.

 

Чтобы подвести итог, на Рисунке 10.4 показаны различные этапы выполнения перерасчёта. Для текстовой ссылки также будет создана аналогичная запись.

 

Рисунок 10.4 Перерасчёт адресов файла типа "flat".

Рисунок 10.4 Перерасчёт адресов файла типа "flat".

 

Случай PIC

 

Снова возьмём файл sample.c с пустой функцией main.

 

Sample.c

 

main {}

 

Скомпилируем программу с включённым параметром PIC и изучим созданный код.

 

#flthdr sample

 

  Magic:        bFLT

  Rev:          4

  Entry:        0x48

  Data Start:   0x220

  Data End:     0x2e0

  BSS End:      0x2f0

  Stack Size:   0x1000

  Reloc Start:  0x2e0

  Reloc Count:  0x2

  Flags:        0x2 ( Has-PIC-GOT )

 

Обратите внимание, что есть только две записи для перерасчёта и поле flags указывает на наличие GOT. Напомним, что GOT присутствует в начале раздела данных с последней записью, указанной с помощью -1. Чтобы изучить содержимое нашего файла и увидеть наличие GOT, мы используем od.

 

#od –Ax –x sample

 

  000000 4662 544c 0000 0400 0000 4800 0000 2002

  000010 0000 e002 0000 f002 0000 0010 0000 e002

  000020 0000 0200 0000 0200 1342 678b 0000 0000

    …

    …

  000220 0000 0000 0000 0000 0000 0000 0000 6802

  000230 0000 7c02 0000 a002 0000 aa01 0000 7402

  000240 0000 6002 0000 6c02 0000 7002 0000 2001

  000250 0000 8802 0000 6402 0000 2800 0000 1c00

  000260 0000 0000 0000 0000 0000 0000 0000 c800

  000270 0000 0000 0000 0001 0000 4400 0000 0000

  000280 ffff ffff 0000 0000 0000 0000 0000 0000

  000290 0000 0000 0000 0000 0000 0000 0000 0000

    …

 

Обратим внимание на начало GOT по адресу 0x220, как указано в Data Start: 0x220, и конец GOT по адресу 0x280, определяемому по значению 0xFFFFFFFF, в общей сложности 16 действительных (ненулевых) записей GOT. Все эти записи GOT предназначены для стандартных символов libc.

Теперь в sample.c снова добавим наши строчки, скомпилируем и распечатаем заголовок.

 

Sample.c

 

int x=0xdeadbeef;

int *y=&x;

 

main () {

  *y++;

}

 

#flthdr sample

 

  Magic:        bFLT

  Rev:          4

  Entry:        0x48

  Data Start:   0x220

  Data End:     0x2e0

  BSS End:      0x2f0

  Stack Size:   0x1000

  Reloc Start:  0x2e0

  Reloc Count:  0x3

  Flags:        0x2 ( Has-PIC-GOT )

 

Сразу же замечаем увеличение счётчика Reloc на 1. Это перерасчёт для x по адресу в y. Как и ожидалось, ссылка на у в тексте создаёт запись в GOT. С помощью od сделаем распечатку sample.

 

000220 0000 0000 0000 0000 0000 0000 0000 7002

000230 0000 8402 0000 a002 0000 b601 0000 7c02

000240 0000 6802 0000 7402 0000 7802 0000 2c01

000250 0000 9002 0000 6c02 0000 3400 0000 1c00

000260 0000 6402 0000 0000 0000 0000 0000 0000

000270 0000 d400 0000 0000 0000 0c01 0000 5000

000280 ffff ffff 0000 0000 0000 0000 0000 0000

 

У нас есть в общей сложности 17 записей в GOT, то есть одна дополнительная запись. Нам необходимо определить ту запись GOT, которая соответствует y. Выполним nm sample.gdb и посмотрим переменные, как и раньше.

 

  …

00000260 D x

00000264 D y

  …

 

Выделенная жирным запись выше в таблице GOT показывает запись для y.

Загрузчик в Распечатке 10.2 выполняет проверку на флаг GOT и сначала перемещает записи GOT, а затем продолжает модифицировать записи. Рисунок 10.5 показывает, как перерасчитывается запись GOT.

 

Рисунок 10.5 Перерасчёт записи GOT.

Рисунок 10.5 Перерасчёт записи GOT.

 

Подводим итог нашего обсуждения в этом разделе.

 

uClinux использует формат файла bFLT (Binary FLAT) .

bFLT может быть либо FRB, либо PIC.

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

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

XIP предусматривает запуск файлов с флеш-памяти, экономя таким образом на текстовом пространстве, которое в противном случае занимало бы место в оперативной памяти

 

Предыдущая  Содержание  Следующая