使用匯編語言編寫加載器(加載用戶程序)

使用匯編語言編寫加載器加載指定格式的用戶程序


在計算機加電以後,計算機首先會讀取硬盤的主引導扇區,作一些必要的初始化工做,可是硬盤的一個扇區只有512字節,因此咱們要實現更多的功能,就要有用戶程序,咱們須要把控制權限交給用戶程序(操做系統暫且也算一種用戶程序吧)。app

在加載用戶程序的過程當中,主要分爲如下幾個大步驟:oop

  • 一.從硬盤讀取用戶程序,並加載到內存中的指定位置(自定義)。
  • 二.重定位用戶程序(段地址)
  • 三.將控制權交給用戶程序

用戶程序頭部:操作系統

SECTION header vstart=0                     ;定義用戶程序頭部段 
    program_length  dd program_end          ;程序總長度[0x00]
    
    ;用戶程序入口點
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code_1.start ;段地址[0x06] 
    
    realloc_tbl_len dw (header_end-code_1_segment)/4
                                            ;段重定位表項個數[0x0a]
    
    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[0x0c]
    code_2_segment  dd section.code_2.start ;[0x10]
    data_1_segment  dd section.data_1.start ;[0x14]
    data_2_segment  dd section.data_2.start ;[0x18]
    stack_segment   dd section.stack.start  ;[0x1c]
    
    header_end:

一.讀取用戶程序到內存

從硬盤讀取信息,須要五個步驟:rest

  1. 設置要讀取的扇區數量

這個數值要寫入0x1f2端口,這是一個8位寄存器,因此能夠使用code

out dx,al

2.設置要讀取的起始LBA扇區號。
這裏使用LBA28。28位的扇區號,分給四個端口,0x1f3-0x1f6,從低到高依次存儲,最後一個端口也就是0x1f6低四位存儲扇區號的最高四位,剩下四位高三位111表示LBA模式,最低覺得0表示從盤,1表示主盤。
例如,若是其實扇區是100,也就是0x60,那麼設置的代碼以下:內存

mov dx,0x1f3
mov al,0x60
out dx,al
inc dx
xor al,al
out dx,al
inc dx
out dx,al
inc dx
mov al,0xe0
out dx,al

3.請求讀寫
次數值寫入0x1f7端口,0x20表示請求讀it

mov al,0x20
mov dx,0x1f7
out dx,al

4.等待硬盤空閒
0x1f7這個端口,除了能夠請求讀意外,還能表示硬盤的狀態,第7位0表示空閒,1表示繁忙,第3位爲1表示準備好進行數據傳輸,因此這裏咱們要等待硬盤空閒才能夠傳輸數據:io

waits:
    in al,dx
    and al,1000_1000B    ;保留第3位和第7位
    cmp 0000_1000B    ;第7位爲0第3位爲1才能夠進行讀取
    jne waits        ;不相等就循環等待

5.讀數據
從硬盤讀取數據,經過0x1f0端口,這是一個16位寄存器。硬盤是典型的塊設備,因此一次必須讀取512字節,或者它的倍數。好比,咱們要讀取一個扇區的數據,並存放在ds:0開始的內存空間:循環

mov dx,0x1f0
    mov cx,256    ;讀取一個扇區,512字節,即256字
    xor bx,bx
read_word
    in ax,dx
    mov [bx],ax
    add bx,2
    loop read_word

6.檢查用戶程序是否讀取完整。
剛纔咱們只讀取了一個扇區,即512字節,咱們並不能肯定用戶程序是否已經徹底讀完,可是咱們已經讀取了用戶程序的頭部,這裏咱們規定用戶程序頭部的第一個雙字必須定義用戶程序的長度.因此咱們從ds:0的位置讀取兩個字,第一個字放在ax,第二個字放在dx,這樣dx:ax表明了用戶程序的總長度,用這個數除以512獲得商和餘數,能夠知道用戶程序的讀取進度,進而來決定是繼續讀取仍是跳轉到後邊的步驟(重定位)。
注意:
因爲一個邏輯段最大是64kb,從0x0000-0xffff,可是用戶程序可能超過這個範圍,爲了不這種事情發生,咱們每讀一個扇區,便把段地址加0x20(512),這樣即可以連續存放且不用擔憂超過邏輯段大小。權限

;檢查用戶程序是否讀取完整
    xor bx,bx
    mov ax,[bx]
    mov dx,[bx+2]
    mov bx,512
    div bx
    
    cmp dx,0
    jne cmp_ax
    dec ax
    
cmp_ax:
    cmp ax,0
    je  redirect_entry    ;跳轉到重定位
;讀取剩餘扇區
    push ds
    
    mov cx,ax
    mov si,start_sector    ;start_sector是定義的一個常數,這裏等於100(用戶程序在100扇區開始)
    
read_rest:
    mov ax,ds
    add ax,0x20
    mov ds,ax
    inc si
    call read_disk
    loop read_rest
    
    pop ds

二.重定位用戶程序

用戶程序在編寫的時候都是分段的,重定位的目的即是肯定每一個段的實際段地址。
這裏咱們規定用戶程序頭部中定義了每一個段的段首位置(彙編地址),轉換成16位的段地址並從新寫入。

;重定位用戶程序
;重定位用戶入口點的段地址
redirect_entry:
    mov ax,[0x06]    ;讀取頭部入口點地址信息
    mov dx,[0x08]
    call calc_seg_base
    mov [0x06],ax
    
;重定位其餘段的段地址
    mov cx,[0x0a]
    mov bx,0x0c
redirect_other_seg:
    mov ax,[bx]
    mov dx,[bx+2]
    call calc_seg_base
    mov [bx],ax
    add bx,4
    loop redirect_other_seg
;過程計算段地址
;已知dx:ax物理地址,求出段地址並存放在ax中返回    
calc_seg_base:
    push dx
    
    add ax,[cs:usr_app_base]
    adc dx,[cs:usr_app_base+2]
    shr ax,4
    ror dx,4
    and dx,0xf000
    or  ax,dx
    
    pop dx
    
    ret

三.將控制權交給用戶程序

經過jmp far 命令進行段間跳轉,這裏是,跳轉到重定位後的入口點位置,存放在ds:0x04處

jmp far [0x04]

至此,咱們的加載器就基本完成了,能夠用它來加載任何符合咱們規定格式的用戶程序(用戶程序頭部)。

相關文章
相關標籤/搜索