《PC Assembly Language》读书笔记
本書下載地址:pcasm-book。
前言
8086處理器只支持實模式(real mode),不能滿足安全、多任務(wù)等需求。
Q:為什么實模式不安全、不支持多任務(wù)?為什么虛模式能解決這些問題?
A: 以下是根據(jù)網(wǎng)上搜索結(jié)果及自己的理解做出的解答,有待斟酌。(1) 安全:實模式下用戶可以訪問任意的物理內(nèi)存,可以修改系統(tǒng)程序或重要數(shù)據(jù)的內(nèi)容,因而不安全。虛模式下用戶能夠訪問的內(nèi)存是由Descriptor Table中的信息決定的,其基地址是事先不確定的,而長度、權(quán)限均有限制,因此相比實模式更安全。(2) 多任務(wù):多任務(wù)意味著CPU可以在不同任務(wù)之間切換,這要求不同任務(wù)之間的地址空間要相互隔離,否則從A任務(wù)切換到B任務(wù)時,B任務(wù)可能會修改A任務(wù)使用的內(nèi)存數(shù)據(jù)。實模式不支持數(shù)據(jù)隔離,而虛模式支持。
本書充分使用了兩個開源軟件:NASM匯編器和DJGPP C/C++編譯器。
書中源碼下載地址
第1章 簡介
1.1 數(shù)字系統(tǒng)
| word | 2 bytes |
| double word | 4 bytes |
| quad word | 8 bytes |
| paragraph | 16 bytes |
1.2 計算機組成
- AX/BX/CX/DX這四個16-bit的通用寄存器可以分解成兩個8-bit的寄存器
- SI和DI:index registers,通常用于指針,也可用于大多數(shù)通用寄存器可以使用的場景,但它們不能分解成8-bit的寄存器。
- BP和SP寄存器用于指向機器語言棧里面的數(shù)據(jù)。
- CS/DS/SS/ES:段寄存器。CS:Code Segment. DS: Data Segment. SS: Stack Segment. ES: Extra Segment.
- IP寄存器和CS寄存器一起作為指示CPU將要執(zhí)行的下一條指令的地址。每當(dāng)執(zhí)行完一條指令,IP將會指向下一條指令的地址。
- FLAGS寄存器存儲上一條指令的執(zhí)行結(jié)果的重要信息。
- 相比8086,通用寄存器擴展到32-bit。為了后向兼容,AX仍然代表16-bit寄存器,而用EAX來代表擴展的32-bit寄存器。
- 段寄存器仍然是16-bit,此外增加了兩個備用的段寄存器FS和GS。
- 實模式的物理地址計算公式:physical address = 16 * selector + offset
- 為什么不直接存儲物理地址,而將其分解成兩部分?回答:8086的地址需要20-bit的數(shù)字來表示,而寄存器只有16-bit,因此需要用兩個16-bit的數(shù)值來表示。
- 那為什么不是將20-bit的地址拆分成最高的4-bit和剩下的16-bit,而是用這種有點費解的表示方式? 回答:不知道...
- 實模式的缺點1:一個selector最多只能reference 64KB內(nèi)存,當(dāng)代碼大于64KB時,需要分段存儲,在不同段之間跳轉(zhuǎn)時,CS的值也需要改變。(CS的值存儲在selector中)
- 實模式的缺點2:同一個物理地址對應(yīng)的段地址不唯一,可以有多種表示方式。
80286處理器使用16-bit保護模式。
- 實模式下selector的值代表物理內(nèi)存的paragraph數(shù)目,保護模式下selector的值代表descriptor table的索引。
- 16-bit 保護模式使用了虛擬內(nèi)存的技術(shù),其基本思想是:只保留程序正在使用的數(shù)據(jù)和代碼在內(nèi)存中,其他數(shù)據(jù)和代碼臨時存儲在磁盤上。
- descriptor table記錄有每個段的信息:是否在內(nèi)存中、內(nèi)存地址、訪問權(quán)限等。
- 16-bit 保護模式的一大缺點是offset仍然是16-bit,導(dǎo)致segment大小仍然現(xiàn)在在64KB。
80386處理器使用32-bit保護模式,它與80286使用的16-bit保護模式的主要區(qū)別是:
- offset被擴展到32-bits,因此offset的大小增大到40億,從而段大小增大到4GB。
- 段可以劃分成大小為4KB的頁。虛擬內(nèi)存系統(tǒng)基于頁而非段來工作。
- 每種中斷都分配有一個數(shù)字,用于索引中斷向量表,找到對應(yīng)的中斷handler來處理當(dāng)前中斷。
- 外部中斷是指由鼠標(biāo)、鍵盤、定時器等外圍設(shè)備觸發(fā)的中斷。
- 內(nèi)部中斷是指有CPU內(nèi)部觸發(fā)的中斷,內(nèi)部中斷可能來自運行error或中斷指令。Error Interrupt也被稱作trap,由中斷指令觸發(fā)的中斷也被稱為軟件中斷。
1.3 匯編語言
- 匯編器是一個將匯編語言轉(zhuǎn)換為機器語言的程序。
- 匯編語言與高級語言的差異之一:每條匯編語言語句直接代表一條機器指令,而每條高級語言語句可能需要轉(zhuǎn)換成多條機器指令。
- 匯編語言與高級語言的差異之二:不同類型的CPU使用的匯編語言不相同,而高級語言則可以相同。因此匯編語言的可移植性要低于高級語言。
- 本書使用NASM匯編器(Netwide Assembler),更通用的匯編器是微軟匯編器MASM(Microsoft's Assembler)和Borland匯編器TASM。
操作數(shù)有4種類型:
- register
- memory
- immediate
- implied:沒直接表示出來的數(shù),比如自加操作中的1.
匯編語言也有類似C語言的預(yù)處理語句,其語句是以%開頭。
- SIZE equ 100
- %define SIZE 100
- L1 db 0: byte labeled L1 with initial value 0
- L2 dw 1000: word labeled L2 with initial value 1000
- L7 resb 1: 1 uninitialized byte labeled L7
- letters for RESX and DX Directives:
- B: byte
- W: word
- D: double word
- Q: quad word
- T: ten bytes
- label可以用來指向代碼中的數(shù)據(jù),label相當(dāng)于指針,在label前后加上中括號([])則表示對指針?biāo)赶虻臄?shù)據(jù),類似C語言的星號。
- 作者在asm_io.inc文件中封裝了C語言的I/O函數(shù),包括print_int, print_char, print_string, print_nl, read_int和read_char。
- 疑問:1.3.6節(jié)說匯編語言可以使用C標(biāo)準(zhǔn)庫的I/O函數(shù),為什么底層語言可以調(diào)用高級語言的函數(shù)的?
- 匯編語言文件包含:%include "asm_io.inc"
- 函數(shù)調(diào)用:使用CALL指令。
1.4 創(chuàng)建程序
- "error: instruction not supported in 64-bit mode"的解決方法:nasm的格式選項改用elf代替elf64,gcc選項增加-m32
- "relocation R_X86_64_32S against '.text' can not be used when making a PIE object; recompile with -fPIC"的解決方法:gcc選項增加-no-pie
- "undefined reference to '_printf'"的解決方法:使用nasm編譯asm_io.asm時,增加-d ELF_TYPE選項。
- 已初始化的數(shù)據(jù)存儲在.data段
- 未初始化的數(shù)據(jù)存儲在.bss段
- 代碼存儲在.text段
global:匯編語言的label默認擁有internal scope,這意味著只有同一個模塊的代碼可以使用此label。globel指令使label擁有external scope,使得程序中任意模塊都可使用此label。
windows的目標(biāo)文件是coff(Common Object File Format)格式的,linux的目標(biāo)文件是elf(Executable and Linkable Format)格式。
-l listing-file選項可以讓nasm生成一個包含匯編信息的list文件。該文件第2列是數(shù)據(jù)或代碼在段中的偏移,注意這個偏移值不一定是最終形成整個程序時的真實偏移值,因為不同模塊都可能在數(shù)據(jù)段定義了自己的label,在鏈接階段,所有數(shù)據(jù)段的label定義匯總在一個數(shù)據(jù)段里,這時鏈接器需要重新計算各個label 的偏移。
- IBM框架、大部分RISC處理器和摩托羅拉處理器都是大端序,而Intel處理器是小端序。
- 需要關(guān)注字節(jié)序的場景:
- 當(dāng)字節(jié)數(shù)據(jù)在不同主機間傳輸時
- 當(dāng)字節(jié)數(shù)據(jù)作為多字節(jié)整數(shù)寫到內(nèi)存,然后逐個字節(jié)讀取時(或者相反)
- 字節(jié)序?qū)?shù)組元素的順序不影響,數(shù)組的第一個元素永遠在最小的地址。但字節(jié)序?qū)?shù)組的每個單獨元素還是會有影響(比如元素是多字節(jié)的整數(shù)時)。
第2章 匯編語言基礎(chǔ)
整數(shù)
- 采用2的補碼的形式來表示負數(shù)。2的補碼是指對正數(shù)的二進制序列取反碼后加1.阮一峰的一篇日志關(guān)于2的補碼解釋了取反碼加1的原因:以-5為例,-5=0-5=100000000 - 00000101=11111111 - 00000101 + 1.
- 當(dāng)多個數(shù)據(jù)之間進行運算時,往往需要增大或減少數(shù)據(jù)的size,這時需要擴展符號位。對unsigned型整數(shù),符號位用0來擴展;對signed型整數(shù),正數(shù)用0來擴展符號位,負數(shù)用1來擴展符號位。
- MOVZX用于無符號正數(shù)擴展,比如將al的值擴展到ax,將ax的值擴展到eax等。
- MOVSX用于有符號正數(shù)擴展。
- 數(shù)據(jù)類型轉(zhuǎn)換
- CBW: Covert Byte to Word. extends AL to AX
- CWD: Covert Word to Double word. extends AX to DX:AX
- CWDE: Covert Word to Double word Extended. extends AX to EAX
- CDQ: Convert Double word to Quad word. extends EAX to EDX:EAX
- EOF是一個用來表示文件結(jié)束的宏(通常被定義為-1),而不是真實存在的字符。
- 加法:add
- 減法:sub
- 無符號乘法:mul source。其中source是寄存器或內(nèi)存地址,不能是立即數(shù)。另一個操作數(shù)根據(jù)size大小存儲在AL、AX、DX:AX或EDX:EAX中。
- 有符號乘法:
- imul source1
- imul dest, source1
- imul dest, source1, source2
- 無符號除法:div source
- 有符號除法:idiv source
- 取反:NEG
控制結(jié)構(gòu)
- 控制結(jié)構(gòu)根據(jù)對數(shù)據(jù)的比較來決定執(zhí)行流程,數(shù)據(jù)比較的結(jié)果存儲在FLAGS寄存器中。
- 80x86使用CMP指令來實現(xiàn)比較,其原理是將兩個操作數(shù)相減,根據(jù)差值設(shè)置FLAGS寄存器。
- 無符號整數(shù)的比較,需要關(guān)注兩個寄存器:ZF(zero)和CF(carry)。
- 有符號整數(shù)的比較,需要關(guān)注三個寄存器:ZF(zero)、OF(overflow)和SF(sign)。
- 無條件分支:JMP、SHORT、NEAR和FAR。
- 有條件分支:JE、JZ等。
- LOOP: ECX減1,如果ECX不為0,則跳到label
- LOOPE, LOOPZ: ECX減1,如果ECX不為0且ZF=1,則跳到label
- LOOPNE, LOOPNZ: ECX減1,如果ECX不為0且ZF=0,則跳到label
第3章 位操作
移位操作
- 指令:SHL和SHR
- 移動的位數(shù)可以是常量或者是存儲在CL寄存器的值
- 移走的那一位的值存儲在CF寄存器中
- 指令:SAL和SAR
- SAL和SHL的行為完全一樣。So,如果左移時改變了符號位怎么辦?
- SAR用符號位來填充最高位。
左移相當(dāng)于乘以2,右移相當(dāng)于除以2.
- 指令:ROL和ROR
- 移走的那一位的值存儲在另一端空出來的位上。
布爾位操作
計算整數(shù)中1的位數(shù)
第4章 子程序
調(diào)用者和被調(diào)用者必須統(tǒng)一約定好如何傳遞數(shù)據(jù)。關(guān)于如何傳遞數(shù)據(jù)的規(guī)則被稱為calling conventions。
間接尋址
間接尋址允許寄存器像指針變量一樣操作,但要注意寄存器沒有數(shù)據(jù)類型的概念,寄存器目前是不是作為指針使用、指向什么類型的數(shù)據(jù)等完全取決于使用的指令。這是匯編語言比高級語言更容易出錯的原因之一。
所有32位的通用寄存器和index寄存器(ESI、EDI)都可用于間接尋址,而16位或8位的寄存器則不能(伍注:我想應(yīng)該是因為地址是32位的,少于32位的寄存器存不下)
子程序舉例
伍注:call指令背后需要做存儲返回地址等操作,留意一下。
$操作符返回當(dāng)前行對應(yīng)的內(nèi)存地址。
棧
- SS(Stack Segment):存儲棧的段地址
- SP(Stack Pointer):存儲棧的偏移地址,也就是棧頂數(shù)據(jù)的地址
- BP(Base Pointer): 和SP聯(lián)合使用,在尋找棧中的數(shù)據(jù)和使用個別的尋址方式時會用到。比如說,堆棧中壓入了很多數(shù)據(jù)或者地址,你肯定想通過SP來訪問這些數(shù)據(jù)或者地址,但SP是要指向棧頂?shù)?#xff0c;是不能隨便亂改的,這時候你就需要使用BP,把SP的值傳遞給BP,通過BP來尋找堆棧里數(shù)據(jù)或者地址。
PUSH指令在棧頂插入一個double word(4字節(jié)),并導(dǎo)致ESP的值減4;POP指令讀取棧頂?shù)囊粋€double word,并導(dǎo)致ESP的值加4.
棧可以用來臨時存儲數(shù)據(jù),以及在進行子程序調(diào)用時傳遞參數(shù)和局部變量。
80x86可以通過PUSHA指令來將EAX、EBX、ECX、EDX、ESI、EDI和EBP寄存器的值全部壓棧,而通過POPA指令將它們的值全部出棧。
CALL指令會進行無條件跳轉(zhuǎn)并且將下一條指令的地址壓棧,RET指令會將下一條指令的地址出棧并且跳到那個地址。
調(diào)用約定(Calling Conventions)
存儲在棧中的參數(shù)在子程序中不會出棧,而是通過指針來訪問。
- C calling Convention: 子程序結(jié)束后,由調(diào)用者負責(zé)清除棧中傳遞給子程序的參數(shù)。這樣做的原因是C語言允許函數(shù)參數(shù)數(shù)目可變,這種情況下子程序不能判斷輸入?yún)?shù)的數(shù)目,因此由調(diào)用者來清除更容易。
- Pascal calling convention: 由子程序負責(zé)清除棧中的輸入?yún)?shù)。這樣相對C語言效率更高些,因為清棧的代碼只需在子程序生成一次即可,而不需要每次調(diào)用時都生成一遍。Pascal不需要函數(shù)參數(shù)可變,因此不存在C語言的問題。
- 可重入:每次調(diào)用子程序時,會從棧中申請內(nèi)存來存儲局部變量,子程序結(jié)束后會釋放內(nèi)存。這樣多次調(diào)用子程序也不會有影響。(伍注:試想如果使用固定內(nèi)存來存儲局部變量,多次調(diào)用子程序時就會相互影響,從而不能滿足可重入的要求)
- 節(jié)省內(nèi)存:存儲在棧中的數(shù)據(jù)只會在子程序生命周期中使用內(nèi)存,不存儲在棧中的數(shù)據(jù)會在整個程序生命周期中都要使用內(nèi)存。
多模塊程序
多模塊程序是指有多個目標(biāo)文件組成的程序。
模塊A如果想訪問模塊B的label1,需要在模塊B中將label1聲明為global,并且在模塊A中將label1定義為extern。
匯編語言與C語言交互
可以在C語言程序中調(diào)用匯編子程序。
C語言假定子程序維持以下寄存器的值不變:EBX,ESI,EDI,EBP,CS,DS,SS,ES。具體地說,EBX、ESI和EDI的值必須保持不變,因為C語言使用這些寄存器來存儲register變量。上述其他寄存器在子程序內(nèi)部可以修改,但在子程序返回前需要恢復(fù)原始值。
大部分C編譯器會在函數(shù)名、全局變量名或靜態(tài)變量名的前面加上一個下劃線,比如DJGPP的gcc編譯器。而在Linux系統(tǒng)中則保持原名、不帶下劃線。
根據(jù)C調(diào)用約定,函數(shù)參數(shù)壓棧的順序與它們出現(xiàn)在函數(shù)調(diào)用的順序相反。這樣有個好處是:對參數(shù)數(shù)目可變的函數(shù),比如printf函數(shù),格式化字符串在棧中的位置永遠是所有參數(shù)的最下面(EBP + 8),這樣子程序通過查看[EBP + 8]的內(nèi)容可以很方便地判斷出參數(shù)數(shù)目。
LEA(Load Effective Address)指令用于計算地址。如lea eax, [ebp - 8]語句的作用是把寄存器EBP存儲的內(nèi)存地址減8后得到的地址賦給寄存器EAX。
根據(jù)C調(diào)用約定,函數(shù)返回值通過寄存器來返回。所有整數(shù)類型、指針類型的數(shù)據(jù)均通過EAX寄存器返回。浮點數(shù)通過ST0寄存器返回。
- GCC編譯器運行多種盜用約定。函數(shù)的調(diào)用約定可以通過__attribute__顯式聲明,比如void f(int) __attribute__((cdecl));
- stdcall和cdecl的區(qū)別是前者要求由子程序負責(zé)清除棧中的參數(shù),因此前者智能用于參數(shù)數(shù)目固定的函數(shù)。
- GCC提供了regparm屬性用來告訴編譯器用寄存器來傳遞最多3個整數(shù)參數(shù),而不是用棧來傳遞。
- Borland和Microsoft為C語言增加了__cdecl和__stdcall關(guān)鍵字,這兩個關(guān)鍵字作為函數(shù)修飾符出現(xiàn)在函數(shù)名的前面。比如:void __cdecl f(int);
- cdecl vs stdcall:
- cdecl的優(yōu)點是簡潔靈活,可以用于任意類型的C函數(shù)和C編譯器,缺點主要是相對其他調(diào)用約定速度更慢、占用內(nèi)存更多,因為每次對子函數(shù)的調(diào)用都需要生成清除棧中參數(shù)的代碼。
- stdcall的優(yōu)點是占用內(nèi)存更少,缺點主要是不支持函數(shù)參數(shù)數(shù)目可變。
- 使用寄存器來傳遞整數(shù)參數(shù)的優(yōu)點是速度更快,缺點主要是復(fù)雜,因為當(dāng)參數(shù)較多時,部分參數(shù)在寄存器中、部分在棧中。
dump_stack 1, 2, 4解釋:第一個參數(shù)“1”是一個數(shù)字label,第2、3個參數(shù)分別代表打印多少個位于EBP下面和上面的double word。
可重入和遞歸子程序
- 必須不能修改任何代碼指令
- 必須不能修改全局變量(比如存儲在data和bss段的數(shù)據(jù)),所有變量存儲在棧中。
- 可重入子程序可以被遞歸調(diào)用
- 可重入程序可以被多個進程共享
- 可重入子程序在多線程程序中工作得更好(why?)
- global: 在所有函數(shù)的外面定義。存儲在data或bss段中,存在于程序的整個生命周期中。默認可以被程序中的任意函數(shù)訪問,但如果聲明為static,則只能被同一模塊的程序訪問。
- static: 函數(shù)中被聲明為static的局部變量。存儲在data或bss段中。只能在它們所在的函數(shù)內(nèi)被訪問。
- automatic: 函數(shù)中定義的局部變量的默認類型。當(dāng)定義它們的函數(shù)被調(diào)用時,這些變量會在棧中分配到內(nèi)存空間,而函數(shù)返回時這些內(nèi)存空間會被釋放。
- register: 該關(guān)鍵字請求編譯器用寄存器來存儲當(dāng)前變量,但編譯器沒必要一定這樣做。C編譯器經(jīng)常自動將普通的auto變量存儲在寄存器中。但如果變量的地址會在代碼中用到,則不能將其存儲在寄存器中,因為寄存器沒有地址。此外結(jié)構(gòu)體類型也不能定義為register,因為寄存器存不下。
- volatile: 該關(guān)鍵字告訴編譯器當(dāng)前變量的值可能在任意時刻被修改,這意味著編譯器不能對變量的修改時間做任何猜測。此關(guān)鍵字可以避免編譯器將變量存儲在寄存器中(編譯器有時會將變量存儲在寄存器中,接著使用寄存器的值來代替變量值),以及避免編譯器自動對代碼中的某些變量賦值進行優(yōu)化(比如x = 10; y = 20; z =x這段代碼,編譯器往往自動將10復(fù)制給z)。
第5章 數(shù)組
- 在data段中定義已初始化的數(shù)組:使用db, dw, dd等指令,可以使用TIMES指令來重復(fù)聲明。
- 在bss段中定義未初始化的數(shù)組:使用resb, resw等指令。
- 在stack段中定義局部數(shù)組變量:計算出所有局部變量的總字節(jié)數(shù),然后將ESP減去此數(shù)值。如果總字節(jié)數(shù)不是4的倍數(shù),需要向上取整到4的倍數(shù),以保證ESP的值以double word為單位。
- C語言根據(jù)指針類型來判斷指針運算中需要移動多少個字節(jié),匯編語言中需要程序員來計算在不同元素間跳轉(zhuǎn)時需要偏移多少個字節(jié)。
- 間接尋址公式:[ base_reg + factor * index_reg + constant ]
- CLD: 清除方向標(biāo)志。此時基址寄存器以遞增的方式工作。(記憶:清真(情增))
- STD: 設(shè)置方向標(biāo)志。此時基址寄存器以遞減的方式工作。
- ESI(Source Index)用于讀內(nèi)存,EDI(Destination Index)用于寫內(nèi)存。
- 用于存儲串操作指令的數(shù)據(jù)的寄存器是固定的,即AL、AX或EAX。
- 串存儲指令使用ES而非DS來標(biāo)志用于寫內(nèi)存的段地址,在使用時記得初始化ES的值(在虛模式下ES會自動初始化,在實模式下則不會)。
- 不能通過MOV指令直接將DS寄存器的值復(fù)制到ES,而需要使用通用寄存器中轉(zhuǎn)。
- LODSx: 把由DS:SI指向的源串中的字節(jié)(或字)裝入到AL(或AX)中,并根據(jù)DF自動修改指針SI,以指向下一個要裝入的字節(jié)(或字)。
- STOSx: 把AL(或AX)中的內(nèi)容存儲到由ES:DI指向的目的串匯總,并根據(jù)DF自動修改指針DI,以指向下一個要寫入的字節(jié)(或字)。
- MOVSx: 將DS:SI所指向的源串中的一個字節(jié)(或字)傳送到ES:DI所指向的存儲單元,并根據(jù)DF自動修改SI和DI的值。
- CMPSx: 比較DS:SI所指向的源串中的一個字節(jié)(或字)與ES:DI所指向的目的串的一個字節(jié)(或字),根據(jù)比較結(jié)果修改FLAGS寄存器。適用于比較或搜索數(shù)組。
- SCASx:比較寄存器AL(或AX)的內(nèi)容與ES:DI所指向的目的串的一個字節(jié)(或字),根據(jù)比較結(jié)果修改FLAGS寄存器。適用于比較弧搜索數(shù)組。
- 指令前綴不是指令,而是位于串指令前面、用來改變指令行為的一個特別的字節(jié)。
- REP指令前綴:重復(fù)執(zhí)行某條指令,重復(fù)次數(shù)存儲在ECX寄存器中。
- REPx指令前綴:重復(fù)執(zhí)行某條指令,直到某個條件不再滿足或重復(fù)次數(shù)達到ECX的值。注意:當(dāng)重復(fù)操作由于比較結(jié)果不再滿足要求而中止時,基址寄存器仍然會加1、ECX寄存器仍然會減1,而FLAGS寄存器仍然保持中止時的狀態(tài)。因此,可以通過Z標(biāo)志來判斷操作中止是因為比較結(jié)果不滿足要求還是ECX變?yōu)?(ECX變?yōu)?只能說明達到最大重復(fù)次數(shù),但不能確認最后一次的比較結(jié)果,所以還是需要看FLAGS寄存器)。
第6章 浮點數(shù)
浮點數(shù)的表示
十進制小數(shù)轉(zhuǎn)換成二進制小數(shù):不斷乘以2,取個位。
IEEE定義了兩種精度不同的浮點數(shù)表示法:單精度(float)和雙精度(double)。Intel的數(shù)學(xué)協(xié)處理器還使用了第三種精度更高的表示法,叫做extended precision。
注意某個數(shù)值在A進制下是有限小數(shù),在B進制下卻可能是無限循環(huán)小數(shù)。比如1/3在十進制下是無限循環(huán)小數(shù)0.3333···,在三進制下則是0.1.
- s: 最高位是符號位,0為整數(shù),1為負數(shù)
- e: 第30~23位是偏移后的指數(shù),其數(shù)值為正確的指數(shù)值加上127.指數(shù)為0或0xFF時有特殊含義。
- f: 第22~0位是底數(shù),其數(shù)值為緊接在個位的1后面的23位。
- 單精度的整數(shù)大小范圍是1.0*2^(-126) ~ 1.1111...*2^127,對應(yīng)十進制的1.1755*10^(-35) ~ 3.4028*10^35.
- e = 0 and f = 0:表示0,注意存在+0和-0(視最高位的符號位而定)
- e = 0 and f != 0:表示非規(guī)范化的小數(shù),用于數(shù)值非常小的小數(shù)。
- e = FF and f = 0: 表示無限大(符號位為正)或無限小(符號位為負)
- e = FF and f != 0: 表示不確定的結(jié)果,NaN(Not a Number)。比如計算負數(shù)的平方根、將兩個無限的數(shù)相加時會返回NaN。
- 指數(shù)的位數(shù)增加到11位,指數(shù)偏移量由127改為1023.
- 底數(shù)的位數(shù)增加到52位
- 單精度的整數(shù)大小范圍為10^(-308)至10^(308).
浮點數(shù)的四則運算
由于計算機的位數(shù)有限,許多浮點數(shù)不能被精確表示。
數(shù)字協(xié)處理器
Intel提供一個額外芯片(數(shù)學(xué)協(xié)處理器)來支持浮點數(shù)運算,8086、80286、80386處理器對應(yīng)的協(xié)處理器分別是8087、80287、80387。自Pentium以后數(shù)學(xué)協(xié)處理器就做進CPU里面了。
- 數(shù)字協(xié)處理器有8個浮點寄存器,名字分別是ST0,ST1,...ST7。每個寄存器能存儲80位的數(shù)據(jù),并且是按照棧的LIFO方式來管理數(shù)據(jù)的。
- ST0永遠存儲棧頂數(shù)據(jù)的地址。
- 數(shù)字協(xié)處理器也有1個狀態(tài)寄存器。
- 為區(qū)分正常CPU指令,數(shù)字協(xié)處理器的指令均以F開頭。
- 加載和保存指令
- FLD source: 從內(nèi)存中加載一個浮點數(shù)到棧頂,source可以是立即數(shù)或協(xié)處理器寄存器。
- FILD source: 從內(nèi)存中加載一個整數(shù),將其轉(zhuǎn)化為浮點數(shù)并保存到棧頂。source是立即數(shù)。
- FLD1: 保存數(shù)字1到棧頂
- FLDZ: 保存數(shù)字0到棧頂
- FST dest: 保存棧頂?shù)絻?nèi)存。dest是立即數(shù)或協(xié)處理器寄存器。
- FSTP dest: 保存棧頂?shù)絻?nèi)存,并將棧頂?shù)臄?shù)字出棧。
- FIST dest: 將棧頂數(shù)字轉(zhuǎn)換成整數(shù),并保存到內(nèi)存中。dest是單字或雙字。
FISTP:除了棧頂數(shù)字出棧和dest可以是四字外,與FIST相同。
- FXCH STn: 交換ST0和STn在棧中的數(shù)值
- FFREE STn: 釋放棧中的一個寄存器(即將該寄存器標(biāo)識為unused或empty)
- 加減法指令
- FADD src: ST0 += src。src可以是協(xié)處理器寄存器或立即數(shù)。
- FADD dest, ST0: dest += ST0. dest是協(xié)處理器寄存器。
- FADDP dest或FADDP dest ST0: dest += ST0,然后出棧。
- FIADD src: ST0 += (float) src. src是立即數(shù)(整數(shù))。
- FSUB src: ST0 -= src. src可以是協(xié)處理器寄存器或立即數(shù)。
- FSUBR src: ST0 = src - ST0. src同上。
- FSUB dest, ST0: dest -= ST0. dest是協(xié)處理器寄存器。
- FSUBR dest, ST0: dest = ST0 - dest.
- FSUBP dest或FSUBP dest, ST0: dest -= ST0,然后出棧。
- FSUBRP dest或FSUBRP dest, ST0: dest = ST0 - dest,然后出棧。
- FISUB src: ST0 -= (float) src.
- FISUBR src: ST0 = (float) src - ST0.
- 乘除法指令
- FMUL src: ST0 *= src.
- FMUL dest, ST0: dest *= ST0.
- FMULP dest或FMULP dest, ST0: dest *= ST0,然后出棧。
- FIMUL src: ST0 *= (float) src.
- FDIV src: ST0 /= src.
- FDIVR src: ST0 = src / ST0.
- FDIV dest, ST0: dest /= ST0.
- FDIVR dest, ST0: dest = ST0 / dest.
- FDIVP dest或FDIVP dest, ST0: dest /= ST0,然后出棧。
- FDIVRP dest或FDIVRP dest, ST0: dest = ST0 / dest,然后出棧。
- FIDIV src: ST0 /= (float)src.
- FIDIVR src: ST0 = (float)src / ST0.
- 比較指令
- FCOM src: 比較ST0和src。
- FCOMP src: 比較ST0和src,然后出棧。
- FCOMPP: 比較ST0和ST1,然后出棧兩次。
- FICOM src: 比較ST0和(float)src。
- FICOMP src: 比較ST0和(float)src,然后出棧。
- FTST: 比較ST0和0.
- 宏指令
- FCHS: ST0 = - ST0
- FABS: ST0 = |ST0|
- FSORT: ST0 = sqrt(ST0)
- FSCALE: ST0 = ST0 * 2^[ST1].
第7章 結(jié)構(gòu)體與C++
匯編與C++
- C++允許多個函數(shù)名字相同,只要它們的參數(shù)類型不完全相同即可。注意:C++不允許兩個函數(shù)名字和參數(shù)類型完全相同而只有返回值類型不同。
- C++使用“函數(shù)名字+函數(shù)參數(shù)類型縮寫”的方式來修飾函數(shù)名。(注意名字修飾時沒用到返回值類型,這也是為啥C++ 不允許兩個函數(shù)只有返回值類型不同的原因。)
- C++對所有函數(shù)都添加函數(shù)參數(shù)類型信息來加以修飾,因為它無法判斷特定函數(shù)是否被重載。
- C++對全局變量也都添加函數(shù)參數(shù)類型信息來加以修飾。
- 類型安全鏈接(typesafe linking):函數(shù)或全局變量在不同地方的類型不一致。
- 由于不同編譯器使用不同的名字修飾規(guī)則,不同編譯器編譯的C++代碼可能無法鏈接到一起。在使用已經(jīng)編譯好的C++庫時需要注意其使用的編譯器的名字修飾規(guī)則。
- 由于C和C++的名字修飾規(guī)則不同,在C++中調(diào)用C函數(shù)時可能會鏈接失敗,為解決該問題,可以使用extern "C"語句來告訴編譯器對應(yīng)的函數(shù)或全局變量使用傳統(tǒng)C的編譯鏈接方式。
引用:C++相對C語言引入的新特性。它允許程序員無需使用指針就能傳遞參數(shù)地址給函數(shù)。
內(nèi)聯(lián)函數(shù):不會發(fā)生函數(shù)調(diào)用,而是將函數(shù)體的代碼替換到調(diào)用處。內(nèi)聯(lián)函數(shù)的主要缺點是內(nèi)聯(lián)代碼不會鏈接,因此所有需要使用該內(nèi)聯(lián)函數(shù)的文件都必須能訪問到該內(nèi)聯(lián)函數(shù)所在的文件;而且,當(dāng)修改了內(nèi)聯(lián)函數(shù)的實現(xiàn),所有調(diào)用該函數(shù)的文件需要重新編譯。
轉(zhuǎn)載于:https://www.cnblogs.com/wuhualong/p/read_pc_assembly_language.html
總結(jié)
以上是生活随笔為你收集整理的《PC Assembly Language》读书笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海豚调度器初次使用 .......
- 下一篇: 吐槽 树洞