Как вызвать шестнадцатеричные данные, хранящиеся в массиве со встроенной сборкой? - PullRequest
2 голосов
/ 20 мая 2019

У меня есть проект ОС, над которым я работаю, и я пытаюсь вызвать данные, которые я прочитал с диска, в C с помощью встроенной сборки.

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

void driveLoop() {
    uint16_t sectors = 31;
    uint16_t sector = 0;
    uint16_t basesector = 40000;
    uint32_t i = 40031;
    uint16_t code[sectors][256];
    int x = 0;
    while(x==0) {
        read(i);
        for (int p=0; p < 256; p++) {
            if (readOut[p] == 0) {
            } else {
                x = 1;
                //kprint_int(i);
            }
        }
        i++;
    }
    kprint("Found sector!\n");
    kprint("Loading OS into memory...\n");
    for (sector=0; sector<sectors; sector++) {
        read(basesector+sector);
        for (int p=0; p<256; p++) {
            code[sector][p] = readOut[p];
        }
    }
    kprint("Done loading.\n");
    kprint("Attempting to call...\n");
    asm volatile("call (%0)" : : "r" (&code));

Когда вызывается встроенная сборка, я ожидаю, что она будет запускать код из секторов, которые я читаю с «диска» (это в ВМ, потому что это хобби ОС). Вместо этого он просто зависает.

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

РЕДАКТИРОВАТЬ: данные, которые я читаю с диска, является двоичным файлом, который был добавлен в файл образа диска с

cat kernel.bin >> disk.img

и kernel.bin скомпилирован с

i686-elf-ld -o kernel.bin -Ttext 0x4C4B40 *insert .o files here* --oformat binary

1 Ответ

1 голос
/ 20 мая 2019

Вместо этого он просто зависает.

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

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


asm volatile("call (%0)" : : "r" (&code)); небезопасен из-за отсутствия клобуферов.

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

У вас есть %0 в скобках, так что это режим адресации. Ассемблер предупредит вас о косвенном вызове без *, но соберет его как call *(%eax), с EAX = адрес code[0][0]. Вы действительно хотите call *%eax или любой другой регистр, выбранный компилятором, регистр-косвенный, а не память-косвенный.

&code и code являются просто указателями на начало массива; &code не создает объект анонимного указателя, хранящий адрес другого адреса. &code принимает адрес массива в целом. code в этом контексте «разлагается» на указатель на первый объект.


https://gcc.gnu.org/wiki/DontUseInlineAsm (для этого).

Вы можете заставить компилятор выдавать инструкцию call, приведя указатель к указателю на функцию.

   __builtin___clear_cache(&code[0][0], &code[30][255]);   // don't optimize away stores into the buffer
   void (*fptr)(void) =  (void*)code;                     // casting to void* instead of the actual target type is simpler

   fptr();

Это скомпилирует (с включенной оптимизацией) что-то вроде lea 16(%esp), %eax / call *%eax для 32-битного x86, потому что ваш буфер code[][] является массивом в стеке.

Или, чтобы вместо этого он испускал jmp, сделайте это в конце функции void, или return funcptr(); в функции без void, чтобы компилятор мог оптимизировать вызов / ret в jmp tailcall.

Если он не возвращается, вы можете объявить его с помощью __attribute__((noreturn)).


Убедитесь, что страница / сегмент памяти является исполняемой . (Ваш uint16_t code[]; является локальным, поэтому gcc выделит его в стеке. Это может быть не то, что вам нужно. Размер - это константа времени компиляции, поэтому вы можете сделать это static, но если вы сделаете это для других массивов в других одноуровневых функциях (не родительских или дочерних), то вы теряете возможность повторно использовать большой кусок памяти стека для разных массивов.)

Это намного лучше, чем ваш небезопасный встроенный ассм. (Вы забыли "memory" clobber, так что ничто не говорит компилятору, что ваш asm действительно читает указанную память). Кроме того, вы забыли объявить любые взломщики регистров; предположительно, загруженный вами блок кода будет перекрывать некоторые регистры, если он вернется, если только он не записан для сохранения / восстановления всего.

В GNU C вам необходимо использовать __builtin__clear_cache при приведении указателя данных к указателю на функцию . На x86 он на самом деле не очищает кеш, он сообщает компилятору, что хранилища в этой памяти не мертвы, потому что он будет читаться при выполнении. См. Как работает __builtin___clear_cache?

Без этого gcc может оптимизировать копирование в uint16_t code[sectors][256];, потому что это похоже на мертвое хранилище. (Точно так же, как с вашим текущим встроенным asm, который запрашивает только указатель в регистре.)

В качестве бонуса эта часть вашей ОС становится переносимой на другие архитектуры, в том числе такие, как ARM, без согласованного кэша команд, где встроенная функция расширяется до реальных инструкций. (На x86 это чисто влияет на оптимизатор).


read(basesector+sector);

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

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

char code[sectors * 512]; было бы хорошо.

...