!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!INT 13 - DISK - READ SECTOR(S) INTO MEMORY !! AH = 02h !! AL = number of sectors to read (must be nonzero) !! CH = low eight bits of cylinder number !! CL = sector number 1-63 (bits 0-5) !! high two bits of cylinder (bits 6-7, hard disk only) !! DH = head number !! DL = drive number (bit 7 set for hard disk) !! ES:BX ->; data buffer !! Return: CF set on error !! if AH = 11h (corrected ECC error), AL = burst length !! CF clear if successful !! AH = status (see #00234) !! AL = number of sectors transferred (only valid if CF set for some !! BIOSes) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
xor dl,dl mov ah,#0x08 ! AH=8用於取得驅動器參數; int 0x13 xor ch,ch
!!!!!!!!!!!!!!!!!!!!!!!!!!! !! INT 13 - DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI) !! AH = 08h !! DL = drive (bit 7 set for hard disk) !!Return: CF set on error !! AH = status (07h) (see #00234) !! CF clear if successful !! AH = 00h !! AL = 00h on at least some BIOSes !! BL = drive type (AT/PS2 floppies only) (see #00242) !! CH = low eight bits of maximum cylinder number !! CL = maximum sector number (bits 5-0) !! high two bits of maximum cylinder number (bits 7-6) !! DH = maximum head number !! DL = number of drives !! ESI ->; drive parameter table (floppies only) !!!!!!!!!!!!!!!!!!!!!!!!!!!!
1、按規定得有個頭,所以一開始是慣用的JMP; 2、頭裡邊內容很豐富,具體用法走著瞧; 3、自我檢測,不知道有什麼用,防偽造?防篡改? 4、如果裝載程序不對,只好死掉!以下終於走入正題; 5、獲取內存容量(使用了三種辦法,其中的E820和E801看不明白,int 15倒是老朋友了--應該是上個世紀80年代末認識的了,真佩服十年過去了,情意依舊,不過遇上一些不守規矩的BIOS,不知道還行不行); 6、將鍵盤重複鍵的重複率設為最大,靈敏一點? 7、檢測硬碟,不懂,放這裡幹什麼? 8、檢測MCA匯流排(不要問我這是什麼); 9、檢測PS/2滑鼠,用int 11,只是不知道為何放這裡; 10、檢測電源管理BIOS;唉,書到用時方恨少,不懂的太多了,真不好意思;不過也沒有關係, 不懂的就別去動它就行了;以下要進入內核了; 11、 在進入保護模式之前,可以調用一個你提供的試模式下的過程,讓你最後在看她一眼,當然你要是不提供,那就有個默認的,無非是塞住耳朵閉上眼睛禁止任何中斷,包括著名的NMI ; 12、設置保護模式起動后的常式地址, 你可以寫自己的常式,但不是代替而是把它加在setup提供的常式的前面(顯示一個小鴨子?); 13、如果內核是zImage, 將它移動到0x10000處; 14、如果自己不在0x90000處,則移動到0x90000處; 15、建立idt, gdt表; 16、啟動A20; 17、屏住呼吸,屏閉所有中斷; 18、啟動!movw $1, %ax ; lmsw %ax; 好已經進入保護模式下,馬上進行局部調整; 19、jmpi 0x100000, __KERNEL_CS,終於進入內核; setup.S A summary of the setup.S code 。The slight differences in the operation of setup.S due to a big kernel is documented here. When the switch to 32 bit protected mode begins the code32_start address is defined as 0x100000 (when loaded) here. code32_start:
After setting the keyboard repeat rate to a maximum, calling video.S, storing the video parameters, checking for the hard disks, PS/2 mouse, and APM BIOS the preparation for real mode switch begins.
The interrupts are disabled. Since the loader changed the code32_start address, the code32 varable is updated. This would be used for the jmpi instruction when the setup.S finally jumps to compressed/head.S. In case of a big kernel this is loacted at 0x100000.
seg cs mov eax, code32_start !modified above by the loader seg cs mov code32,eax
!code32 contains the correct address to branch to after setup.S finishes After the above code there is a slight difference in the ways the big and small kernels are dealt. In case of a small kernel the kernel is moved down to segment address 0x100, but a big kernel is not moved. Before decompression, the big kernel stays at 0x100000. The following is the code that does thischeck.test byte ptr loadflags,
#LOADED_HIGH jz do_move0 ! a normal low loaded zImage is moved jmp end_move ! skip move
The interrupt and global descriptors are initialized:
lidt idt_48 ! load idt wit 0,0 lgdt gdt_48 ! load gdt with whatever appropriate
After enabling A20 and reprogramming the interrupts, it is ready to set the PE bit:
mov ax,#1 lmsw ax jmp flush_instr flush_instr: xor bx.bx !flag to indicate a boot ! Manual, mixing of 16-bit and 32 bit code db 0x166,0xea !prefix jmpi-opcode code32: dd ox1000 !this has been reset in caes of a big kernel, to 0x100000 dw __KERNEL_CS
Finally it prepares the opcode for jumping to compressed/head.S which in the big kernel is at 0x100000. The compressed kernel would start at 0x1000 in case of a small kernel.
compressed/head.S
When setup.S relinquishes control to compressed/head.S at beginning of the compressed kernmel at 0x100000. It checks to see if A20 is really enabled otherwise it loops forever.
Itinitializes eflags, and clears BSS (Block Start by Symbol) creating reserved space for uninitialized static or global variables. Finally it reserves place for the moveparams structure (defined in misc.c) and pushes the current stack pointer on the stack and calls the C function decompress_kernel which takes a struct moveparams * as an argument
Te C function decompress_kernel returns the variable high_loaded which is set to 1 in the function setup_output_buffer_if_we_run_high, which is called in decompressed_kernel if a big kernel was loaded. When decompressed_kernel returns, it jumps to 3f which moves the move routine.
movl $move_routine_start,%esi ! puts the offset of the start of the source in the source index register mov $0x1000,?? ! the destination index now contains 0x1000, thus after move, the move routine starts at 0x1000 movl $move_routine_end,?? sub %esi,?? ! ecx register now contains the number of bytes to be moved ! (number of bytes between the labels move_routine_start and move_routine_end) cld rep movsb ! moves the bytes from ds:si to es:di, in each loop it increments si and di, and decrements cx ! the movs instruction moves till ecx is zero
Thus the movsb instruction moves the bytes of the move routine between the labels move_routine_start and move_routine_end. At the end the entire move routine labeled move_routine_start is at 0x1000. The movsb instruction moves bytes from ds:si to es:si.
At the start of the head.S code es,ds,fs,gs were all intialized to __KERNEL_DS, which is defined in /usr/src/linux/include/asm/segment.h as 0x18. This is the offset from the goobal descriptor table gdtwhich was setup in setup.S. The 24th byte is the start of the data segment descriptor, which has the base address = 0. Thus the moe routine is moved and starts at offset 0x1000 from __KERNEL_DS, the kernel data segment base (which is 0). The salient features of what is done by the decompress_kernel is discussed in the next section but it is worth noting that the when the decompressed_kernel function is invoked, space was created at the top of the stack to contain the information about the decompressed kernel. The decompressed kernel if big may be in the high buffer and in the low buffer. After the decompressed_kernel function returns, the decompressed kernel has to be moved so that we have a contiguous decompressed kernel starting from address 0x100000. To move the decompressed kernel, the important parameters needed are the start addresses of the high buffer and low buffer, and the number of bytes in the high and low buffers. This is at the top of the stack when decompressed_kernel returns (the top of the stack was passed as an argument : struct moveparams*, and in the function the fileds of the moveparams struture was adjusted toreflect the state of the decompression.)
/* in compressed/misc.c */ struct moveparams { uch *low_buffer_start; ! start address of the low buffer int count; ! number of bytes in the low buffer after decompression is doneuch *high_buffer_start; ! start address of the high buffer int hcount; ! number of bytes in the high buffer aftre decompression is done };
Thus when the decompressed_kernel returns, the relevant bytes are popped in the respective registers as shown below. After preparing these registers the decompressed kernel is ready to be moved and the control jumps to the moved move routine at __KERNEL_CS:0x1000. The code for setting the appropriate registers is given below:
popl %esi ! discard the address, has the return value (high_load) most probably popl %esi ! low_buffer_start popl ?? ! lcount popl ?? ! high_buffer_count popl ?? ! hcount movl %0x100000,?? cli ! disable interrutps when the decompressed kernel is being moved ljmp $(__KERNEL_CS), $0x1000 ! jump to the move routine which was moved to low memory, 0x1000
The move_routine_start basically has two parts, first it moves the part of the decompressed kernel in the low buffer, then it moves (if required) the high buffer contents. It should be noted that the ecx has been intialized to the number of bytes in the low end buffer, and the destination index register di has been intialized to 0x100000. move_routine_start:
rep ! repeat, it stops repeating when ecx == 0 movsb ! the movsb instruction repeats till ecx is 0. In each loop byte is transferred from ds:esi to es:edi! In each loop the edi and the esi are incremented and ecx is decremented ! when the low end buffer has been moved the value of di is not changed and the next pasrt of the code! uses it to transfer the bytes from the high buffer movl ??,%esi ! esi now has the offset corresponding to the start of the high buffer movl ??,?? ! ecx is now intialized to the number of bytes in the high buffer rep movsb ! moves all the bytes in the high buffer, and doesn』t move at all if hcount was zero (if it was determined, in! close_output_buffer_if_we_run_high that the high buffer need not be moveddown ) xorl ??,?? mov $0x90000, %esp ! stack pointer is adjusted, most probably to be used by the kernel in the intialization ljmp $(__KERNEL_CS), $0x100000 ! jump to __KERNEL_CS:0X100000, where the kernel code starts move_routine_end:At the end of the this the control goes to the kernel code segment.
Linux Assembly code taken from head.S and setup.S Comment code added by us
compressed/misc.c The differences in decompressing big and small kernels. http://www.vuse.vanderbilt.edu/~ ... ation/hw3_part3.htm The function decompressed_kernel is invoked from head.S and a parameter to the top of the stack is passed to store the results of the decompression namely, the start addresses of the high and the low buffers which contain the decompressed kernel and the numebr of bytes in each buffer (hcount and lcount). int decompress_kernel(struct moveparams *mv) { if (SCREEN_INFO.orig_video_mode == 7) { vidmem = (char *) 0xb0000; vidport = 0x3b4; } else { vidmem = (char *) 0xb8000; vidport = 0x3d4; } lines = SCREEN_INFO.orig_video_lines; cols = SCREEN_INFO.orig_video_cols; if (free_mem_ptr < 0x100000) setup_normal_output_buffer(); // Call if smallkernel else setup_output_buffer_if_we_run_high(mv); // Call if big kernel makecrc(); puts("Uncompressing Linux... "; gunzip(); puts("Ok, booting the kernel.\n"; if (high_loaded) close_output_buffer_if_we_run_high(mv); return high_loaded; }
The first place where a distinction is made is when the buffers are to be setup for the decmpression routine gunzip(). Free_mem_ptr, is loaded with the value of the address of the extern variabe end. The variable end marks the end of the compressed kernel. If the free_mem-ptr is less than the 0x100000,then a high buffer has to be setup. Thus the function setup_output_buffer_if_we_run_high is called and the pointer to the top of the moveparams structure is passed so that when the buffers are setup, the start addresses fields are updated in moveparams structure. It is also checked to see if the high buffer needs to be moved down after decompression and this is reflected by the hcount which is 0 if we need not move the high buffer down.
void setup_output_buffer_if_we_run_high(struct moveparams *mv) { high_buffer_start = (uch *)(((ulg)&end) HEAP_SIZE); //the high buffer start address is at the end HEAP_SIZE #ifdef STANDARD_MEMORY_BIOS_CALL if (EXT_MEM_K < (3*1024)) error("Less than 4MB of memory.\n"; #else if ((ALT_MEM_K >; EXT_MEM_K ? ALT_MEM_K : EXT_MEM_K) < (3*1024)) error("Less than 4MB of memory.\n"; #endif mv->;low_buffer_start = output_data = (char *)LOW_BUFFER_START; //the low buffer start address is at 0x2000 and it extends till 0x90000. high_loaded = 1; //high_loaded is set to 1, this is returned by decompressed_kernel free_mem_end_ptr = (long)high_buffer_start; // free_mem_end_ptr points to the same address as te high_buffer_start // the code below finds out if the high buffer needs to be moved after decompression // if the size if the low buffer is >; the size of the compressed kernel and the HEAP_SIZE // then the high_buffer_start has to be shifted up so that when the decompression starts it doesn』t // overwrite the compressed kernel data. Thus when the high_buffer_start islow then it is shifted // up to exactly match the end of the compressed kernel and the HEAP_SIZE. The hcount filed is // is set to 0 as the high buffer need not be moved down. Otherwise if the high_buffer_start is too // high then the hcount is non zero and while closing the buffers the appropriate number of bytes // in the high buffer is asigned to the filed hcount. Since the start address of the high buffer is // known the bytes could be moved down if ( (0x100000 LOW_BUFFER_SIZE) >; ((ulg)high_buffer_start)) { high_buffer_start = (uch *)(0x100000 LOW_BUFFER_SIZE); mv->;hcount = 0; /* say: we need not to move high_buffer */ } else mv->;hcount = -1; mv->;high_buffer_start = high_buffer_start; // finally the high_buffer_start field is set to the varaible high_buffer_start }
After the buffers are set gunzip() is invoked which decompresses the kernel Upon return, bytes_out has the number of bytes in the decompressed kernel.Finally close_output_buffer_if_we_run_high is invoked if high_loaded is non zero:
void close_output_buffer_if_we_run_high(struct moveparams *mv) { mv->;lcount = bytes_out; // if the all of decompressed kernel is in low buffer, lcount = bytes_out if (bytes_out >; LOW_BUFFER_SIZE) { // if there is a part of the decompressed kernel in the high buffer, the lcount filed is set to // the size of the low buffer and the hcount field contains the rest of the bytes mv->;lcount = LOW_BUFFER_SIZE; if (mv->;hcount) mv->;hcount = bytes_out - LOW_BUFFER_SIZE; // if the hcount field is non zero (made in setup_output_buffer_if_we_run_high) // then the high buffer has to be moved doen and the number of bytes in the high buffer is // in hcount } else mv->;hcount = 0; // all the data is in the high buffer } Thus at the end of the the decompressed_kernel function the top of the stack has the addresses of the buffers and their sizes which is popped and the appropriate registers set for the move routine to move the entire kernel. After the move by the move_routine the kernel resides at 0x100000. If a small kernel is being decompressed then the setup_normal_output_buffer() is invoked from decompressed_kernel, which just initializes output_data to 0x100000 where the decompressed kernel would lie. The variable high_load is still 0 as setup_output_buffer_if_we_run_high() is not invoked. Decompression is done starting at address 0x100000. As high_load is 0, when decompressed_kernel returns in head.S, a zero is there in the eax. Thus the control jumps directly to 0x100000. Since the decompressed kernel lies there directly and the move routine need not be called.
Linux code taken from misc.c Comment code added by us
#define OF(args) args ; 用於函數原型聲明的宏 #ifndef memzero #define memzero(s, n) memset ((s), 0, (n)) #endif typedef unsigned char uch; 定義inflate.c所使用的3種數據類型 typedef unsigned short ush; typedef unsigned long ulg; #define INBUFSIZ 4096 用戶輸入緩衝區尺寸 #define WSIZE 0x8000 /* window size--must be a power of two, and */ /* at least 32K for zip's deflate method */
static uch *inbuf; 用戶輸入緩衝區,與inflate.c無關 static uch *window; 解壓窗口 static unsigned insize; /* valid bytes in inbuf */ static unsigned inptr; /* index of next byte to be processed in inbuf */ static unsigned outcnt; /* bytes in output buffer */ static int exit_code; static long bytes_out; 總解壓輸出長度,與inflate.c無關 static struct file *crd_infp, *crd_outfp;
/* =========================================================================== * Fill the input buffer. This is called only when the buffer is empty * and at least one byte is really needed. */
static int __init fill_inbuf(void) 填充輸入緩衝區 { if (exit_code) return -1; insize = crd_infp->;f_op->;read(crd_infp, inbuf, INBUFSIZ, if (insize == 0) return -1; inptr = 1; return inbuf[0]; }
/* =========================================================================== * Write the output window window[0..outcnt-1] and update crc and bytes_out. * (Used for the decompressed data only.) */
static void __init flush_window(void) 輸出window緩衝區中outcnt個位元組串 { ulg c = crc; /* temporary variable */ unsigned n; uch *in, ch;
crd_outfp->;f_op->;write(crd_outfp, window, outcnt, in = window; for (n = 0; n ch = *in++; c = crc_32_tab[((int)c ^ ch) 0xff] ^ (c >;>; ; 計算輸出串的CRC } crc = c; bytes_out += (ulg)outcnt; 刷新總位元組數 outcnt = 0; }
lss SYMBOL_NAME(stack_start),%esp # 自解壓代碼的堆棧為misc.c中定義的16K位元組的數組 xorl %eax,%eax 1: incl %eax # check that A20 really IS enabled movl %eax,0x000000 # loop forever if it isn't cmpl %eax,0x100000 je 1b
/* * Initialize eflags. Some BIOS's leave bits like NT set. This would * confuse the debugger if this code is traced. * XXX - best to initialize before switching to protected mode. */ pushl $0 popfl /* * Clear BSS 清除解壓程序的BSS段 */ xorl %eax,%eax movl $ SYMBOL_NAME(_edata),%edi movl $ SYMBOL_NAME(_end),%ecx subl %edi,%ecx cld rep stosb /* * Do the decompression, and jump to the new kernel.. */ subl $16,%esp # place for structure on the stack movl %esp,%eax pushl %esi # real mode pointer as second arg pushl %eax # address of structure as first arg call SYMBOL_NAME(decompress_kernel) orl %eax,%eax # 如果返回非零,則表示為內核解壓為低端和高端的兩個片斷 jnz 3f popl %esi # discard address popl %esi # real mode pointer xorl %ebx,%ebx ljmp $(__KERNEL_CS), $0x100000 # 運行start_kernel
/* * We come here, if we were loaded high. * We need to move the move-in-place routine down to 0x1000 * and then start it with the buffer addresses in registers, * which we got from the stack. */ 3: movl $move_routine_start,%esi movl $0x1000,%edi movl $move_routine_end,%ecx subl %esi,%ecx addl $3,%ecx shrl $2,%ecx # 按字取整 cld rep movsl # 將內核片斷合併代碼複製到0x1000區域, 內核的片段起始為0x2000
popl %esi # discard the address popl %ebx # real mode pointer popl %esi # low_buffer_start 內核低端片段的起始地址 popl %ecx # lcount 內核低端片段的位元組數量 popl %edx # high_buffer_start 內核高端片段的起始地址 popl %eax # hcount 內核高端片段的位元組數量 movl $0x100000,%edi 內核合併的起始地址 cli # make sure we don't get interrupted ljmp $(__KERNEL_CS), $0x1000 # and jump to the move routine
/* * Routine (template) for moving the decompressed kernel in place, * if we were high loaded. This _must_ PIC-code ! */ move_routine_start: movl %ecx,%ebp shrl $2,%ecx rep movsl # 按字拷貝第1個片段 movl %ebp,%ecx andl $3,%ecx rep movsb # 傳送不完全字 movl %edx,%esi movl %eax,%ecx # NOTE: rep movsb won't move if %ecx == 0 addl $3,%ecx shrl $2,%ecx # 按字對齊 rep movsl # 按字拷貝第2個片段 movl %ebx,%esi # Restore setup pointer xorl %ebx,%ebx ljmp $(__KERNEL_CS), $0x100000 # 運行start_kernel move_routine_end:
ypedef unsigned char uch; typedef unsigned short ush; typedef unsigned long ulg;
#define WSIZE 0x8000 /* Window size must be at least 32k, */ /* and a power of two */
static uch *inbuf; /* input buffer */ static uch window[WSIZE]; /* Sliding window buffer */
static unsigned insize = 0; /* valid bytes in inbuf */ static unsigned inptr = 0; /* index of next byte to be processed in inbuf */ static unsigned outcnt = 0; /* bytes in output buffer */
/* gzip flag byte */ #define ASCII_FLAG 0x01 /* bit 0 set: file probably ASCII text */ #define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */ #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ #define COMMENT 0x10 /* bit 4 set: file comment present */ #define ENCRYPTED 0x20 /* bit 5 set: file is encrypted */ #define RESERVED 0xC0 /* bit 6,7: reserved */
/* =========================================================================== * Fill the input buffer. This is called only when the buffer is empty * and at least one byte is really needed. */ static int fill_inbuf(void) { if (insize != 0) { error("ran out of input data\n"; }