Обработчик прерываний не работает на реальном компьютере

Я пишу подобную загрузчику программу, которая изменяет обработчик прерываний по умолчанию для прерывания клавиатуры (int 0x9). Он работает на bochs и qemu, но не на реальном компьютере, где он печатает "A" только один раз, а затем не реагирует на нажатие любой клавиши. (Он должен печатать символ как минимум дважды, один раз для нажатия и один для отпускания). Как я могу заставить программу работать правильно? Я использую GCC. Код ядра:

asm(".code16gcc\n");

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef struct __attribute__((__packed__)) FullAddr{
    ushort offset;
    ushort seg;
} FullAddr;

#define MAIN_CODE_START 0x9200

asm (
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    /*Linker places the start of the .data section at this address */
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw %ax, %ss\n\t"
    "movw $0xFFFB, %sp\n\t" 
    "jmp main"
);

void print_char(char c){
    asm volatile("int  $0x10" : : "a"(0x0E00 | c), "b"(7));
}

void get_int_addr(ushort interrupt, FullAddr *addr)
{
    asm volatile(
        "pushw %%ds\n\t"
        "movw %w3, %%ds\n\t"
        "movw (%w2), %w0\n\t"
        "movw 2(%w2), %w1\n\t"
        "popw %%ds"
        : "=c"(addr->offset), "=a"(addr->seg):"b"(interrupt*4),"a"(0)
    );
}

void set_int_addr(ushort interrupt, uint func){
    asm volatile(
        "cli\n\t"
        "pushw %%ds\n\t"
        "movw %w2, %%ds\n\t"
        "movw %w0, (%w1)\n\t"
        "movw %%cs, 2(%w1)\n\t"
        "popw %%ds\n\t"
        "sti"
        : : "c"(func-MAIN_CODE_START), "b"(interrupt*4), "a"(0):
    );
}

void wait(uint usec)
{
    asm volatile("int $0x15": : "a"(0x8600), "c"(usec>>16), "d"(usec&0xFFFF));
}

FullAddr addr;

void handler_func(){
    print_char('A');
}

void handler();
asm(
    "handler:\n\t"
    "pushal\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "ljmp *addr\n\t"
    "iret\n\t"
);

void main(){
    get_int_addr(9, &addr);
    set_int_addr(9, (uint)handler);
    while(1){
        wait(1000);
    }
}    

Полный проект можно скачать здесь, он включает в себя образ дискеты. Чтобы собрать его, запустите файлы build.sh и build_main.sh.

Обновление: я пробовал код @RossRidge - lcall инструкция переходит к 0xfe9e6 вместо 0xfe897 и возникает бесконечный цикл. handler код:

asm(
    "handler:\n\t"
    "pushw %ds\n\t"
    "pushal\n\t"
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "pushf\n\t"
    "lcall *addr\n\t"
    "popw %ds\n\t"
    "iret\n\t"
);

Update2: я понял, что я должен использовать lcallw вместо этого, но бесконечный цикл остается. После iret выполнение переходит к 0xfe9e6, а затем обратно к iret,

1 ответ

Решение

Ваша проблема, вероятно, ljmp *addr заявление. Место памяти addr относительно DS, но в обработчике прерываний это может быть что угодно. Поскольку он почти уверен, что обработчик будет вызываться в контексте кода BIOS, который обрабатывает int $0x15, %ah = 0x85DS будет установлен на то, что установлен в BIOS, а не на то, что установлен вашим кодом.

Простым решением было бы сделать расположение памяти относительно CS с ljmp *%cs:addr, но ваш код использует разные значения для CS и DS, так что это не будет работать. Вы должны действительно исправить это, чтобы они были одинаковыми, но если вам не удастся использовать что-то вроде ljmp *cs:addr-0x9200,

Другие вопросы по тегам