日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《PC Assembly Language》读书笔记

發(fā)布時間:2024/3/13 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《PC Assembly Language》读书笔记 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本書下載地址: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)

  • 內(nèi)存單位
  • 內(nèi)存單位字節(jié)數(shù)
    word2 bytes
    double word4 bytes
    quad word8 bytes
    paragraph16 bytes
  • ASCII使用一個字節(jié)來對字符編碼,而Unicode使用兩個字節(jié)。
  • 1.2 計算機組成

  • 8086 16-bit寄存器
    • 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é)果的重要信息。
  • 80386 32-bit寄存器
    • 相比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)的段地址不唯一,可以有多種表示方式。
  • 16-bit 保護模式
    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。
  • 32-bit 保護模式
    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ù)
    操作數(shù)有4種類型:
    • register
    • memory
    • immediate
    • implied:沒直接表示出來的數(shù),比如自加操作中的1.
  • driectives
    匯編語言也有類似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指令。
  • 調(diào)試:通過打印問題現(xiàn)場中寄存器、內(nèi)存、棧和數(shù)學(xué)處理器里面的數(shù)值來進行調(diào)試。作者在asm_io.inc文件中封裝了調(diào)試相關(guān)函數(shù),包括dump_regs,dump_mem,dump_stack和dump_math。
  • 1.4 創(chuàng)建程序

  • 使用NASM編譯匯編程序
  • nasm -f elf hello.asm gcc -o hello hello.o
  • 解決編譯錯誤
    • "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。

  • enter指令創(chuàng)建一個棧幀,leave指令銷毀一個棧幀。enter指令的第一個參數(shù)用來指示需要為局部變量申請的內(nèi)存大小。enter和leave指令的等價代碼如下所示:
  • ; enter push ebp mov esp, ebp sub firstPram, esp ; leave mov esp, ebp pop ebp
  • 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ù)

  • 整數(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),而不是真實存在的字符。
  • 2的補碼的四則運算
    • 加法: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等。
  • 循環(huán)
    • 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寄存器中
  • 算術(shù)移位
    • 指令:SAL和SAR
    • SAL和SHL的行為完全一樣。So,如果左移時改變了符號位怎么辦?
    • SAR用符號位來填充最高位。
  • 左移相當(dāng)于乘以2,右移相當(dāng)于除以2.

  • 循環(huán)移位
    • 指令:ROL和ROR
    • 移走的那一位的值存儲在另一端空出來的位上。
  • 布爾位操作

  • AND
  • OR
  • XOR
  • NOT
  • TEST:TEST指令執(zhí)行一個AND操作,根據(jù)結(jié)果來設(shè)置FLAGS寄存器,而不保存結(jié)果。
  • 計算整數(shù)中1的位數(shù)

  • 逐位檢查:常規(guī)方法
  • 更快的檢查:data = data & (data - 1)
  • 以空間換時間:事先存儲0~255的1的數(shù)目,然后對int型整數(shù)每個字節(jié)查表再匯總。
  • 很巧妙但實在不知道怎么想出來的方法:x = (x & mask[i]) + ((x >> shift) && mask[i]);
  • 第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)存地址。

  • 棧相關(guān)寄存器
    • 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ù)在子程序中不會出棧,而是通過指針來訪問。

  • 80386提供EBP寄存器來訪問棧中的數(shù)據(jù)。C語言calling conventions要求子程序首先將EBP的值入棧,然后將ESP的值賦給EBP。在子程序運行過程中,ESP的值隨著數(shù)據(jù)的入棧或出棧而改變,但EBP的值保持為ESP的原始值。在子程序結(jié)束時,EBP需要恢復(fù)為原始值(伍注:我想是因為在嵌套場景下EBP保存了父程序的ESP的原始值,若不能正常恢復(fù)EBP的話,父程序的數(shù)據(jù)訪問就會亂套)。
  • subprogram_label:push ebp ; save original EBP value on stackmov ebp, esp ; new EBP = ESP ; subprogram codepop ebp ; restore original EBP valueret
  • 子程序的開始和結(jié)束可以使用兩條簡單的指令:ENTER和LEAVE。ENTER指令需要兩個輸入?yún)?shù),C語言調(diào)用約定中第二個參數(shù)永遠為0,第一個參數(shù)則是局部變量需要使用的字節(jié)數(shù)。LEAVE指令不需要輸入?yún)?shù)。
  • subprogram_label:enter LOCAL_BYTES, 0 ; = # bytes needed by locals ; subprogram codeleaveret
  • 清除棧中的輸入?yún)?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寄存器返回。

  • 調(diào)用約定
    • 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。

  • 可重入和遞歸子程序

  • 一個可重入的子程序必須滿足以下性質(zhì):
    • 必須不能修改任何代碼指令
    • 必須不能修改全局變量(比如存儲在data和bss段的數(shù)據(jù)),所有變量存儲在棧中。
  • 編寫可重入代碼的優(yōu)勢:
    • 可重入子程序可以被遞歸調(diào)用
    • 可重入程序可以被多個進程共享
    • 可重入子程序在多線程程序中工作得更好(why?)
  • C變量存儲類型
    • 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ù)組

  • 定義數(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為單位。
  • 訪問數(shù)組元素
    • C語言根據(jù)指針類型來判斷指針運算中需要移動多少個字節(jié),匯編語言中需要程序員來計算在不同元素間跳轉(zhuǎn)時需要偏移多少個字節(jié)。
    • 間接尋址公式:[ base_reg + factor * index_reg + constant ]
  • 方向標(biāo)志
    • CLD: 清除方向標(biāo)志。此時基址寄存器以遞增的方式工作。(記憶:清真(情增))
    • STD: 設(shè)置方向標(biāo)志。此時基址寄存器以遞減的方式工作。
  • 讀寫內(nèi)存
    • 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ù)組。
  • REP指令前綴:
    • 指令前綴不是指令,而是位于串指令前面、用來改變指令行為的一個特別的字節(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.

  • IEEE單精度表示法:32位,一般能精確到7位十進制有效數(shù)字。
    • 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和f的特殊值
    • 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。
  • IEEE雙精度表示法:64位。一般能精確到15位十進制有效數(shù)字。
    • 指數(shù)的位數(shù)增加到11位,指數(shù)偏移量由127改為1023.
    • 底數(shù)的位數(shù)增加到52位
    • 單精度的整數(shù)大小范圍為10^(-308)至10^(308).
  • 浮點數(shù)的四則運算

  • 由于計算機的位數(shù)有限,許多浮點數(shù)不能被精確表示。

  • 程序員必須記住:計算機中的浮點數(shù)運算得到的永遠是近似值。一個常見的編程錯誤是使用等號來比較兩個浮點數(shù)是否相等,比如:
  • if ( f(x) == 0.0 ) // wrong if ( fabs(f(x)) < EPS ) // true. Here EPS is a macro defined to be a very small positive value (like 1 * 10^(-10))

    數(shù)字協(xié)處理器

  • Intel提供一個額外芯片(數(shù)學(xué)協(xié)處理器)來支持浮點數(shù)運算,8086、80286、80386處理器對應(yīng)的協(xié)處理器分別是8087、80287、80387。自Pentium以后數(shù)學(xué)協(xié)處理器就做進CPU里面了。

  • 數(shù)字協(xié)處理器的寄存器
    • 數(shù)字協(xié)處理器有8個浮點寄存器,名字分別是ST0,ST1,...ST7。每個寄存器能存儲80位的數(shù)據(jù),并且是按照棧的LIFO方式來管理數(shù)據(jù)的。
    • ST0永遠存儲棧頂數(shù)據(jù)的地址。
    • 數(shù)字協(xié)處理器也有1個狀態(tài)寄存器。
  • 數(shù)字協(xié)處理器的指令
    • 為區(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。