Пользовательский распределитель памяти для реального режима 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 программ.