Skip to content
Advertisement

Why does this nostdlib C++ code segfault when I call a function with a thread local variable? But not with a global var or when I access members?

Assembly included. This weekend I tried to get my own small library running without any C libs and the thread local stuff is giving me problems. Below you can see I created a struct called Try1 (because it’s my first attempt!) If I set the thread local variable and use it, the code seems to execute fine. If I call a const method on Try1 with a global variable it seems to run fine. Now if I do both, it’s not fine. It segfaults despite me being able to access members and running the function with a global variable. The code will print Hello and Hello2 but not Hello3

I suspect the problem is the address of the variable. I tried using an if statement to print the first hello. if ((s64)&t1 > (s64)buf+1024*16) It was true so it means the pointer isn’t where I thought it was. Also it isn’t -8 as gdb suggest (it’s a signed compare and I tried 0 instead of buf)

Assembly under the c++ code. First line is the first call to write

JavaScript

Asm:

JavaScript

Advertisement

Answer

The ABI requires that fs:0 contains a pointer with the absolute address of the thread-local storage block, i.e. the value of fsbase. The compiler needs access to this address to evaluate expressions like &t1, which here it needs in order to compute the this pointer to be passed to Try1::Get().

It’s tricky to recover this address on x86-64, since the TLS base address isn’t in a convenient general register, but in the hidden fsbase. It isn’t feasible to execute rdfsbase every time we need it (expensive instruction that may not be available) nor worse yet to call arch_prctl, so the easiest solution is to ensure that it’s available in memory at a known address. See this past answer and sections 3.4.2 and 3.4.6 of “ELF Handling for Thread-Local Storage”, which is incorporated by reference into the x86-64 ABI.

In your disassembly at 0x4010d9, you can see the compiler trying to load from address fs:0x0 into rax, then adding -8 (the offset of t1 in the TLS block) and moving the result into rdi as the hidden this argument to Try1::Get(). Obviously since you have zeros at fs:0 instead, the resulting pointer is invalid and you get a crash when Try1::Get() reads val, which is really this->val.

I would write something like

JavaScript

(Or memcpy(fsbase, &fsbase, sizeof(void *)) might be more compliant with strict aliasing.)

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement