Пользовательский распределитель памяти для реального режима DOS .COM (отдельно стоящий) - как отлаживать?

Сначала немного предыстории: наткнувшись на этот пост в блоге, я узнал, что можно создать DOS .COM файлы с компоновщиком GNU, и это даже не ракетостроение. С помощью clang и -m16 переключатель (создание 32-битного кода, совместимого с реальным режимом, с соответствующим префиксом 32-битных инструкций), это сработало довольно хорошо. Так что у меня возникла идея попробовать реализовать достаточно времени выполнения, чтобы получить небольшую игру с проклятиями, которую я недавно написал для компиляции в .COM и запустить в реальном режиме DOS. Игра достаточно мала, так что сжатие всего (текст, данные, bss, куча, стек) в 64 КБ казалось выполнимым. Конечно, он использует malloc(), Поэтому мне пришлось придумать свою реализацию. Вот как это выглядит:

typedef unsigned short size_t; /* from stddef.h */

typedef struct hhdr hhdr;
struct hhdr
{
    void *next;
    int free;
};

extern char _heap;
static char *hbreak = &_heap;
static hhdr hhead = { &_heap, 0 };

static void *newchunk(size_t size)
{
    char *stack;
    __asm__("mov %%esp, %0": "=rm" (stack));
    if (hbreak + size > stack - 0x40) return 0;
    if (size < 1024) size = 1024;
    hhdr *chunk = (hhdr *)hbreak;
    hbreak += size;
    if (hbreak > stack - 0x40) hbreak = stack - 0x40;
    chunk->next = hbreak;
    chunk->free = 1;
    return chunk;
}

void *malloc(size_t size)
{
    if (!size) return 0;
    if (size % sizeof(hhdr)) size += sizeof(hhdr) - (size % sizeof(hhdr));

    hhdr *hdr = &hhead;
    while ((char *)hdr->next < hbreak)
    {
        hdr = hdr->next;
        if (hdr->free && 
                (char *)hdr->next - (char *)hdr - sizeof(hhdr) >= size)
        {
            if ((char *)hdr->next - (char *)hdr - 2*sizeof(hhdr) > size)
            {
                hhdr *hdr2 = (hhdr *)((char *)hdr + sizeof(hhdr) + size);
                hdr2->free = 1;
                hdr2->next = hdr->next;
                hdr->next = hdr2;
            }
            hdr->free = 0;
            return (char *)hdr + sizeof(hhdr);
        }
    }

    if (!(hdr->next = newchunk(size + sizeof(hhdr)))) return 0;
    return malloc(size);
}

void free(void *ptr)
{
    if (!ptr) return;
    hhdr *hdr = (hhdr *)((char *)ptr - sizeof(hhdr));
    hdr->free = 1;
    if ((void *)hdr != hhead.next)
    {
        hhdr *hdr2 = hhead.next;
        while (hdr2->next != hdr) hdr2 = hdr2->next;
        if (hdr2->free) hdr = hdr2;
    }
    hhdr *next = hdr->next;
    while ((char *)next < hbreak)
    {
        if (!next->free) break;
        hdr->next = next;
        next = next->next;
    }
    if ((char *)next == hbreak) hbreak = (char *)hdr;
}

_heap Символ определяется компоновщиком. Не показывать realloc() здесь, так как он сейчас не используется (и, следовательно, полностью не проверен).

Теперь проблема в том, что я создал свою среду выполнения (malloc находится в src/libdos/stdlib.c), написал множество тестовых материалов, и в конце все, казалось, работало довольно хорошо. С другой стороны, моя игра тщательно протестирована и проверена на недопустимый доступ к памяти с помощью valgrind, Тем не менее, соединяя обе части, он просто падает. (Попробуйте собрать игру из Git с make -f libdos.mk, вам нужно будет установить llvm/clang).

Поскольку я впервые столкнулся со странным heisenbugобходил его сейчас), я полагаю, что это МОЖЕТ быть ошибка оптимизаторов, которые могут ошибаться при компиляции для реального режима, что действительно редко встречается. Но я не уверен, и следующим чувствительным кандидатом, вероятно, будет управление моей памятью, см. Выше.

Теперь сложный вопрос: как бы я отладил такую ​​вещь? С моим собственным тестовым кодом это работает очень хорошо. Я не могу скомпилировать свою игру без оптимизации, потому что при этом она превысит 64 КБ. Какие-либо предложения? Или кто-то может заметить что-то явно не так с приведенным выше кодом?

1 ответ

Если это реальный режим DOS, я не уверен насчет старших бит esp. Что касается malloc()используйте память между ss:sp and 0xa000:0000память между вершиной стека и 640k граница Я не помню, выделяет ли MS-DOS всю область 640 КБ для программы.COM или нет. Есть два вызова DOS, INT 21H, ah = 04Ah releases memory, ah = 048H allocates memory, но я не помню, если это для.COM или.EXE программ.

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