Статически определенная IDT
Я работаю над проектом, который имеет жесткие требования времени загрузки. Целевой архитектурой является процессор на базе IA-32, работающий в 32-битном защищенном режиме. Одна из выявленных областей, которые могут быть улучшены, заключается в том, что текущая система динамически инициализирует IDT процессора (таблица дескрипторов прерываний). Поскольку у нас нет никаких устройств plug-and-play и система относительно статична, я хочу использовать статически встроенную IDT.
Однако это оказывается проблематичным для арки IA-32, поскольку 8-байтовые дескрипторы шлюза прерывания разделяют адрес ISR. Младшие 16 битов ISR появляются в первых 2 байтах дескриптора, некоторые другие биты заполняют следующие 4 байта, и, наконец, последние 16 битов ISR появляются в последних 2 байтах.
Я хотел использовать массив const для определения IDT, а затем просто указать на него регистр IDT следующим образом:
typedef struct s_myIdt {
unsigned short isrLobits;
unsigned short segSelector;
unsigned short otherBits;
unsigned short isrHibits;
} myIdtStruct;
myIdtStruct myIdt[256] = {
{ (unsigned short)myIsr0, 1, 2, (unsigned short)(myIsr0 >> 16)},
{ (unsigned short)myIsr1, 1, 2, (unsigned short)(myIsr1 >> 16)},
и т.п.
Очевидно, что это не сработает, поскольку в C это запрещено, потому что myIsr не является константой. Его значение определяется компоновщиком (который может выполнять только ограниченное количество математических операций), а не компилятором.
Любые рекомендации или другие идеи о том, как это сделать?
Спасибо,
1 ответ
Вы столкнулись с хорошо известной бородавкой x86. Я не верю, что компоновщик может заполнить адрес ваших подпрограмм isr в закрученной форме, ожидаемой записью IDT.
Если вы чувствуете себя честолюбивым, вы можете создать сценарий IDT-компоновщика, в котором используется нечто подобное (на основе Linux). Я не проверял эту схему, и она, вероятно, в любом случае квалифицируется как противный взлом, поэтому действуйте осторожно.
Шаг 1: Напишите скрипт для запуска "nm" и запишите стандартный вывод.
Шаг 2: В вашем скрипте проанализируйте вывод nm, чтобы получить адрес памяти всех ваших подпрограмм обработки прерываний.
Шаг 3: Выведите двоичный файл 'idt.bin', в котором все байты IDT настроены и готовы к выполнению инструкции LIDT. Ваш скрипт, очевидно, выводит адреса isr в правильной закрученной форме.
Шаг 4: Преобразуйте его сырой двоичный файл в раздел эльфов с помощью objcopy:
objcopy -I binary -O elf32-i386 idt.bin idt.elf
Шаг 5: Теперь в файле idt.elf есть ваш двоичный файл IDT с таким символом:
> nm idt.elf
000000000000000a D _binary_idt_bin_end
000000000000000a A _binary_idt_bin_size
0000000000000000 D _binary_idt_bin_start
Шаг 6: перекомпоновайте ваш бинарный файл, включая idt.elf. В своих заглушках сборок и сценариях компоновщика вы можете ссылаться на символ _binary_idt_bin_start в качестве основы IDT. Например, ваш скрипт компоновщика может разместить символ _binary_idt_bin_start по любому адресу, который вам нравится.
Будьте осторожны, чтобы перекомпоновка с разделом IDT больше не двигала ничего в вашем двоичном файле, например, в ваших подпрограммах isr. Управляйте этим в своем скрипте компоновщика (файл.ld), поместив IDT в отдельный раздел.
--- РЕДАКТИРОВАТЬ --- Из комментариев, кажется, путаница по поводу проблемы. 32-битный x86 IDT ожидает, что адрес подпрограммы обработки прерывания будет разбит на два разных 16-битных слова, например так:
31 16 15 0 + --------------- + --------------- + | Адрес 31-16 | | +---------------+---------------+ | | Адрес 15-0 | +---------------+---------------+
Таким образом, компоновщик не может подключить адрес ISR как обычное перемещение. Таким образом, во время загрузки программное обеспечение должно создать этот разделенный формат, который замедляет время загрузки.
Вы могли бы сделать что-то вроде этого:
main.c:
#include <stdint.h>
#include <stdio.h>
void isr0 ();
struct idt_entry
{
uint32_t idt_a;
uint32_t idt_b;
};
extern char idt_a_0;
extern char idt_b_0;
struct idt_entry idt0 = {
(uint32_t)&idt_a_0,
(uint32_t)&idt_b_0
};
int main ()
{
printf ("isr0: %08x\n", &isr0);
printf ("%08x\n", ((uint16_t*)&idt0)[0]);
printf ("%08x\n", ((uint16_t*)&idt0)[1]);
printf ("%08x\n", ((uint16_t*)&idt0)[2]);
printf ("%08x\n", ((uint16_t*)&idt0)[3]);
return 0;
}
link.ld:
seg_selector = 1;
other_bits = 2;
isr0_lo = isr0 & 0xFFFF;
isr0_hi = isr0 >> 16;
idt_a_0 = (seg_selector << 16) | isr0_lo;
idt_b_0 = (isr0_hi << 16) | other_bits;
isr0.c:
void isr0 ()
{
}
Makefile:
CFLAGS=-m32
main: main.o isr0.o link.ld
gcc -m32 -Wl,link.ld -o $@ $^
main.o: main.c
isr0.o: isr0.c