The translation is temporarily closed for contributions due to maintenance, please come back later.

Translation

(itstool) path: figure/programlisting
English
main:
testb $SETDRV,-69(%bp) # Set drive number?
jnz disable_update # Yes
testb %dl,%dl # Drive number valid?
js save_curdrive # Possibly (0x80 set)
Context English Chinese (Simplified) (zh_CN) State
Note that the <filename>boot0.S</filename> source file is assembled <quote>as is</quote>: instructions are translated one by one to binary, with no additional information (no <acronym>ELF</acronym> file format, for example). This kind of low-level control is achieved at link time through special control flags passed to the linker. For example, the text section of the program is set to be located at address <literal>0x600</literal>. In practice this means that <filename>boot0</filename> must be loaded to memory address <literal>0x600</literal> in order to function properly.
It is worth looking at the <filename>Makefile</filename> for <filename>boot0</filename> (<filename>sys/boot/i386/boot0/Makefile</filename>), as it defines some of the run-time behavior of <filename>boot0</filename>. For instance, if a terminal connected to the serial port (COM1) is used for I/O, the macro <literal>SIO</literal> must be defined (<literal>-DSIO</literal>). <literal>-DPXE</literal> enables boot through <acronym>PXE</acronym> by pressing <keycap>F6</keycap>. Additionally, the program defines a set of <emphasis>flags</emphasis> that allow further modification of its behavior. All of this is illustrated in the <filename>Makefile</filename>. For example, look at the linker directives which command the linker to start the text section at address <literal>0x600</literal>, and to build the output file <quote>as is</quote> (strip out any file formatting):
<filename>sys/boot/i386/boot0/Makefile</filename> <filename>sys/boot/i386/boot0/Makefile</filename>
BOOT_BOOT0_ORG?=0x600
LDFLAGS=-e start -Ttext ${BOOT_BOOT0_ORG} \
-Wl,-N,-S,--oformat,binary
BOOT_BOOT0_ORG?=0x600
LDFLAGS=-e start -Ttext ${BOOT_BOOT0_ORG} \
-Wl,-N,-S,--oformat,binary
Let us now start our study of the <acronym>MBR</acronym>, or <filename>boot0</filename>, starting where execution begins.
Some modifications have been made to some instructions in favor of better exposition. For example, some macros are expanded, and some macro tests are omitted when the result of the test is known. This applies to all of the code examples shown.
<filename>sys/boot/i386/boot0/boot0.S</filename> <filename>sys/boot/i386/boot0/boot0.S</filename>
start:
cld # String ops inc
xorw %ax,%ax # Zero
movw %ax,%es # Address
movw %ax,%ds # data
movw %ax,%ss # Set up
movw 0x7c00,%sp # stack
start:
cld # String ops inc
xorw %ax,%ax # Zero
movw %ax,%es # Address
movw %ax,%ds # data
movw %ax,%ss # Set up
movw 0x7c00,%sp # stack
When in doubt, we refer the reader to the official Intel manuals, which describe the exact semantics for each instruction: <link xlink:href="http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html"/>.
This first block of code is the entry point of the program. It is where the <acronym>BIOS</acronym> transfers control. First, it makes sure that the string operations autoincrement its pointer operands (the <literal>cld</literal> instruction) <_:footnote-1/>. Then, as it makes no assumption about the state of the segment registers, it initializes them. Finally, it sets the stack pointer register (<literal>%sp</literal>) to address <literal>0x7c00</literal>, so we have a working stack.
The next block is responsible for the relocation and subsequent jump to the relocated code.
movw $0x7c00,%si # Source
movw $0x600,%di # Destination
movw $512,%cx # Word count
rep # Relocate
movsb # code
movw %di,%bp # Address variables
movb $16,%cl # Words to clear
rep # Zero
stosb # them
incb -0xe(%di) # Set the S field to 1
jmp main-0x7c00+0x600 # Jump to relocated code
movw $0x7c00,%si # Source
movw $0x600,%di # Destination
movw $512,%cx # Word count
rep # Relocate
movsb # code
movw %di,%bp # Address variables
movb $16,%cl # Words to clear
rep # Zero
stosb # them
incb -0xe(%di) # Set the S field to 1
jmp main-0x7c00+0x600 # Jump to relocated code
As <filename>boot0</filename> is loaded by the <acronym>BIOS</acronym> to address <literal>0x7C00</literal>, it copies itself to address <literal>0x600</literal> and then transfers control there (recall that it was linked to execute at address <literal>0x600</literal>). The source address, <literal>0x7c00</literal>, is copied to register <literal>%si</literal>. The destination address, <literal>0x600</literal>, to register <literal>%di</literal>. The number of bytes to copy, <literal>512</literal> (the program's size), is copied to register <literal>%cx</literal>. Next, the <literal>rep</literal> instruction repeats the instruction that follows, that is, <literal>movsb</literal>, the number of times dictated by the <literal>%cx</literal> register. The <literal>movsb</literal> instruction copies the byte pointed to by <literal>%si</literal> to the address pointed to by <literal>%di</literal>. This is repeated another 511 times. On each repetition, both the source and destination registers, <literal>%si</literal> and <literal>%di</literal>, are incremented by one. Thus, upon completion of the 512-byte copy, <literal>%di</literal> has the value <literal>0x600</literal>+<literal>512</literal>= <literal>0x800</literal>, and <literal>%si</literal> has the value <literal>0x7c00</literal>+<literal>512</literal>= <literal>0x7e00</literal>; we have thus completed the code <emphasis>relocation</emphasis>.
Next, the destination register <literal>%di</literal> is copied to <literal>%bp</literal>. <literal>%bp</literal> gets the value <literal>0x800</literal>. The value <literal>16</literal> is copied to <literal>%cl</literal> in preparation for a new string operation (like our previous <literal>movsb</literal>). Now, <literal>stosb</literal> is executed 16 times. This instruction copies a <literal>0</literal> value to the address pointed to by the destination register (<literal>%di</literal>, which is <literal>0x800</literal>), and increments it. This is repeated another 15 times, so <literal>%di</literal> ends up with value <literal>0x810</literal>. Effectively, this clears the address range <literal>0x800</literal>-<literal>0x80f</literal>. This range is used as a (fake) partition table for writing the <acronym>MBR</acronym> back to disk. Finally, the sector field for the <acronym>CHS</acronym> addressing of this fake partition is given the value 1 and a jump is made to the main function from the relocated code. Note that until this jump to the relocated code, any reference to an absolute address was avoided.
The following code block tests whether the drive number provided by the <acronym>BIOS</acronym> should be used, or the one stored in <filename>boot0</filename>.
main:
testb $SETDRV,-69(%bp) # Set drive number?
jnz disable_update # Yes
testb %dl,%dl # Drive number valid?
js save_curdrive # Possibly (0x80 set)
main:
testb $SETDRV,-69(%bp) # Set drive number?
jnz disable_update # Yes
testb %dl,%dl # Drive number valid?
js save_curdrive # Possibly (0x80 set)
This code tests the <literal>SETDRV</literal> bit (<literal>0x20</literal>) in the <emphasis>flags</emphasis> variable. Recall that register <literal>%bp</literal> points to address location <literal>0x800</literal>, so the test is done to the <emphasis>flags</emphasis> variable at address <literal>0x800</literal>-<literal>69</literal>= <literal>0x7bb</literal>. This is an example of the type of modifications that can be done to <filename>boot0</filename>. The <literal>SETDRV</literal> flag is not set by default, but it can be set in the <filename>Makefile</filename>. When set, the drive number stored in the <acronym>MBR</acronym> is used instead of the one provided by the <acronym>BIOS</acronym>. We assume the defaults, and that the <acronym>BIOS</acronym> provided a valid drive number, so we jump to <literal>save_curdrive</literal>.
The next block saves the drive number provided by the <acronym>BIOS</acronym>, and calls <literal>putn</literal> to print a new line on the screen.
save_curdrive:
movb %dl, (%bp) # Save drive number
pushw %dx # Also in the stack
#ifdef TEST /* test code, print internal bios drive */
rolb $1, %dl
movw $drive, %si
call putkey
#endif
callw putn # Print a newline
save_curdrive:
movb %dl, (%bp) # Save drive number
pushw %dx # Also in the stack
#ifdef TEST /* test code, print internal bios drive */
rolb $1, %dl
movw $drive, %si
call putkey
#endif
callw putn # Print a newline
Note that we assume <varname>TEST</varname> is not defined, so the conditional code in it is not assembled and will not appear in our executable <filename>boot0</filename>.
Our next block implements the actual scanning of the partition table. It prints to the screen the partition type for each of the four entries in the partition table. It compares each type with a list of well-known operating system file systems. Examples of recognized partition types are <acronym>NTFS</acronym> (<trademark class="registered">Windows</trademark>, ID 0x7), <literal>ext2fs</literal> (<trademark class="registered">Linux</trademark>, ID 0x83), and, of course, <literal>ffs</literal>/<literal>ufs2</literal> (FreeBSD, ID 0xa5). The implementation is fairly simple.
movw $(partbl+0x4),%bx # Partition table (+4)
xorw %dx,%dx # Item number

read_entry:
movb %ch,-0x4(%bx) # Zero active flag (ch == 0)
btw %dx,_FLAGS(%bp) # Entry enabled?
jnc next_entry # No
movb (%bx),%al # Load type
test %al, %al # skip empty partition
jz next_entry
movw $bootable_ids,%di # Lookup tables
movb $(TLEN+1),%cl # Number of entries
repne # Locate
scasb # type
addw $(TLEN-1), %di # Adjust
movb (%di),%cl # Partition
addw %cx,%di # description
callw putx # Display it

next_entry:
incw %dx # Next item
addb $0x10,%bl # Next entry
jnc read_entry # Till done
movw $(partbl+0x4),%bx # Partition table (+4)
xorw %dx,%dx # Item number

read_entry:
movb %ch,-0x4(%bx) # Zero active flag (ch == 0)
btw %dx,_FLAGS(%bp) # Entry enabled?
jnc next_entry # No
movb (%bx),%al # Load type
test %al, %al # skip empty partition
jz next_entry
movw $bootable_ids,%di # Lookup tables
movb $(TLEN+1),%cl # Number of entries
repne # Locate
scasb # type
addw $(TLEN-1), %di # Adjust
movb (%di),%cl # Partition
addw %cx,%di # description
callw putx # Display it

next_entry:
incw %dx # Next item
addb $0x10,%bl # Next entry
jnc read_entry # Till done
It is important to note that the active flag for each entry is cleared, so after the scanning, <emphasis>no</emphasis> partition entry is active in our memory copy of <filename>boot0</filename>. Later, the active flag will be set for the selected partition. This ensures that only one active partition exists if the user chooses to write the changes back to disk.
The next block tests for other drives. At startup, the <acronym>BIOS</acronym> writes the number of drives present in the computer to address <literal>0x475</literal>. If there are any other drives present, <filename>boot0</filename> prints the current drive to screen. The user may command <filename>boot0</filename> to scan partitions on another drive later.
popw %ax # Drive number
subb $0x79,%al # Does next
cmpb 0x475,%al # drive exist? (from BIOS?)
jb print_drive # Yes
decw %ax # Already drive 0?
jz print_prompt # Yes
popw %ax # Drive number
subb $0x79,%al # Does next
cmpb 0x475,%al # drive exist? (from BIOS?)
jb print_drive # Yes
decw %ax # Already drive 0?
jz print_prompt # Yes
We make the assumption that a single drive is present, so the jump to <literal>print_drive</literal> is not performed. We also assume nothing strange happened, so we jump to <literal>print_prompt</literal>.
This next block just prints out a prompt followed by the default option:
print_prompt:
movw $prompt,%si # Display
callw putstr # prompt
movb _OPT(%bp),%dl # Display
decw %si # default
callw putkey # key
jmp start_input # Skip beep
print_prompt:
movw $prompt,%si # Display
callw putstr # prompt
movb _OPT(%bp),%dl # Display
decw %si # default
callw putkey # key
jmp start_input # Skip beep
Finally, a jump is performed to <literal>start_input</literal>, where the <acronym>BIOS</acronym> services are used to start a timer and for reading user input from the keyboard; if the timer expires, the default option will be selected:
start_input:
xorb %ah,%ah # BIOS: Get
int $0x1a # system time
movw %dx,%di # Ticks when
addw _TICKS(%bp),%di # timeout
read_key:
movb $0x1,%ah # BIOS: Check
int $0x16 # for keypress
jnz got_key # Have input
xorb %ah,%ah # BIOS: int 0x1a, 00
int $0x1a # get system time
cmpw %di,%dx # Timeout?
jb read_key # No
start_input:
xorb %ah,%ah # BIOS: Get
int $0x1a # system time
movw %dx,%di # Ticks when
addw _TICKS(%bp),%di # timeout
read_key:
movb $0x1,%ah # BIOS: Check
int $0x16 # for keypress
jnz got_key # Have input
xorb %ah,%ah # BIOS: int 0x1a, 00
int $0x1a # get system time
cmpw %di,%dx # Timeout?
jb read_key # No
An interrupt is requested with number <literal>0x1a</literal> and argument <literal>0</literal> in register <literal>%ah</literal>. The <acronym>BIOS</acronym> has a predefined set of services, requested by applications as software-generated interrupts through the <literal>int</literal> instruction and receiving arguments in registers (in this case, <literal>%ah</literal>). Here, particularly, we are requesting the number of clock ticks since last midnight; this value is computed by the <acronym>BIOS</acronym> through the <acronym>RTC</acronym> (Real Time Clock). This clock can be programmed to work at frequencies ranging from 2 Hz to 8192 Hz. The <acronym>BIOS</acronym> sets it to 18.2 Hz at startup. When the request is satisfied, a 32-bit result is returned by the <acronym>BIOS</acronym> in registers <literal>%cx</literal> and <literal>%dx</literal> (lower bytes in <literal>%dx</literal>). This result (the <literal>%dx</literal> part) is copied to register <literal>%di</literal>, and the value of the <varname>TICKS</varname> variable is added to <literal>%di</literal>. This variable resides in <filename>boot0</filename> at offset <literal>_TICKS</literal> (a negative value) from register <literal>%bp</literal> (which, recall, points to <literal>0x800</literal>). The default value of this variable is <literal>0xb6</literal> (182 in decimal). Now, the idea is that <filename>boot0</filename> constantly requests the time from the <acronym>BIOS</acronym>, and when the value returned in register <literal>%dx</literal> is greater than the value stored in <literal>%di</literal>, the time is up and the default selection will be made. Since the RTC ticks 18.2 times per second, this condition will be met after 10 seconds (this default behavior can be changed in the <filename>Makefile</filename>). Until this time has passed, <filename>boot0</filename> continually asks the <acronym>BIOS</acronym> for any user input; this is done through <literal>int 0x16</literal>, argument <literal>1</literal> in <literal>%ah</literal>.

Loading…

No matching activity found.

Browse all component changes

Things to check

Unchanged translation

Source and translation are identical

Reset

Glossary

English Chinese (Simplified) (zh_CN)
No related strings found in the glossary.

Source information

Source string comment
(itstool) path: figure/programlisting
Flags
no-wrap
Source string location
book.translate.xml:704
String age
a year ago
Source string age
a year ago
Translation file
books/zh_CN/arch-handbook.po, string 80