網站首頁 編程語言 正文
1.概述
在閱讀Linux內核代碼時,經常能碰到匯編代碼,網上能查的資料千篇一律,大多都描述的很模糊。俗話說,實踐是檢驗真理的唯一標準,我們就參考官方文檔,自己寫匯編代碼并反匯編,探尋其中的奧妙。
2.adrp
在Linux內核啟動代碼primary_entry
中,使用adrp
指令獲取Linux內核在內存中的起始頁地址,頁大小為4KB,由于內核啟動的時候MMU還未打開,此時獲取的Linux內核在內存中的起始頁地址為物理地址。adrp
通過當前PC地址的偏移地址計算目標地址,和實際的物理無關,因此屬于位置無關碼。對于具體的計算過程,下面慢慢分析。
[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 // 內核的物理地址 [arch/arm64/include/asm/memory.h] // 內核的起始地址和結束地址在vmlinux.lds鏈接腳本中定義 #define KERNEL_START _text // 內核代碼段的起始地址,也即內核的起始地址 #define KERNEL_END _end // 內核的結束地址
2.1.定義
adrp
指令根據PC的偏移地址計算目標頁地址。首先adrp
將一個21位有符號立即數左移12位,得到一個33位的有符號數(最高位為符號位),接著將PC地址的低12位清零,這樣就得到了當前PC地址所在頁的地址,然后將當前PC地址所在頁的地址加上33位的有符號數,就得到了目標頁地址,最后將目標頁地址寫入通用寄存器。此處頁大小為4KB,只是為了得到更大的地址范圍,和虛擬內存的頁大小沒有關系。通過adrp
指令,可以獲取當前PC地址±4GB范圍內的地址。通常的使用場景是先通過adrp
獲取一個基地址,然后再通過基地址的偏移地址獲取具體變量的地址。
下面是adrp
指令的編碼格式。立即數占用21位,在運行的時候,會將21位立即數擴展為33位有符號數。最高位為1,表示這是一個aarch64指令。
2.2.測試
Linux內核啟動代碼不好測試,需要寫一個簡單的測試代碼。下面是本次adrp
的測試代碼,使用adrp
指令獲取g_val1
和g_val2
數組所在頁的基地址,同時會打印數組的地址和調用函數的地址,由于是應用層的程序,這些地址都是虛擬地址,但是計算過程都是一樣的。
#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); }
上面程序運行的輸出結果如下,g_val1
和g_val2
的地址分別為0x5583e25028
和0x5583e26028
,g_val1
的頁基地址為0x5583e25000
,g_val2
頁的基地址為0x5583e26000
,adrp_test
函數的地址為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
頁基地址的計算過程,包括編譯時和運行時,g_val2
頁基地址的計算過程類似,這里不再贅述。
- 將
g_val1
址低低12位清零,得到0x1100,將當前adrp
指令所在地址的低12清零,得到0x0(編譯時完成) - 0x1100減去0x0得到偏移地址0x11000,偏移地址右移12位得到偏移頁數量0x11,將立即數0x11保存到指令編碼中(編譯時完成)
- 取出立即數0x11,左移12位轉換成偏移的字節數,即0x11000(運行時完成)
- 將PC地址的低12位清零得到0x5583e14000(運行時完成)
- 將0x5583e14000加上0x1100得到
g_val1
運行時頁基地址0x5583e25000(運行時完成)
000000000000079c <adrp_test>: // 運行時的地址為0x5583e1479c ...... 7b0: b0000080 adrp x0, 11000 <__data_start> // 獲取g_val1頁基地址 ...... 7e0: d0000080 adrp x0, 12000 <g_val1+0xfd8> // 獲取g_val2頁基地址 Disassembly of section .data: // 數據段定義 0000000000011000 <__data_start>: // 運行時的地址為0x5583e25000 ... ...... Disassembly of section .bss: // bss段定義 0000000000011028 <g_val1>: // 運行時地址為0x5583e25028 ... 0000000000012028 <g_val2>: // 運行時地址為0x5583e26028 ...
從上面可以看出,編譯時和運行時的地址不一樣,但通過adrp
指令都能正確獲取g_val1
頁基地址和g_val2
頁基地址。說明adrp
獲取的地址是位置無關的,不管運行時的地址怎么變,都可以正確獲取對應變量頁基地址。當然我們也可以使用專業的反匯編工具,直接將機器碼轉換為匯編代碼。上面兩條adrp
指令轉換的匯編代碼如下,和上面一樣,這里的偏移地址都已經做了左移12位的處理。
3.adr
3.1.定義
adr
指令根據PC的偏移地址計算目標地址。偏移地址是一個21位的有符號數,加上當前的PC地址得到目標地址。adr
可以獲取當前PC地址±1MB范圍內的地址。下面是adr
指令的編碼格式。立即數占用21位。
3.2.測試
下面是測試代碼,使用adr
指令獲取變量g_val3
和g_val4
的地址,并與通過&
獲取的地址進行對比。
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); }
下面是測試結果,使用&
獲取的地址和通過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。執行第一條adr
指令的PC地址為0x5583e14824,則0x5583e14824+0x107f4=0x5583e25018為g_val3的地址。g_val4
的計算過程類似,不再贅述。
0000000000000810 <adr_test>: // 運行地址為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>: // 運行地址為0x5583e25018 ... 0000000000011020 <g_val4>: // 運行地址為0x5583e25020 ...
參考資料
- linux-5.10.81原代碼
- Arm ? Architecture Reference Manual Armv8, for A-profile architecture
原文鏈接:https://blog.csdn.net/u011037593/article/details/121877496
相關推薦
- 2022-08-13 從源碼理解SpringBootServletInitializer的作用
- 2022-10-03 numpy數組疊加的實現示例_python
- 2023-04-04 C/C++關于實現CAN信號的獲取方法_C 語言
- 2022-06-29 Oracle執行Update語句的幾種方式_oracle
- 2022-04-25 T-SQL查詢為何慎用IN和NOT?IN詳解_MsSql
- 2022-05-17 Spring Cloud Loadbalancer 修改默認緩存為Caffeine,修改微服務啟動關
- 2022-10-18 一文詳解Python中的Map,Filter和Reduce函數_python
- 2022-07-21 shardingjdbc+mybatisP+Seata 報錯 throw new ShouldNev
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支