В данном посте хочу рассказать как можно запись исполняемый код в загрузочный сектор не задев при этом служебной информации о фат12(которая используется на дискете). Для большей ясности рекомендую прочесть статью на харбе , прочитать про FAT а также на нашем сайте уже писалось по поводу загрузочного сектора.
И так приступим. Наш загрузчик должен быть компактен, чтоб его размер не превышал 256 байт(хотя можно и чуть больше, я в данном примере копирую в сектор 432 (1b0h) байта то есть такой размер кода может быть в загрузочном секторе , но реально 256 байт хватает с головой чтоб загрузить с дискеты файл и запустить его ). Для начала просто напишем код код который выводит на экран «Hello world!» . Для уменьшения кода я написал простенький макрос который используется в 2х местах _rwsecto, этот макрос как видите принимает два параметра, первый отвечает за запись/чтение второй буфер куда или от куда считывать.
view source
01 | macro _rwsector rw,buf{ |
02 | ;2 чтение, 3 запись |
03 | mov ah ,rw ; //функция чтения/записи |
04 | mov al ,1 ; //кол секторов |
05 | mov bx ,buf ; //буфер куда читаем |
06 | mov ch ,0 ; //номер дорожки |
07 | mov cl ,1 ; //номер сектора |
08 | mov dh ,0 ; //сторона головки |
09 | mov dl ,0 ; //сам дисковод А=0 B=1 |
10 | int 13h |
11 | } |
12 |
13 | org 100h ; //генерируем СОМ файл, загружаемый с 0x100 |
14 | use16 |
15 | jmp start |
16 | qwe db 0ch dup (0) |
17 | fun: |
18 | jmp pisem |
19 | ; //------------- |
20 | pisem: |
21 | mov ax ,07b3h |
22 | mov ds , ax |
23 | mov si ,msg |
24 | cld ; //направление для строковых команд |
25 | mov ah , 0x0E ; //номер функции BIOS |
26 | pnext: |
27 | lodsb ; //загружаем очередной символ в al |
28 | cmp al , '$' ; // $ символ означает конец строки |
29 | jz next |
30 | int 0x10 ; //вызываем функцию BIOS |
31 | jmp pnext |
32 | next: |
33 | |
34 | jmp $ ; // организовываем вечный цикл |
35 | ; //================== |
36 | msg db "Hello World!" , '$' ; переменная которую выведем на экран |
37 | ; //================== |
38 | start: ; // начало кода который записывает данные в загрузочный сектор |
39 | mov ax , cs ; |
40 | mov es , ax ; //также определяем es так как чтение кластера дискеты будет проходить по адресу es:ds |
41 | _rwsector 2,buf ; //макрос который считывает в буфер загрузочный сектор дискеты |
42 |
43 | ; //пишем сначала джамп а потом идем на +03аh и там наш код пишем |
44 | ; //просто после первых 3 байт идет описание дискеты оно занимает 0а3h , дальше все наше)) |
45 | ;/если эту информацию повредить то windows или dos не смогут определить какая там файловая |
46 | ; //система и предложат ее отформатировать |
47 | mov si ,buf |
48 | mov ah ,0ebh |
49 | mov [ si ], ah |
50 | mov ah ,03ch |
51 | mov [ si +1], ah |
52 | mov ah ,90h |
53 | mov [ si +2], ah |
54 | ; //далее пишем сам код |
55 | mov cx ,1b0h ; |
56 | mov di ,buf ; //куда писать будем |
57 | add di ,03eh ; //отступаем чтоб не повредить инфу о дискете |
58 | mov si ,fun ; //указываем начало кода который нужно вписать |
59 |
60 | rep movsb ; //и пишем |
61 | _rwsector 3,buf ; // затем преобразованный сектор пишем обратно на дискету |
62 |
63 | ;================ |
64 |
65 | mov ah ,0x4C ; //эта функция завершает программу |
66 | mov al ,0 ; //код возврата 0 |
67 | int 0x21 ; //вызываем ядро операционной системы |
68 | buf db 512 dup (0) ; // буфер для хранения информации считанной с сектора |
Данный код должен быть выполнен на os Windows или в DOSe с тем учетом что в ПК иметься дискета (3,5″ и объемом 1,4 Мб) под буквой А. Можно (даже рекомендуется) все делать на виртуальной машине. Затем перезагрузить машину(ПК) и загрузиться с дискетки. Если все сделали верно то должно вывести «Hello world!!» как на рисунке ниже:
ОС на ассемблере. Загрузочный сектор
Теперь давайте разберемся как загрузиться с дискеты а затем загрузить нужный нам файл в память и передать управление этому участку памяти куда мы загрузили наш файл.
Прежде всего давайте рассмотрим как вообще распределяется память в реальном режиме у ПК:
- от 00000h до A0000h основная память 640Кбайта ( векторы прерываний, ядро ос,программы и прочее )
- от A0000h до C0000h графика EGA
- от C0000h до D0000h видео память.
- от D0000h до F0000h системная область
- от F0000h до FFFFFh системное ПЗУ
Исходя из того что в самом начале памяти размещены векторы прерываний то мы будим грузиться в память по адресу 0800h. Теперь рассмотрим пример программы которая будет грузить в память с дискеты файл io.bin а затем выполнит его. Давайте сначала разберем код файла io.bin :
view source
01 | mov ax , cs |
02 | mov ds , ax |
03 | pisem: |
04 | mov si ,msg |
05 | print: |
06 | mov al ,[ si ] |
07 | cmp al ,0 |
08 | je next |
09 | |
10 | mov ah ,0x0e |
11 | int 0x10 |
12 | mov al ,[ si ] |
13 | |
14 | jmp pnext |
15 | next: |
16 | endfun: |
17 | jmp endfun |
18 | pnext: |
19 | inc si ; i |
20 | jmp print |
21 | msg db "files is load!" , 0 |
Пример кода программы которая будет писать в загрузчик код, который ищет файл и загружает его в оперативную память:
view source
001 | ;описываем макросы |
002 | macro _rwsector rw,kols,buf,nomd,noms,nomg,disk{ |
003 | ;2 чтение, 3 запись |
004 | mov ah ,rw ;функция чтения/записи |
005 | mov al ,kols ;кол секторов |
006 | mov bx ,buf ;буфер куда читаем ES: BX |
007 | mov ch ,nomd ;номер дорожки |
008 | mov cl ,noms ;номер сектора |
009 | mov dh ,nomg ;сторона головки |
010 | mov dl ,disk ;сам дисковод А=0 B=1 |
011 | int 13h |
012 | } |
013 |
014 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
015 | macro _searchfile buf,nfile,nomer{ |
016 | ;макрос ищет файл если нашел то возвращает его относительный адрес |
017 | ; |
018 | local yes |
019 | local no |
020 | local exit |
021 | local search |
022 | _rwsector 2,1,buf,0,2,1,nomer ; чтение каталога(списка) файлов |
023 | |
024 | mov cx ,512 |
025 | mov bx ,0 |
026 | |
027 | xor bx , bx |
028 | mov bx ,buf |
029 | |
030 | search: |
031 | mov si ,nfile |
032 | mov di , bx |
033 | cld |
034 | mov cx ,11 |
035 | repe CMPSB ;сравниваем пока строки равны, идет сравнение 6ти элементов |
036 | je yes |
037 | |
038 |
039 | add bx ,32 |
040 | |
041 | cmp bx ,buf+512 |
042 | jne search |
043 | |
044 | mov ax ,0 |
045 | |
046 | jmp exit |
047 | |
048 | yes: |
049 |
050 | |
051 | mov ah ,[ bx +27] |
052 | mov al ,[ bx +26] |
053 | |
054 | exit: |
055 | } |
056 |
057 | macro _nextsector sector,buf{;макрос смотрит таблицу относительных |
058 | ;секторов и возвращает значение по этому адресу(чтоб узнать, |
059 | ;конец файла или нет) |
060 | ;значение возвращаем в ах |
061 | ;метод читает относительный адрес в карте |
062 | ; ax - номер относительного сектора на карте |
063 | ;будем учитывать что все находится в буфере buf(карта) |
064 | local nechet |
065 | local next |
066 | push sector |
067 | |
068 | |
069 | ;загружаем карту сперва |
070 | _rwsector 2,9,buf,0,2,0,0 ; |
071 | |
072 | mov cx ,2 |
073 | xor dx , dx |
074 | |
075 | div cx |
076 | |
077 | pop bx |
078 | add ax , bx |
079 | add ax , dx |
080 | ;ах = относительный сектор |
081 | ;читаем карту диска и снова вычисляем относительный адрес |
082 | |
083 | push dx |
084 | push ax |
085 | |
086 | |
087 | pop bx ; номер кластера |
088 | pop dx |
089 | |
090 | |
091 | mov ah ,[buf+ bx -1];идет следом |
092 | mov al ,[buf+ bx -2] ;идет |
093 | |
094 | cmp dx ,1 |
095 | je nechet |
096 | ;если нет остачи то AB *C=*CAB если нет то A* BC= BCA* |
097 | |
098 | shr ax ,4 |
099 | |
100 | jmp next |
101 | ;--------- |
102 | nechet: |
103 | shl ax ,4 |
104 | shr ax ,4 |
105 | |
106 | |
107 | next: |
108 | ;грузим в память сектор |
109 |
110 | |
111 | |
112 | |
113 | } |
114 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
115 | ;================================== |
116 | macro _loadsector buf,nomer,disk{ |
117 | local ost |
118 | local next |
119 | push buf ;куда грузим |
120 | push nomer |
121 | xor dx , dx |
122 | |
123 | pop ax |
124 | add ax ,14 |
125 | mov cx ,18 |
126 | div cx |
127 | ; ah -остаток |
128 | |
129 | mov cl , dl |
130 | push cx |
131 | mov ah ,0 |
132 | mov cl ,2 |
133 | div cl |
134 | pop cx |
135 | cmp ah ,1 |
136 | je ost |
137 | mov dh ,1 |
138 | jmp next |
139 | ost: |
140 | mov dh ,0 |
141 | next: |
142 | |
143 | add al , ah |
144 | |
145 | mov ch , al |
146 | |
147 | |
148 | pop buf |
149 | mov ah ,2 ;функция чтения |
150 | mov al ,1 ;кол секторов |
151 | |
152 | |
153 | mov dl ,disk ;сам дисковод А=0 B=1 |
154 | int 13h |
155 | } |
156 |
157 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
158 | macro _loadfile2 fname,buf,fbuf{ |
159 | ;fname - имя файла, переменная 11 байт |
160 | ;buf-буфер для чтения информационных секторов, желательно иметь |
161 | ;размер 12*512byte=6144 byte |
162 | ;fbuf - буфер для загрузки файла |
163 | ;ах возвращает код операции fff все норм, 0-файл на найден |
164 | |
165 | local exit |
166 | local go |
167 | |
168 | |
169 | _searchfile buf,fname,0 |
170 | |
171 | cmp ax ,0 |
172 | je exit |
173 | |
174 | mov bx ,fbuf |
175 | |
176 | go: |
177 | |
178 | push bx |
179 | push ax |
180 | _loadsector bx , ax ,0 |
181 | pop ax |
182 | _nextsector ax ,buf |
183 | pop bx |
184 | |
185 | cmp ax ,0fffh |
186 | je exit |
187 | |
188 | add bx ,512 |
189 | jmp go |
190 | exit: |
191 | |
192 | } |
193 |
194 | org 100h ;генерируем СОМ файл, загружаемый с 0x10 |
195 | use16 |
196 | jmp start |
197 | qwe db 0bh dup (0) |
198 | fun: |
199 | jmp pisem |
200 | ;------------- |
201 | ; код который выполниться при загрузке первого сектора |
202 | pisem: |
203 | ; |
204 | mov ax ,07b3h |
205 | mov ds , ax |
206 | mov ax ,0800h ;сюда грузим имя файла |
207 | mov es , ax ; а затем от туда его вынимаем |
208 | cld |
209 | xor di , di |
210 | mov si ,nfile |
211 | mov cx ,11 |
212 | rep movsb |
213 | mov ds , ax |
214 | _loadfile2 0,buf,0 |
215 | jmp dword 0800h:0000 |
216 | exit: |
217 | jmp $ |
218 |
219 |
220 | nfile db "IO BIN" , '!$' ;имя нашего файла который грузим в ОП |
221 | ;================== |
222 | start: |
223 | mov ax , cs |
224 | mov es , ax |
225 | |
226 |
227 | _rwsector 2,1,buf,0,1,0,0 |
228 |
229 |
230 | ;пишем сначала джамп а потом затем идем на +1а3 и там наш код пишем |
231 |
232 | mov si ,buf |
233 | mov ah ,0ebh |
234 | mov [ si ], ah |
235 | mov ah ,03ch |
236 | mov [ si +1], ah |
237 | mov ah ,90h |
238 | mov [ si +2], ah |
239 |
240 | mov cx ,1b0h ; |
241 | mov di ,buf |
242 | add di ,03eh |
243 | mov si ,fun |
244 |
245 | rep movsb |
246 | _rwsector 3,1,buf,0,1,0,0 |
247 | ;================ |
248 | mov ah ,0x4C ;эта функция завершает программу |
249 | mov al ,0 ;код возврата 0 |
250 | int 0x21 ;вызываем ядро операционной системы |
251 | buf db 512 dup (0) ; буфер для хранения информации считанной с сектора |
Теперь давайте разберемся с макросами. Слегка изменили макрос _rwsector , теперь он стал гибче и принимает больше параметров. _rwsector макрос который ищет файл, он принимает буфер для того чтоб грузить туда таблицу файлов, имя файла, и номер диска. Макрос читает таблицу файлов, и если файл найден то возвращает его относительный адрес, то есть относительный адрес первого сектора файла. Затем идет макрос _rwsector который принимает буфер и относительный сектор, он проверяет относительный сектор файла если его номер равен 0fffh то это последний сектор в файле если нет то смотрим второй и так пока не достигнем последний сектор. Макрос _rwsector загружает сектор по относительному адресу, принимает относительный адрес сектора а также буфер куда грузить и номер диска. И последний макрос _loadfile2 принимает имя файла, затем буфер служебный буфер куда будет грузиться сам файл, этот макрос содержит все выше перечисленные макросы и с помощью них загружает в память файл, также макрос в AX возвращает код операции если 0 то файл не найден.