淺析ARMv8匯編指令adrp和adr
1.概述
在閱讀Linux內(nèi)核代碼時,經(jīng)常能碰到匯編代碼,網(wǎng)上能查的資料千篇一律,大多都描述的很模糊。俗話說,實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),我們就參考官方文檔,自己寫匯編代碼并反匯編,探尋其中的奧妙。
2.adrp
在Linux內(nèi)核啟動代碼primary_entry中,使用adrp指令獲取Linux內(nèi)核在內(nèi)存中的起始頁地址,頁大小為4KB,由于內(nèi)核啟動的時候MMU還未打開,此時獲取的Linux內(nèi)核在內(nèi)存中的起始頁地址為物理地址。adrp通過當(dāng)前PC地址的偏移地址計算目標(biāo)地址,和實(shí)際的物理無關(guān),因此屬于位置無關(guān)碼。對于具體的計算過程,下面慢慢分析。
[arch/arm64/kernel/head.S]
SYM_CODE_START(primary_entry)
......
adrp x23, __PHYS_OFFSET
and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
......
SYM_CODE_END(primary_entry)
[arch/arm64/kernel/head.S]
#define __PHYS_OFFSET KERNEL_START // 內(nèi)核的物理地址
[arch/arm64/include/asm/memory.h]
// 內(nèi)核的起始地址和結(jié)束地址在vmlinux.lds鏈接腳本中定義
#define KERNEL_START _text // 內(nèi)核代碼段的起始地址,也即內(nèi)核的起始地址
#define KERNEL_END _end // 內(nèi)核的結(jié)束地址
2.1.定義
adrp指令根據(jù)PC的偏移地址計算目標(biāo)頁地址。首先adrp將一個21位有符號立即數(shù)左移12位,得到一個33位的有符號數(shù)(最高位為符號位),接著將PC地址的低12位清零,這樣就得到了當(dāng)前PC地址所在頁的地址,然后將當(dāng)前PC地址所在頁的地址加上33位的有符號數(shù),就得到了目標(biāo)頁地址,最后將目標(biāo)頁地址寫入通用寄存器。此處頁大小為4KB,只是為了得到更大的地址范圍,和虛擬內(nèi)存的頁大小沒有關(guān)系。通過adrp指令,可以獲取當(dāng)前PC地址±4GB范圍內(nèi)的地址。通常的使用場景是先通過adrp獲取一個基地址,然后再通過基地址的偏移地址獲取具體變量的地址。
下面是adrp指令的編碼格式。立即數(shù)占用21位,在運(yùn)行的時候,會將21位立即數(shù)擴(kuò)展為33位有符號數(shù)。最高位為1,表示這是一個aarch64指令。

2.2.測試
Linux內(nèi)核啟動代碼不好測試,需要寫一個簡單的測試代碼。下面是本次adrp的測試代碼,使用adrp指令獲取g_val1和g_val2數(shù)組所在頁的基地址,同時會打印數(shù)組的地址和調(diào)用函數(shù)的地址,由于是應(yīng)用層的程序,這些地址都是虛擬地址,但是計算過程都是一樣的。
#define PAGE_4KB (4096)
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
uint64_t g_val1[PAGE_4KB / sizeof(uint64_t)];
uint64_t g_val2[PAGE_4KB / sizeof(uint64_t)];
#define ADRP(label) ({ \
uint64_t __adrp_val__ = 0; \
asm volatile("adrp %0," __stringify(label) :"=r"(__adrp_val__)); \
__adrp_val__; \
})
static void adrp_test()
{
printf("g_val1 addr 0x%lx, adrp_val1 0x%lx, adrp_test addr 0x%lx\n",
(uint64_t)g_val1, ADRP(g_val1), (uint64_t)adrp_test);
printf("g_val2 addr 0x%lx, adrp_val2 0x%lx, adrp_test addr 0x%lx\n",
(uint64_t)g_val2, ADRP(g_val2), (uint64_t)adrp_test);
}
上面程序運(yùn)行的輸出結(jié)果如下,g_val1和g_val2的地址分別為0x5583e25028和0x5583e26028,g_val1的頁基地址為0x5583e25000,g_val2頁的基地址為0x5583e26000,adrp_test函數(shù)的地址為0x5583e1479c。
g_val1 addr 0x5583e25028, adrp_val1 0x5583e25000, adrp_test addr 0x5583e1479c g_val2 addr 0x5583e26028, adrp_val2 0x5583e26000, adrp_test addr 0x5583e1479c
反匯編代碼如下所示。下面分析一下g_val1頁基地址的計算過程,包括編譯時和運(yùn)行時,g_val2頁基地址的計算過程類似,這里不再贅述。
- 將
g_val1址低低12位清零,得到0x1100,將當(dāng)前adrp指令所在地址的低12清零,得到0x0(編譯時完成) - 0x1100減去0x0得到偏移地址0x11000,偏移地址右移12位得到偏移頁數(shù)量0x11,將立即數(shù)0x11保存到指令編碼中(編譯時完成)
- 取出立即數(shù)0x11,左移12位轉(zhuǎn)換成偏移的字節(jié)數(shù),即0x11000(運(yùn)行時完成)
- 將PC地址的低12位清零得到0x5583e14000(運(yùn)行時完成)
- 將0x5583e14000加上0x1100得到
g_val1運(yùn)行時頁基地址0x5583e25000(運(yùn)行時完成)
000000000000079c <adrp_test>: // 運(yùn)行時的地址為0x5583e1479c ...... 7b0: b0000080 adrp x0, 11000 <__data_start> // 獲取g_val1頁基地址 ...... 7e0: d0000080 adrp x0, 12000 <g_val1+0xfd8> // 獲取g_val2頁基地址 Disassembly of section .data: // 數(shù)據(jù)段定義 0000000000011000 <__data_start>: // 運(yùn)行時的地址為0x5583e25000 ... ...... Disassembly of section .bss: // bss段定義 0000000000011028 <g_val1>: // 運(yùn)行時地址為0x5583e25028 ... 0000000000012028 <g_val2>: // 運(yùn)行時地址為0x5583e26028 ...
從上面可以看出,編譯時和運(yùn)行時的地址不一樣,但通過adrp指令都能正確獲取g_val1頁基地址和g_val2頁基地址。說明adrp獲取的地址是位置無關(guān)的,不管運(yùn)行時的地址怎么變,都可以正確獲取對應(yīng)變量頁基地址。當(dāng)然我們也可以使用專業(yè)的反匯編工具,直接將機(jī)器碼轉(zhuǎn)換為匯編代碼。上面兩條adrp指令轉(zhuǎn)換的匯編代碼如下,和上面一樣,這里的偏移地址都已經(jīng)做了左移12位的處理。

