包括組裝。這個周末我試圖讓我自己的小庫在沒有任何 C 庫的情況下運行,執行緒本地的東西給我帶來了問題。下面你可以看到我創建了一個名為的結構體Try1(因為這是我的第一次嘗試!)如果我設定執行緒區域變數并使用它,代碼似乎執行得很好。如果我使用全域變數在 Try1 上呼叫 const 方法,它似乎運行良好。現在如果我兩者都做,那就不好了。盡管我能夠訪問成員并使用全域變數運行該函式,但它仍然存在段錯誤。該代碼將列印 Hello 和 Hello2 但不列印 Hello3
我懷疑問題出在變數的地址上。我嘗試使用 if 陳述句列印第一個 hello。if ((s64)&t1 > (s64)buf 1024*16)這是真的,所以這意味著指標不在我認為的位置。它也不是 gdb 建議的 -8(這是一個帶符號的比較,我嘗試了 0 而不是 buf)
c 代碼下的匯編。第一行是寫的第一個呼叫
//test.cpp
//clang or g -std=c 20 -g -fno-rtti -fno-exceptions -fno-stack-protector -fno-asynchronous-unwind-tables -static -nostdlib test.cpp -march=native && ./a.out
#include <immintrin.h>
typedef unsigned long long int u64;
ssize_t my_write(int fd, const void *buf, size_t size) {
register int64_t rax __asm__ ("rax") = 1;
register int rdi __asm__ ("rdi") = fd;
register const void *rsi __asm__ ("rsi") = buf;
register size_t rdx __asm__ ("rdx") = size;
__asm__ __volatile__ (
"syscall"
: " r" (rax)
: "r" (rdi), "r" (rsi), "r" (rdx)
: "cc", "rcx", "r11", "memory"
);
return rax;
}
void my_exit(int exit_status) {
register int64_t rax __asm__ ("rax") = 60;
register int rdi __asm__ ("rdi") = exit_status;
__asm__ __volatile__ (
"syscall"
: " r" (rax)
: "r" (rdi)
: "cc", "rcx", "r11", "memory"
);
}
struct Try1
{
u64 val;
constexpr Try1() { val=0; }
u64 Get() const { return val; }
};
static char buf[1024*8]; //originally mmap but lets reduce code
static __thread u64 sanity_check;
static __thread Try1 t1;
static Try1 global;
extern "C"
int _start()
{
auto tls_size = 4096*2;
auto originalFS = _readfsbase_u64();
_writefsbase_u64((u64)(buf 4096));
global.val = 1;
global.Get(); //Executes fine
sanity_check=6;
t1.val = 7;
my_write(1, "Hello\n", sanity_check);
my_write(1, "Hello2\n", t1.val); //Still fine
my_write(1, "Hello3\n", t1.Get()); //crash! :/
my_exit(0);
return 0;
}
匯編:
4010b4: e8 47 ff ff ff call 401000 <_Z8my_writeiPKvm>
4010b9: 64 48 8b 04 25 f8 ff mov rax,QWORD PTR fs:0xfffffffffffffff8
4010c0: ff ff
4010c2: 48 89 c2 mov rdx,rax
4010c5: 48 8d 05 3b 0f 00 00 lea rax,[rip 0xf3b] # 402007 <_ZNK4Try13GetEv 0xeef>
4010cc: 48 89 c6 mov rsi,rax
4010cf: bf 01 00 00 00 mov edi,0x1
4010d4: e8 27 ff ff ff call 401000 <_Z8my_writeiPKvm>
4010d9: 64 48 8b 04 25 00 00 mov rax,QWORD PTR fs:0x0
4010e0: 00 00
4010e2: 48 05 f8 ff ff ff add rax,0xfffffffffffffff8
4010e8: 48 89 c7 mov rdi,rax
4010eb: e8 28 00 00 00 call 401118 <_ZNK4Try13GetEv>
4010f0: 48 89 c2 mov rdx,rax
4010f3: 48 8d 05 15 0f 00 00 lea rax,[rip 0xf15] # 40200f <_ZNK4Try13GetEv 0xef7>
4010fa: 48 89 c6 mov rsi,rax
4010fd: bf 01 00 00 00 mov edi,0x1
401102: e8 f9 fe ff ff call 401000 <_Z8my_writeiPKvm>
401107: bf 00 00 00 00 mov edi,0x0
40110c: e8 12 ff ff ff call 401023 <_Z7my_exiti>
401111: b8 00 00 00 00 mov eax,0x0
401116: c9 leave
401117: c3 ret
uj5u.com熱心網友回復:
ABI 要求fs:0包含一個指標,該指標具有執行緒本地存盤塊的絕對地址,即 的值fsbase。編譯器需要訪問這個地址來計算運算式,比如&t1,這里需要它來計算this要傳遞給的指標Try1::Get()。
在 x86-64 上恢復這個地址很棘手,因為 TLS 基地址不在方便的通用暫存器中,而是在隱藏的fsbase. rdfsbase每次我們需要它時都執行它是不可行的(昂貴的指令可能不可用),更糟糕的是呼叫arch_prctl,所以最簡單的解決方案是確保它在記憶體中的已知地址可用。請參閱此過去的答案以及“用于執行緒本地存盤的 ELF 處理”的第 3.4.2 和 3.4.6 節,該部分通過參考合并到 x86-64 ABI 中。
在您的反匯編中0x4010d9,您可以看到編譯器嘗試從地址加載fs:0x0到rax,然后添加 -8(t1TLS 塊中的偏移量)并將結果rdi作為隱藏this引數移動到Try1::Get()。顯然,因為你有零fs:0,結果指標是無效的,你會在Try1::Get() 讀取時崩潰val,實際上是this->val.
我會寫類似的東西
void *fsbase = buf 4096;
_writefsbase_u64((u64)fsbase);
*(void **)fsbase = fsbase;
(或者memcpy(fsbase, &fsbase, sizeof(void *))可能更符合嚴格的別名。)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/323187.html
