接着上一篇博文說。oop
每一個代碼段都有本身的段界限。同棧段一個道理,有效界限和G位相關。spa
G=0:有效界限 = 描述符中的段界限.net
G=1:有效界限 = 描述符中的段界限值 * 0x1000 + 0xFFFcode
當處理器取指令的時候,偏移地址由EIP提供,EIP的範圍應該在 [0,有效界限] 之間(爲了說明問題,我就用數學上的閉區間表示了)。不然會引起異常。blog
對於本代碼,代碼段描述符中的界限值是0x1FF,G=0,那麼有效界限=0x1FF,也就是說這個值就是段內最後一個容許訪問的偏移地址。排序
再舉一個例子,源文件中ip
71 mov dword [es:0x0b8000],0x072e0750 ;字符'P'、'.'及其顯示屬性 72 mov dword [es:0x0b8004],0x072e074d ;字符'M'、'.'及其顯示屬性 73 mov dword [es:0x0b8008],0x07200720 ;兩個空白字符及其顯示屬性 74 mov dword [es:0x0b800c],0x076b076f ;字符'o'、'k'及其顯示屬性
這四行對應的.list文件以下v8
能夠看到,第71行,mov dword [es:0x0b8000],0x072e0750 這條指令,其偏移地址爲 0xB8~0xC2; 那麼,要想這條指令順利執行,定義代碼段時,有效界限就要大於等於0xC2.當等於0xC2的時候,這條指令能夠順利執行,可是下一條指令的執行會引起異常。字符串
也就是說,若是某條指令的偏移地址爲M~N,那麼這條指令被順利執行的必要條件是集合[M,N]屬於集合[0,代碼段的有效界限]。(請原諒我借用集合來描述,但是我再也想不出什麼簡明的表達了)get
這裏所說的數據段,特指向上擴展的數據段,有別於棧段和向下擴展的數據段。
訪問數據段時,是否越界與操做數的長度有關。
對於向上擴展的數據段,有效界限的計算方法依然是:
G=0:有效界限 = 描述符中的段界限
G=1:有效界限 = 描述符中的段界限值 * 0x1000 + 0xFFF
舉例來講,第74行
74 mov dword [es:0x0b800c],0x076b076f ;字符'o'、'k'及其顯示屬性
這條指令的目的是把0x076b076f寫入偏移地址爲0x0b800c~0x0b800f的4個存儲單元。若是要成功寫入,那麼0x0b800c~0x0b800f必須在數據段的有效界限內。也就是要求:
[0xb800c, 0xb800f] 屬於 [0, 上擴數據段的有效界限]
本代碼中,數據段的有效界限值是0xFFFF_FFFF,也就是說能夠訪問4GB空間內的任何一個單元。
104;------------------------------------------------------------------------------- 105 string db 's0ke4or92xap3fv8giuzjcy5l1m7hd6bnqtw.' 106;-------------------------------------------------------------------------------
第105行,定義了一串字符。做者要對這串字符進行升序排序,其實「項莊舞劍;意在沛公」——排序是假,使用別名段是真。由於代碼段是不容許寫入的,因此要修改代碼段,就須要爲之建立一個別名描述符。第3三、34行,程序爲代碼段建立了一個可讀寫的數據段描述符。也就是說,咱們能夠把代碼段當成數據段來用。
33 mov dword [ebx+0x18],0x7c0001ff ;基地址爲0x00007c00,512字節 34 mov dword [ebx+0x1c],0x00409200 ;粒度爲1個字節,數據段描述符,可讀寫
第5九、60行,把這個別名段的選擇子加載到DS
59 mov eax,0x0018 60 mov ds,eax
這裏咱們用冒泡排序法,雖然這個方法效率低,可是很適合初學者入門。關於冒泡排序的知識,請參考其餘資料。
爲了說明問題,我繪製了一張圖,假如一共有5個數,要把它們從左至右按照升序排列,示意圖以下。
每比較一次,就把較大的數放在右邊。那麼第一次外循環後,最大的數就冒到了最右邊;第二次外循環後,第二大的數冒到了從右邊數第二個位置;……
若是N個數參加排序,那麼外循環須要(N-1)次。第1次須要(N-1)次比較,第2次須要(N-2)次比較,……第(N-1)次須要一次比較。
說了這麼多,我就是想說清楚代碼。
76 ;開始冒泡排序 77 mov ecx,pgdt-string-1 ;遍歷次數=串長度-1 78 @@1: 79 push ecx ;32位模式下的loop使用ecx 80 xor bx,bx ;32位模式下,偏移量能夠是16位,也能夠 81 @@2: ;是後面的32位 82 mov ax,[string+bx] 83 cmp ah,al ;ah中存放的是源字的高字節 84 jge @@3 85 xchg al,ah 86 mov [string+bx],ax 87 @@3: 88 inc bx 89 loop @@2 90 pop ecx 91 loop @@1
第77行,剛開始,ECX保存着外循環的次數(字符數-1)。
79行把ECX壓棧是由於內循環也要用這個值(這個值就是本循環比較的次數),並且在內循環中,這個次數會變化。爲了避免破壞外循環的次數,因此要壓棧保存。
第81~89行,是內循環,完成字符對比的工做。首先一次性讀入2個字符到AX中,AL中存放的是前一個(低地址處的)字符,AH中存放的是後一個(高地址處的)字符,若是前者大,則交換AL和AH的內容,而後把AX重寫入原來的存儲單元。接下來,BX加1,以指向下一個字符。
93 mov ecx,pgdt-string 94 xor ebx,ebx ;偏移地址是32位的狀況 95 @@4: ;32位的偏移具備更大的靈活性 96 mov ah,0x07 97 mov al,[string+ebx] 98 mov [es:0xb80a0+ebx*2],ax ;演示0~4GB尋址。 99 inc ebx 100 loop @@4 101 102 hlt
排序完畢後,第93~100,用於顯示最終的排序結果。
第98行表示從顯存的第2行第1列(0xb8000 + 0xa0(=160D) = 0xb80a0)開始顯示字符串。當EBX=0、一、2……時,對應的地址是0xb80a0、0xb80a二、0xb80a4……(使用比例因子就是這麼方便),注意,「0xb80a0 + ebx * 2」,這個表達式的值是在指令執行時,由處理器計算出來的。
(12章完)