void memset(void* p_dst, char ch, int size)
這是memset
的函數原型,在C語言中使用這個函數時,需按這個原型傳參。函數
memset
的功能是:用size
個char
類型的數據填充初始內存地址是p_dst
的這片內存空間。oop
global memset memset: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 8] ; Destination mov edx, [ebp + 12] ; Char to be putted mov ecx, [ebp + 16] ; Counter .1: cmp ecx, 0 ; 判斷計數器 jz .2 ; 計數器爲零時跳出 mov byte [edi], dl ; ┓ inc edi ; ┛ dec ecx ; 計數器減一 jmp .1 ; 循環 .2: pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; 函數結束,返回
nasm
彙編寫函數的模板是:測試
; 函數名 funcName: ; 被修改的寄存器都要事先存儲到堆棧中,因此,ebp、eax、ebx、ecx都要入棧 push ebp mov ebp, esp push eax push ebx push ecx ; 調用funcName時,參數按照從右到左依次入棧 ; ebp + 0 是 eip,ebp + 4 是esp mov eax, [ebp + 16] ; 第三個參數 mov ebx, [ebp + 12] ; 第二個參數 mov ecx, [ebp + 8] ; 第一個參數 ; some code ; some code ; 在函數末尾經過出棧還原被修改過的寄存器中的值,出棧順序和簽名的入棧順序相同 pop ecx pop ebx pop eax pop ebp ; 函數末尾必須用這個指令結尾,出棧esp和eip。 ret
要在其餘文件中使用這個函數,需在本文件使用global memset
將此函數導出。rest
.1: cmp ecx, 0 ; 判斷計數器 jz .2 ; 計數器爲零時跳出 mov byte [edi], dl ; ┓ inc edi ; ┛ dec ecx ; 計數器減一 jmp .1 ; 循環
dl
是第二個參數char ch
。char
是8個字節,所以只須要使用寄存器dl
。code
mov byte [edi], dl
把ch
填充到es:edi
內存空間。視頻
.1: ;some code ;some code jmp .1
用jmp
實現循環指令。能不用loop
指令就不用。loop
指令必須與ecx
配合使用,很是容易出錯。進程
; 函數名稱是 out_byte out_byte: ; esp 是 eip mov edx, [esp + 4] ; port,第一個參數 mov al, [esp + 4 + 4] ; value,第二個參數 ; 把al寫入dx端口 out dx, al nop ; 一點延遲 nop ; 堆棧中的eip出棧 ret
; 函數名稱是 in_byte in_byte: mov edx, [esp + 4] ; port,第一個參數 xor eax, eax ; 設置eax的值是0。異或運算,不相等結果是1,相等結果是0。 in al, dx ; 把dx端口的值寫入dl nop ; 一點延遲 nop ret
disp_str: push ebp mov ebp, esp mov esi, [ebp + 8] ; pszInfo mov edi, [disp_pos] mov ah, 0Fh .1: lodsb test al, al jz .2 cmp al, 0Ah ; 是回車嗎? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax jmp .1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [disp_pos], edi pop ebp ret
理解這個函數花了不少時間。緣由是沒有及時聯想到讀寫顯存的座標知識。ip
流程是:內存
pszInfo
加載一個字節數據到al
。al
是否爲空。
回車
,打印字符,視頻段偏移量自增2個字節,而後跳轉到最外層流程1。N
行,計算公式是:視頻偏移量/160
。N+1
。(N+1)*160
。[gs:(80*1+0)*2]
,把字符寫入第1行第1列。字符串
[gs:(80*2+1)*2]
,把字符寫入第1行第2列。
lodsb
,把[ds:si]
處的數據讀入al
,si
自動自增1。
test al, al jz .2
al
爲空時,跳轉到.2
。
cmp al, 0Ah ; 是回車嗎? jnz .3
al
的值不是0Ah
時,跳轉到.3
。0Ah
是回車鍵的ASCII碼。
mov eax, edi mov bl, 160 div bl and eax, 0FFh
div
是除法。被除數在eax
中,除數在bl
中,商在al
中,餘數在ah
中。
and eax, 0FFh
獲取al
中的值。
disp_color_str: push ebp mov ebp, esp mov esi, [ebp + 8] ; pszInfo mov edi, [disp_pos] mov ah, [ebp + 12] ; color .1: lodsb test al, al jz .2 cmp al, 0Ah ; 是回車嗎? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax jmp .1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [disp_pos], edi pop ebp ret
只在disp_str
的基礎上增長了一句mov ah, [ebp + 12] ; color
,設置打印字符串的顏色,其餘部分與disp_str
徹底一致。
restart: mov esp, [p_proc_ready] lldt [esp + P_LDT_SEL] lea eax, [esp + P_STACKTOP] mov dword [tss + TSS3_S_SP0], eax pop gs pop fs pop es pop ds popad add esp, 4 iretd
這是構造好進程後,啓動第一個進程使用的函數。
語法很好懂,難點在業務邏輯。
lldt [esp + P_LDT_SEL]
,加載LDT。[esp + P_LDT_SEL]
是LDT的選擇子。
假設,si = 1000h,ds = 50000h,(51000h)=1234h,那麼
lea ax, [ds:si]
執行後,ax的值是1000h
。mov ax, [d:si]
執行後,ax的值是1234h
。lea eax, [esp + P_STACKTOP]
執行後,eax
的值是一個偏移量,是內存地址,而不是內存esp + P_STACKTOP
中的值。
popad
依次出棧EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX
。
暫時沒有找到權威資料。
[p_proc_ready]
指向進程表的初始位置。jP_LDT_SEL
是regs的長度。[esp + P_LDT_SEL]
跳過regs,指向進程表的第二個成員結構,是LDT的選擇子。[esp + P_LDT_SEL]
和[esp + P_STACKTOP]
指向同一個內存單元。總之,這個時候,指向regs的棧頂。mov dword [tss + TSS3_S_SP0], eax
,讓TSS
的sp0
指向regs的棧頂。ss0
和sp0
,因此,出棧的棧是進程表中的regs。