在 64 位程式中,用于獲取堆疊保護器的 selector:offset 是 fs:0x28,其中 fs=0。這不會造成任何問題,因為在 64 位中我們有 MSR fs_base(設定為指向 TLS)并且 GDT 被完全忽略。
但是對于 32 位程式,堆疊保護器是從 gs:0x14 讀取的。在 64 位系統上運行我們有 gs=0x63,在 32 位系統上 gs=0x33。這里沒有 MSR,因為它們是在 x86_64 中引入的,所以 GDT 在這里起著重要的作用。
剖析這兩種情況下的值,我們得到 RPL=3(這是預期的),描述符表選擇器指示 GDT(Linux 中未使用 LDT)并且選擇器指向具有索引 12 代表 64 位和索引 6 代表 32 的條目位。
使用內核模塊,我能夠檢查 64 位 linux 中的此條目是否為 NULL!所以我不明白TLS的地址是如何決議的。
內核模塊的相關部分如下:
void gdtread()
{
struct desc_ptr gdtr;
seg_descriptor* gdt_entry = NULL;
uint16_t tr;
int i;
asm("str %0" : "=m"(tr));
native_store_gdt(&gdtr); // equiv. to asm("sgdt %0" : "=m"(gdtr));
printk("GDT address: 0x%px, GDT size: %d bytes = %i entries\n",
(void*)gdtr.address, gdtr.size 1, (gdtr.size 1) / 8);
gdt_entry = (seg_descriptor*)gdtr.address;
for(i = 0; i < (gdtr.size 1) / 8; i )
{
if(tr >> 3 == i)
printk("Entry #%i:\t<--- TSS (RPL = %i)", i, tr & 3);
else
printk("Entry #%i:", i);
if(!((uint64_t*)gdt_entry)[i])
{
printk("\tNULL");
continue;
}
if(gdt_entry[i].s)
user_segment_desc(&gdt_entry[i]);
else
system_segment_desc((sys_seg_descriptor*)&gdt_entry[i ]);
}
}
在 64 位系統上輸出以下內容:
[ 3817.191065] GDT address: 0xfffffe0000001000, GDT size: 128 bytes = 16 entries
[ 3817.191073] Entry #0:
[ 3817.191075] NULL
[ 3817.191078] Entry #1:
[ 3817.191081] Raw: 0x00cf9b000000ffff
[ 3817.191084] Base: 0x00000000
[ 3817.191088] Limit: 0xfffff
[ 3817.191091] Flags: 0xc09b
[ 3817.191096] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191100] S = 0 (user)
[ 3817.191103] DPL = 0
[ 3817.191105] P = 1 (present)
[ 3817.191109] AVL = 0
[ 3817.191112] L = 0 (legacy mode)
[ 3817.191115] D/B = 1
[ 3817.191118] G = 1 (KiB)
[ 3817.191121] Entry #2:
[ 3817.191124] Raw: 0x00af9b000000ffff
[ 3817.191127] Base: 0x00000000
[ 3817.191130] Limit: 0xfffff
[ 3817.191133] Flags: 0xa09b
[ 3817.191137] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191141] S = 0 (user)
[ 3817.191144] DPL = 0
[ 3817.191146] P = 1 (present)
[ 3817.191149] AVL = 0
[ 3817.191152] L = 1 (long mode)
[ 3817.191155] D/B = 0
[ 3817.191157] G = 1 (KiB)
[ 3817.191160] Entry #3:
[ 3817.191163] Raw: 0x00cf93000000ffff
[ 3817.191166] Base: 0x00000000
[ 3817.191169] Limit: 0xfffff
[ 3817.191171] Flags: 0xc093
[ 3817.191175] Type = 0x3 (Data, expand down, writable, accessed)
[ 3817.191178] S = 0 (user)
[ 3817.191181] DPL = 0
[ 3817.191183] P = 1 (present)
[ 3817.191186] AVL = 0
[ 3817.191189] L = 0
[ 3817.191191] D/B = 1
[ 3817.191194] G = 1 (KiB)
[ 3817.191197] Entry #4:
[ 3817.191199] Raw: 0x00cffb000000ffff
[ 3817.191202] Base: 0x00000000
[ 3817.191205] Limit: 0xfffff
[ 3817.191207] Flags: 0xc0fb
[ 3817.191211] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191214] S = 0 (user)
[ 3817.191217] DPL = 3
[ 3817.191219] P = 1 (present)
[ 3817.191222] AVL = 0
[ 3817.191224] L = 0 (legacy mode)
[ 3817.191227] D/B = 1
[ 3817.191230] G = 1 (KiB)
[ 3817.191233] Entry #5:
[ 3817.191235] Raw: 0x00cff3000000ffff
[ 3817.191238] Base: 0x00000000
[ 3817.191241] Limit: 0xfffff
[ 3817.191243] Flags: 0xc0f3
[ 3817.191246] Type = 0x3 (Data, expand down, writable, accessed)
[ 3817.191250] S = 0 (user)
[ 3817.191252] DPL = 3
[ 3817.191255] P = 1 (present)
[ 3817.191258] AVL = 0
[ 3817.191260] L = 0
[ 3817.191262] D/B = 1
[ 3817.191265] G = 1 (KiB)
[ 3817.191268] Entry #6:
[ 3817.191270] Raw: 0x00affb000000ffff
[ 3817.191273] Base: 0x00000000
[ 3817.191276] Limit: 0xfffff
[ 3817.191278] Flags: 0xa0fb
[ 3817.191281] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191284] S = 0 (user)
[ 3817.191287] DPL = 3
[ 3817.191289] P = 1 (present)
[ 3817.191292] AVL = 0
[ 3817.191295] L = 1 (long mode)
[ 3817.191298] D/B = 0
[ 3817.191300] G = 1 (KiB)
[ 3817.191303] Entry #7:
[ 3817.191306] NULL
[ 3817.191308] Entry #8: <--- TSS (RPL = 0)
[ 3817.191312] Raw: 0x00000000fffffe0000008b0030004087
[ 3817.191316] Base: 0xfffffe0000003000
[ 3817.191321] Limit: 0x04087
[ 3817.191324] Flags: 0x008b
[ 3817.191327] Type = 0xb (Busy 64-bit TSS)
[ 3817.191331] S = 1 (system)
[ 3817.191333] DPL = 0
[ 3817.191336] P = 1 (present)
[ 3817.191339] AVL = 0
[ 3817.191341] L = 0
[ 3817.191344] D/B = 0
[ 3817.191347] G = 0 (B)
[ 3817.191349] Entry #10:
[ 3817.191352] NULL
[ 3817.191355] Entry #11:
[ 3817.191358] NULL
[ 3817.191360] Entry #12:
[ 3817.191362] NULL
[ 3817.191365] Entry #13:
[ 3817.191367] NULL
[ 3817.191369] Entry #14:
[ 3817.191372] NULL
[ 3817.191374] Entry #15:
[ 3817.191377] Raw: 0x0040f50000000000
[ 3817.191380] Base: 0x00000000
[ 3817.191382] Limit: 0x00000
[ 3817.191385] Flags: 0x40f5
[ 3817.191389] Type = 0x5 (Data, expand up, read only, accessed)
[ 3817.191392] S = 0 (user)
[ 3817.191395] DPL = 3
[ 3817.191397] P = 1 (present)
[ 3817.191400] AVL = 0
[ 3817.191403] L = 0
[ 3817.191405] D/B = 1
[ 3817.191408] G = 0 (B)
我還沒有在 32 位系統上嘗試過這個模塊,但我正在路上。
那么,讓問題弄清楚:gs 段選擇器如何在 64 位 linux 內核上運行的 32 位程式中作業?
uj5u.com熱心網友回復:
在@PeterCordes 發表評論后,我在“AMD64 架構程式員手冊,第 2 卷”中進行了搜索,其中第 27 頁說:
兼容模式在計算有效地址時忽略 FS 和 GS 段描述符中基地址的高 32 位。
這意味著管理 32 位行程的 64 位內核像使用 64 位行程一樣使用MSR_*S_BASE暫存器。內核可以在 64 位長模式下正常設定段基址,因此這些 MSR 是否在長模式的32 位兼容子模式或純 32 位保護模式下可用并不重要(傳統模式,32 位內核)。一個64位Linux內核只使用為環3(用戶空間)兼容模式,其中wrmsr和rdmsr不可用,因為的特權。與往常一樣,基于段的設定在權限級別的更改中保持不變,例如使用sysret或回傳用戶空間iret。
另一件讓我認為這個暫存器不用于兼容模式行程的事情是 GDB。這是在除錯 32 位程式時嘗試列印此暫存器時發生的情況。:
(gdb) i r $gs_base
Invalid register `gs_base'
除錯 64 位程式它作業正常。
(gdb) i r $fs_base
fs_base 0x7ffff7d00c00 0x7ffff7d00c00
由于指令rdgsbase是 64 位指令(嘗試在 32 位程式中執行該操作碼會產生 SIGILL 信號),因此在 32 位程式中獲取該暫存器的值有點棘手。
我想到的第一個解決方案是從內核模塊中讀取它:
unsigned long gs_base = 0xdeadbeefc0ffee13;
asm("swapgs;"
"rdgsbase %0;" // maybe unsafe if an interrupt happens here
// be careful if using this for anything more than toy experiments.
"swapgs;"
: "=r"(gs_base));
printk("gs_base: 0x6lx", gs_base);
所以我為 中的設備創建了一個驅動程式/dev,所以當一個程式open()s 該檔案時,上面的代碼被執行。編譯并運行打開此檔案的 32 位程式后,我得到了這個
[10793.682033] gs_base: 0x00000000f7f9f040
并且使用 gdb 檢查0xf7f9f040 0x14我看到了金絲雀,這意味著它是 TLS。
(gdb) x/wx 0xf7f9f040 0x14
0xf7f9f054: 0x21f03c00
(gdb) x/wx $ebp-0xc
0xbffff60c: 0x21f03c00
我能想到的另一種方法是執行一個far call從32位變為64位,執行rdgsbase然后回傳到64位。可能這是一個更好的解決方案,因為它不需要內核模塊。(只要您可以假設您在支持 FSGSBASE 擴展的 CPU 上運行,并且有足夠新的內核來啟用它。)
像這樣的東西:
#include <stdio.h>
__attribute__((naked)) // or define the function in an asm statement at global scope
extern void rdgsbase()
{
asm("rdgsbase
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/384835.html
上一篇:nasm不能在函式中呼叫函式
