03_ARMv8指令集介紹加載與存儲指令


Github地址:carloscn/uncle-ben-os at car_lab_06 (github.com)

ARMv8指令集介紹

  • A64指令集只能運行在aarch64
  • 所有A64匯編都是32 bits寬的
    • 關注指令的使用、有什么limitation
    • A64能訪問的地址數據是64位寬的
  • A64支持全部的大寫或者小寫方式
    • ARM官方大寫
    • 應用使用小寫
  • 寄存器命名
    • Wn表示32bits寬的寄存器
    • Xn表示64bits寬的寄存器
    • WZR表示32位內容全為0的寄存器
    • XZR表示64位內容全為0的寄存器
    • ...

LDR指令

LDR Xd, [Xn, $offset]

  • 【釋義】:將Xn寄存器中存儲的地址+offset地址偏移存 組成一個新的地址,把這個地址里面存儲的值放在Xd寄存器中。[]有取地址內存儲的數值的含義。

  • 【示例】:

    • S1: 使用MOV指令把0x80000加載到X1寄存器: MOV x1, 0x80000 (如果是一個數,而非#0x80000, 則是一個地址)

    • S2: 使用MOV指令把16數值加載到X3寄存器: MOV x3, 16

    • S3: 使用LDR指令讀取X1地址里面存儲的值,存儲到X0中: LDR x0,[x1] , 這個不允許->LDR x2,[0x80000]

    • S4:使用LDR指令讀取X1 + 8地址里面存儲的值,存儲到X2中:LDR x2,[x1, #8]

    • S5:使用LDR指令讀取(X1 + X3)地址里面存儲的值,存儲到X4中: LDR x4,[x1, x3]

    • S6: 使用LDR指令讀取(X1+(X3<<3))地址里面存儲的值,存儲到X5中: LDR x5,[x1,x3,lsl #3]

  • 【注意】:

    • 給的數不加任何標志的視為地址
    • 需要給立即數的場景而非地址的值,使用#
    • []有取地址值的意思
    • LDR lsl擴展指令,只支持1和3
    • LDR x2,[x1, #8] x1的值不會被更新為0x80008
  • 【變基模式】:

    • 前變基模式 pre-index: 先更新偏移地址,后訪問地址 (注意有嘆號!表示)

      LDR x6, [x1, #8]! : 將x1里面的地址增加偏移#8並賦給x1,最后將新的x1寄存器內的地址的值給x6寄存器

    • 后變基模式 post-index: 先訪問內存地址,再更新偏移地址

      LDR x6, [x1], #8 : 將x1寄存器內的地址的值賦給x6寄存器,並將x1地址偏移+8。

  • 【偽指令】:

    偽指令與指令的最大不同在於,偽指令屬於編譯器處理的范疇,偽指令會被編譯展開為多條指令;指令是CPU處理的命令的最小單元。

    • LDR x7,=0x80000 -> 等同於 MOV x7, 0x80000
    • 需要區別 LDR x7, 0x800000; 這條指令的意義是,將當前PC寄存器的地址的 + 0x80000的偏移,取出地址內容填充到x7寄存器中。

STR指令

從一個寄存器的值吐到內存中,支持立即數和寄存器操作。把Xd的值,存儲到[xn|sp]里面。

immediate-post-index: STR Xd, [Xn|SP], #<simm>

immediate-pre-index: STR Xd, [Xn|SP, #<simm>]!

  • 【示例】:

    ; Example 1:
    MOV x2, 0x400000             ; -> x2 is 0x400000
    LDR x6, =0x1234abce          ; -> x6 is 0x1234abce
    STR x6, [x2, #8]!            ; -> 把x6的值(0x1234abce),存儲到0x400008地址的內存里面
    ; What's value of x2? And the value in 0x400000 address?  
    
    ;Example 2:
    MOV x2, 0x500000			 ; -> x2 is 0x500000
    STR x6, [x2], #8			 ; -> 把x6的值(0x1234abce),存儲到0x500000里面,並將x2寄存器變為0x500008
    ; What's value of x2? And the value in 0x400000 address?  
    

MOV/MOVZ指令

MOV底層原理實際上是MOVZ,MOV 16-bit的立即數到寄存器。

MOV xd, #<imm> 16位立即數

MOVZ xd, #<imm16>, LSL #<shift> 16位的立即數,邏輯左移動 16,32,48位

LDP/STP指令

相比於LDR和STR指令(8 bytes),LDP和STP指令用於多字節(16 bytes)操作,

【釋義】:

  • LDP :LDP x3, x7, [x0] -> 從x0的值為基地址,加載地址到X3寄存器,存儲x0+8到x7寄存器。
  • STP :STP x1, x2, [x4]-> 以x4的值為基地址,存儲x1地址的值到x4,存儲x2地址的值到x4 + 8。

【練習】:

練習1: 使用LDR和STR多字節加載和存儲命令實現memset()函數,假設內存地址s是16字節對齊,count也是16字節對齊。例如:memset(0x200000, 0x55, 32)

// memset_a_byte
void *memset_a_byte (void *s, int c) {
    char *xs = s;
    *xs++ =c;
    return s;
}
// 使用STR指令,單字節操作
.global my_memset_test:
my_memset_test:

// 保存地址s到x1寄存器,保存c的值到x2寄存器,保存長度到x3寄存器
MOV x1, 0x2000000   // 這個值是需要被修改的 肯定需要STP
MOV x2, 0x55        // 這個是個固定的參數
ADD x0, x1, 32
// 確定原子操作 向地址寫值,然后地址增加
wrt:
STR x2, [x1], #8	// 把x2里面的值存儲到x1里面(0x55 -> 0x200000),接着0x200008加一
cmp x1, x0
b.cc wrt

ret
// 使用STP指令,雙字節操作
.global my_memset_test:
my_memset_test:

// 保存地址s到x1寄存器,保存c的值到x2寄存器,保存長度到x3寄存器
MOV x1, 0x2000000   // 這個值是需要被修改的 肯定需要STP
MOV x2, 0x55        // 這個是個固定的參數
ADD x0, x1, 32
// 確定原子操作 向地址寫值,然后地址增加
wrt:
STP x2, x2, [x1], #16	// 把x2里面的值存儲到x1里面(0x55 -> 0x200000),接着0x200008加一
cmp x1, x0
b.cc wrt

ret

練習二:同上,使用非對齊的memset(0x200004, 0x55, 37)

// 需要匯編和C語言混合編程實現對於非16字節對齊的地址和長度進行memset操作
// 匯編實現一個16字節的memset
// C語言用於對非對齊部分進行C語言單字節的處理,用匯編實現16字節對齊地址和16字節對齊長度的處理。

// 函數調用為 memset(0x200004, 0x55, 37)
.global asm_memset_16_byte_align:
asm_memset_16_byte_align:
ADD x4, x0, x2
wrt:
STP x1, x1, [x0], #16
CMP x0, x4
b.cc wrt
ret

void *memset (void *s, int c, int count)
{
   int align = 16;
   if (s & (align - 1)) {
     // 處理非對齊地址
   }
   // 對齊部分直接調用 asm_memset_16_byte_align(s, c, l);
   // 非對齊部分直接c語言指針訪問賦值。
}

一些需要注意的地方

  • FAQ1:加載一個很大的數值到通用寄存器,例如0xFFFF0000FFFF0000, 使用MOV指令,是否正確?

    錯誤,MOV 后面的立即數為16-bit,應該是使用LDR x1,=0xFFFF......0000 偽指令來加載大數。

  • FAQ2:加載一個寄存器的值,使用移位:MOV x1, (1<<0) | (1<<2)|(1<<20)|(1<<40)|(1<<55)

    錯誤,同樣是MOV立即數16-bit,使用LDR x1, = (1<<0)|......|(1<<55).

  • FAQ3: 字符串的LDR指令

    string1:
    	.string "Booting at EL"
    LDR x0, string1      // 加載string1字符串的ascii碼值到寄存器,最高限制在X寄存器大小也就是 64-bit,如果是W寄存器就是32-bit    
    LDR x1, =string1     // 加載string1字符串的地址到x1
    
  • FAQ3: 定義數據LDR指令

    my_data:
    	.word 0x40
    LDR x0, my_data     //加載0x40到X0,等同於MOV x0,0x40 前提是不超過16bits
    LDR x1, =my_data    //加載存儲my_data的地址到x1
    
  • 一種易錯的死機狀態: 樹莓派4b上面的寄存器都是32bit的,下面代碼配置26到U_IBRD_REG寄存器,有什么問題?

    LDR x1, =U_IBRD_REG
    MOV x2, #26
    STR x2, [x1]
    
    //正確寫法:
    LDR w1, =U_IBRD_REG
    MOV w2, #26
    STR w2, [w1]
    

    錯誤點在於樹莓派4b寄存器訪問都是32bit的,現在使用X寄存器,為64位的寄存器,應該使用W寄存器,32位寄存器訪問。

GDB-Tips

  • 啟動GDB和QEMU鏈接

    • > gdb-multiarch --tui benos.elf

    • gdb> c

    • gdb> target remote localhost:1234

    • gdb> b ldr_test // 設定斷點

    • gdb> c

    • gdb> next //下一步

    • gdb> info register // 查看所有寄存器

    • gdb> info x1 x2 x3 // 查看x1/x2/x3寄存器

    • gdb> x 0x80000 // 讀取內存0x80000值 32位

    • gdb> x/xg 0x80000 // 讀取內存0x80000值64位


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM