НейроАгент

Как заставить GCC генерировать чистый 16-битный код

Узнайте, почему GCC не может генерировать настоящий 16-битный код и как правильно настроить его для работы в реальном режиме процессора с использованием директив .code16gcc.

Вопрос

Как заставить GCC (i686) генерировать настоящий 16-битный код без использования 32-битных регистров вроде EAX, ECX и т. д.?

Я использую флаг -m16 в GCC, но сгенерированный код все равно использует 32-битные регистры. Как правильно настроить GCC для генерации чистого 16-битного кода, совместимого с реальным режимом процессора?

Вот мой Makefile и пример кода:

makefile
# Makefile

METAL=-m16 -nostdlib -ffreestanding

all: prepare
	i686-elf-as Bootloader.asm -o Build/Objects/Bootloader.o
	i686-elf-ld Build/Objects/Bootloader.o -o Build/Binaries/Bootloader \
		-Ttext 0x7C00 --oformat binary

	i686-elf-gcc -c Main.c -o Build/Objects/Main.o $(METAL)
	i686-elf-gcc -c Modules/*.c $(METAL) \
	&& mv *.o Build/Objects/Modules
	i686-elf-ld Build/Objects/Main.o Build/Objects/Modules/* -o Build/Binaries/Main \
		-Ttext 0x7E00 --oformat binary

	dd if=/dev/zero of=Build/Floppy.img \
		bs=1K count=360
	dd if=Build/Binaries/Bootloader of=Build/Floppy.img conv=notrunc
	dd if=Build/Binaries/Main of=Build/Floppy.img \
		seek=1 conv=notrunc
clean:
	-rm -r Build/{Objects,Binaries}
scrub:
	-rm -r Build

run:
	qemu-system-x86_64 -fda Build/Floppy.img

prepare:
	mkdir -p Build
	mkdir -p Build/{Objects,Binaries}
	mkdir -p Build/Objects/Modules
assembly
# Bootloader.asm

.code16
.globl boot

boot:
	movb $0, %ah
	movb $0x13, %al
	int $0x10

	movb $0x2, %ah
	movb $0x2, %al

	movw $0x7E00, %bx
	movb $0, %ch
	movb $2, %cl
	int $0x13

	jc error

	ljmp $0, $0x7E00

error:
	movb $0, %ah
	movb $3, %al
	int $0x10

	movb $0xE, %ah
	movb $'E', %al
	int $0x10

	cli
	hlt

.org 510
.word 0xAA55

При компиляции с флагом -m16 GCC все равно генерирует код с использованием 32-битных регистров, что не подходит для реального режима процессора. Как правильно настроить GCC для генерации чистого 16-битного кода?

НейроАгент

GCC не может генерировать настоящий чистый 16-битный код без использования 32-битных регистров, даже с флагом -m16. GCC является fundamentally 32-битным компилятором и не поддерживает генерацию настоящего 16-битного кода, хотя может производить код, который выполняется в 16-битном режиме с некоторыми ограничениями.

Содержание

Почему GCC не может генерировать чистый 16-битный код

GCC является 32-битным компилятором по своей архитектуре и не поддерживает генерацию настоящего 16-битного кода. Как указывается в источниках, “gcc cannot actually generate real 16 bit code” и “GCC is 32 bit compiler, it can’t generate 16 bit assembly”. Даже при использовании флага -m16 компилятор все равно производит 32-битные инструкции, которые могут выполняться в 16-битном режиме, но не являются чисто 16-битными.

Основная проблема заключается в том, что GCC внутренне использует 32-битную архитектуру и генерирует код, который требует процессора уровня 386 или выше для выполнения в реальном режиме.

Что на самом деле делает флаг -m16

С флагом -m16 GCC генерирует код, который:

  • Модифицирует инструкции для выполнения в 16-битном режиме
  • Добавляет префиксы (66h и 67h) для переключения между 16-битным и 32-битным режимами
  • Использует 32-битные регистры (EAX, ECX и т.д.) с префиксами
  • Требует процессора 386 или выше, так как не может работать на чисто 16-битных процессорах (8086/8088/80286)

Как отмечается в источниках, “it generates the same code as in 32 bit mode but with some adjustments to run in 16 bit mode” и “it still insists on using the 32-bit registers, even for 2 byte wide variables”.

Правильное использование директив сборки

Для правильной генерации 16-битного кода вам нужно использовать директивы сборки GNU AS:

Директива .code16

assembly
.code16

Эта директива переключает ассемблер в режим генерации чисто 16-битного кода. Все инструкции будут использовать 16-битные операнды и регистры.

Директива .code16gcc

assembly
.code16gcc

Эта директива специально разработана для кода, сгенерированного GCC. Она отличается от .code16 тем, что инструкции call, ret, enter, leave, push, pop, pusha, popa, pushf, popf по умолчанию имеют 32-битный размер.

Для вашего случая с C-кодом нужно добавить в начале каждого C-файла:

c
__asm__(".code16gcc\n");

Или создать заголовочный файл code16gcc.h:

c
// code16gcc.h
#ifndef _CODE16GCC_H_
#define _CODE16GCC_H_
__asm__(".code16gcc\n");
#endif

И затем включать его в ваши C-файлы:

c
#include "code16gcc.h"

Альтернативные решения для чистого 16-битного кода

1. Использование Open Watcom

Open Watcom нативно поддерживает 16-битную компиляцию и может генерировать настоящий 16-битный код без использования 32-битных регистров.

2. Использование Turbo C или Borland C++

Эти старые компиляторы были специально разработаны для 16-битного кода и могут генерировать чистый 16-битный код.

3. Комбинированный подход

Смешивать C-код (с ограничениями) и чистый ассемблер:

assembly
# pure16.asm
.code16
pure_16bit_function:
    ; чисто 16-битный код
    movw %ax, %bx
    ret
c
// main.c
__asm__(".code16gcc\n");

extern void pure_16bit_function(void);

void c_function(void) {
    // код с ограничениями
    pure_16bit_function();
}

Ограничения и требования к процессору

Ограничения GCC

  • Требуется процессор 386 или выше
  • Не работает на 8086/8088/80286
  • 32-битные регистры все равно используются с префиксами
  • Ограниченная поддержка типов данных (long ints и другие)

Требования к коду

c
// НЕ будет работать с чистым 16-битным кодом
long large_value = 0xFFFFFFFF;  // 32-битное значение
int* pointer = &some_variable;  // 32-битные указатели
c
// БУДЕТ работать с ограниченным 16-битным кодом
unsigned short value = 0xFFFF;  // 16-битное значение

Практические рекомендации

1. Обновите ваш Makefile

makefile
# Makefile

METAL=-m16 -ffreestanding -fno-stack-protector -fno-pic
ASFLAGS=-m16

# Добавьте директиву .code16gcc для C-файлов
%.o: %.c
	i686-elf-gcc -c $< -o $@ $(METAL) -include code16gcc.h

%.o: %.asm
	i686-elf-as $< -o $@ $(ASFLAGS)

2. Создайте правильный заголовочный файл

c
// code16gcc.h
#ifndef _CODE16GCC_H_
#define _CODE16GCC_H_
__asm__(".code16gcc\n");
#endif

3. Измените ваш C-код

c
// Main.c
#include "code16gcc.h"

// Используйте только 16-битные типы
unsigned short x = 0x1234;
unsigned char y = 0xAB;

// Избегайте 32-битных операций
void my_function(void) {
    __asm__("pushw %ax");     // вместо push %eax
    __asm__("popw %ax");      // вместо pop %eax
    // и т.д.
}

4. Используйте правильные директивы в ассемблере

assembly
# Bootloader.asm
.code16gcc  # вместо .code16 для совместимости с GCC

5. Альтернативный вариант - используйте ассемблер для критичного кода

Если вам нужен действительно чистый 16-битный код, напишите критичные части на чистом ассемблере:

assembly
# pure16.asm
.code16
pure_16bit_routine:
    movw %ax, %bx
    addw $1, %bx
    retw

И вызывайте его из C-кода.

Заключение

  1. GCC не может генерировать настоящий чистый 16-битный код - компилятор по своей архитектуре является 32-битным и не поддерживает генерацию чисто 16-битного кода.

  2. Флаг -m16 создает псевдо-16-битный код - он использует 32-битные инструкции с префиксами и требует процессора 386+.

  3. Для лучшей совместимости используйте .code16gcc - эта директива специально разработана для кода, сгенерированного GCC.

  4. Рассмотрите альтернативные компиляторы - для настоящего чистого 16-битного кода используйте Open Watcom, Turbo C или Borland C++.

  5. Смешивайте C и ассемблер - для критичных к производительности или совместимости участков кода используйте чистый ассемблер.

Реальная 16-битная совместимость с процессорами 8086/8088/80286 требует либо использования специализированных компиляторов, либо написания кода полностью на ассемблере.

Источники

  1. Stack Overflow - How to tell GCC to generate 16-bit code for real mode
  2. GNU Assembler Documentation - i386-16bit
  3. OSDev Forum - gcc 16bit
  4. Stack Overflow - gcc compiles 16 bit real time code with -m16 option
  5. dc0d32 Blog - Real mode in C with gcc