3.adr
3.1.定義
adr指令根據(jù)PC的偏移地址計算目標(biāo)地址。偏移地址是一個21位的有符號數(shù),加上當(dāng)前的PC地址得到目標(biāo)地址。adr可以獲取當(dāng)前PC地址±1MB范圍內(nèi)的地址。下面是adr指令的編碼格式。立即數(shù)占用21位。

3.2.測試
下面是測試代碼,使用adr指令獲取變量g_val3和g_val4的地址,并與通過&獲取的地址進(jìn)行對比。
uint64_t g_val3 = 0;
uint64_t g_val4 = 0;
#define ADR(label) ({ \
uint64_t __adr_val__ = 0; \
asm volatile("adr %0," __stringify(label) :"=r"(__adr_val__)); \
__adr_val__; \
})
static void adr_test()
{
printf("g_val3 addr 0x%lx, adr_val1 0x%lx, adr_test addr 0x%lx\n",
(uint64_t)&g_val3, ADR(g_val3), (uint64_t)adr_test);
printf("g_val4 addr 0x%lx, adr_val2 0x%lx, adr_test addr 0x%lx\n",
(uint64_t)&g_val4, ADR(g_val4), (uint64_t)adr_test);
}
下面是測試結(jié)果,使用&獲取的地址和通過adr獲取的地址相同。
g_val3 addr 0x5583e25018, adr_val1 0x5583e25018, adr_test addr 0x5583e14810 g_val4 addr 0x5583e25020, adr_val2 0x5583e25020, adr_test addr 0x5583e14810
下面是反匯編的代碼??梢钥闯?,adr匯編代碼中的偏移地址被objdump使用符號地址代替了,沒有使用真正的偏移地址。g_val3真正的偏移地址為0x107f4,g_val4真正的偏移地址為0x107cc。執(zhí)行第一條adr指令的PC地址為0x5583e14824,則0x5583e14824+0x107f4=0x5583e25018為g_val3的地址。g_val4的計算過程類似,不再贅述。
0000000000000810 <adr_test>: // 運(yùn)行地址為0x5583e14810
......
824: 10083fa0 adr x0, 11018 <g_val3> // 偏移地址為0x11018-0x824=0x107f4
......
854: 10083e60 adr x0, 11020 <g_val4> // 偏移地址為0x11020-0x854=0x107cc
......
isassembly of section .data:
0000000000011000 <__data_start>:
...
......
Disassembly of section .bss:
......
0000000000011018 <g_val3>: // 運(yùn)行地址為0x5583e25018
...
0000000000011020 <g_val4>: // 運(yùn)行地址為0x5583e25020
...

參考資料
- linux-5.10.81原代碼
- Arm ? Architecture Reference Manual Armv8, for A-profile architecture
到此這篇關(guān)于ARMv8匯編指令-adrp和adr的文章就介紹到這了,更多相關(guān)ARMv8匯編指令內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解匯編語言RCL(帶進(jìn)位循環(huán)左移)和RCR(帶進(jìn)位循環(huán)右移)指令
這篇文章主要介紹了匯編語言RCL(帶進(jìn)位循環(huán)左移)和RCR(帶進(jìn)位循環(huán)右移)指令的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
匯編語言中的函數(shù)調(diào)用參數(shù)傳遞及全局與局部變量與“基址”
這篇文章主要介紹了匯編眼中的函數(shù)調(diào)用參數(shù)傳遞以及全局與局部變量與“基址”,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02
從匯編代碼開始全面解析synchronized還原最真實(shí)的偏向鎖
這篇文章主要為大家介紹了從模板解釋器匯編源碼開始分析還原最真實(shí)的偏向鎖實(shí)現(xiàn),解釋monitorenter字節(jié)碼命令的方法開始,從匯編代碼開始全面解析synchronized2022-02-02
匯編語言系列之匯編實(shí)現(xiàn)簡單數(shù)學(xué)運(yùn)算
這篇文章主要介紹了匯編語言系列之匯編實(shí)現(xiàn)簡單數(shù)學(xué)運(yùn)算的思路詳解,本文給大家列出了兩種算術(shù)運(yùn)算的代碼,設(shè)計思路給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-11-11
Windows10下利用DOSBOX和MASM32搭建匯編語言開發(fā)環(huán)境
這篇文章主要介紹了Windows10下利用DOSBOX和MASM32搭建匯編語言開發(fā)環(huán)境,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
UEFI開發(fā)實(shí)戰(zhàn)用戶交互界面基礎(chǔ)說明
這篇文章主要為大家介紹了UEFI開發(fā)實(shí)戰(zhàn)用戶交互界面的基礎(chǔ)說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

