Как подсказал @jester, вы захотите установить начальную точку адреса виртуальной памяти (VMA). Линкер, который вы используете в Windows, использовал внутренний скрипт компоновщика, который устанавливает VMA> = 0x10000. Таким образом, любые ссылки на абсолютные адреса, которые не могут поместиться в 16-битном формате, вызовут ошибку перемещения. Вы не можете вставить адрес> = 0x10000 в 16-битный регистр, поэтому компоновщик прерывается с ошибкой, подобной следующей:
(. Text + 0x1f): перемещение усечено до соответствия: R_386_16 против ` .text '
Ошибка может немного отличаться, если вы используете 64-битную цепочку инструментов, но это будет что-то вроде R_???????_16 against '.text'
Чтобы исправить это, вы можете создать свой собственный скрипт компоновщика или задайте для базового VMA соответствующее значение в командной строке LD. Я бы порекомендовал использовать -Ttext=0x7c00
и установить DS в 0x0000 в своем загрузчике.
У меня есть General Bootloader советы, которые обсуждают многие проблемы написания загрузчика. Не следует предполагать, что когда ваш загрузчик работает, регистры сегментов имеют какое-то конкретное значение. Если вы используете -Ttext=0x7c00
в качестве VMA (ORG), вам нужно установить DS в ноль. Сегмент : смещение пара 0x0000: 0x7c00 = физический адрес 0x07c00 (0x0000 << 4 + 0x7c00). 0x07c00 - это место, где устаревший B IOS загрузит ваш загрузочный сектор в память. </p>
Если вы когда-либо получите ошибку перемещения, например:
(. Text + 0x1f): перемещение усечено чтобы соответствовать: R_386_16 против `.text '
Вы всегда можете использовать OBJDUMP для отображения записей перемещения в объектном файле. В этом случае, так как вы пишете 16-битный код, вам нужно, чтобы OBJDUMP выводил код (-D
); декодировать как 16-битные инструкции (-Mi8086
) и вывести записи перемещения (-r
). Вывод objdump -Mi8086 -Dr bootReal.o
будет выглядеть примерно так (он может варьироваться в зависимости от используемого компоновщика):
00000000 <_start>:
0: eb 1b jmp 1d <_boot>
00000002 <welcome>:
2: 48 dec %ax
3: 65 gs
4: 6c insb (%dx),%es:(%di)
5: 6c insb (%dx),%es:(%di)
6: 6f outsw %ds:(%si),(%dx)
7: 2c 20 sub $0x20,%al
9: 57 push %di
a: 6f outsw %ds:(%si),(%dx)
b: 72 6c jb 79 <_boot+0x5c>
d: 64 0a 0d or %fs:(%di),%cl
...
00000011 <.writeStringIn>:
11: ac lods %ds:(%si),%al
12: 08 c0 or %al,%al
14: 74 06 je 1c <.writeStringOut>
16: b4 0e mov $0xe,%ah
18: cd 10 int $0x10
1a: eb f5 jmp 11 <.writeStringIn>
0000001c <.writeStringOut>:
1c: c3 ret
0000001d <_boot>:
1d: 8d 36 02 00 lea 0x2,%si
1f: R_386_16 .text
21: e8 ed ff call 11 <.writeStringIn>
...
1fc: 00 00 add %al,(%bx,%si)
1fe: 55 push %bp
1ff: aa stos %al,%es:(%di)
При ошибке перемещения .text+0x1f
упоминается как источник проблемы. Если вы посмотрите на вывод OBJDUMP, то есть запись о перемещении:
1d: 8d 36 02 00 lea 0x2,%si
1f: R_386_16 .text
Это перемещение связано с инструкцией над ним. Это означает, что компоновщик попытался сгенерировать смещение для инструкции LEA
, но значение не может быть представлено в 16-битном значении. SI является 16-битным регистром и не может содержать в себе значение> = 0x10000.
Проблемы с кодом
- Вы хотите правильно установить DS в 0, если используется VMA (ORG) 0x7c00
- . Убедитесь, что строковые инструкции, такие как
lodsb
, перемещаются по строкам в прямом направлении. Используйте инструкцию CLD
, чтобы очистить флаг направления (DF = 0). - Обычно рекомендуется установить собственный указатель стека SS: SP . Это важно, если вы когда-нибудь прочитаете больше данных с диска в память. Вы не знаете, где установлен B IOS SS: SP , и вы не хотите его заглушить. Удобное место для установки стека находится чуть ниже загрузчика в 0x0000: 0x7c00.
- Чтобы предотвратить запуск в вашем загрузчике полуслучайного кода после последней инструкции, вы захотите поместить процессор в какое-то бесконечное число. oop. Проще всего было бы
jmp .
- LD сгенерирует исполняемый файл в формате, который не является двоичным файлом и не может быть запущен в качестве загрузчика. Вы можете заставить компоновщик написать двоичный файл с опцией
--oformat=binary
.
Пересмотренный код может выглядеть так:
#generate 16-bit code
.code16
#hint the assembler that here is the executable code located
.text
.globl _start;
#boot code entry
_start:
jmp _boot #jump to boot code
welcome: .asciz "Hello, World\n\r" #here we define the string
.macro mWriteString str #macro which calls a function to print a string
leaw \str, %si
call .writeStringIn
.endm
#function to print the string
.writeStringIn:
lodsb
orb %al, %al
jz .writeStringOut
movb $0x0e, %ah
int $0x10
jmp .writeStringIn
.writeStringOut:
ret
_boot:
xor %ax, %ax
mov %ax, %ds # Initialize the DS segment to zero
mov %ax, %ss # Set the stack pointer below bootloader at 0x0000:0x7c00
mov $0x7c00, %sp
cld # Clear Direction Flag (DF=0) to set forward movement on string
# instructions
mWriteString welcome
jmp . # Enter an infinite loop to stop executing code beyond this point
#move to 510th byte from the start and append boot signature
. = _start + 510
.byte 0x55
.byte 0xaa
Для сборки и запуска вы можете использовать что-то вроде :
as bootReal.s -o bootReal.o
ld -Ttext=0x7c00 --oformat=binary -o boot.bin bootReal.o
Альтернативой является генерация исполняемого файла с LD и использование OBJCOPY для преобразования исполняемого файла в двоичный файл:
as bootReal.s -o bootReal.o
ld -Ttext=0x7c00 -o file.tmp bootReal.o
objcopy -O binary file.tmp boot.bin
В любом случае должен быть создан 512-байтовый двоичный файл ( загрузчик) называется boot.bin
. Если вы запустите его с QEMU с qemu-system-i386 -fda boot.bin
, то результат будет выглядеть примерно так: