我在内核和线程之上实现用户线程并观察到,当用户线程在内核线程之间迁移时,即使变量也被标记为volatile,也会从先前的内核位置读取thread_local变量.
由于编译器仅将用户级swapcontext视为函数调用,下面的示例演示了简单函数调用的问题.
#include <stdio.h> struct Foo { int x; int y; }; __thread Foo* volatile foo; void bar() { asm("nop"); } void f() { foo->x = 5; bar(); asm volatile("":::"memory"); // We desire a second computation of the address of foo here as an offset // from the FS register. foo->y = 7; } int main(){ foo = new Foo; f(); delete foo; }
接下来,我们运行以下命令进行编译和反汇编.请注意,-fPIC标志似乎是重现此问题所必需的,并且对于我的用例也是必需的,因为我正在构建库.假设上面的代码在一个名为TL.cc的文件中
g++ -std=c++11 -O3 -fPIC -Wall -g TL.cc -o TL objdump -d TL
这是函数f()的汇编转储.
400760: 53 push %rbx # Notice this computation happens only once. 400761: 64 48 8b 04 25 00 00 mov %fs:0x0,%rax 400768: 00 00 40076a: 48 8d 80 f8 ff ff ff lea -0x8(%rax),%rax 400771: 48 89 c3 mov %rax,%rbx 400774: 48 8b 00 mov (%rax),%rax 400777: c7 00 05 00 00 00 movl $0x5,(%rax) 40077d: e8 ce ff ff ff callq 400750 <_Z3barv> # Observe that the value of rbx came from before the function call,# so if the function bar() actually returned on a different kernel # thread,we would be referencing the original kernel thread's # version of foo,instead of the new kernel thread's version. 400782: 48 8b 03 mov (%rbx),%rax 400785: c7 40 04 07 00 00 00 movl $0x7,0x4(%rax) 40078c: 5b pop %rbx 40078d: c3 retq 40078e: 66 90 xchg %ax,%ax
我们观察到寄存器rax正在从内存重新加载,但是在调用bar()之前确定了内存位置.
有没有办法强制重新加载变量的地址作为fs寄存器当前值的偏移量?
如果存在这些,我可以使用gcc特定的黑客攻击.
这是输出g –version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609 copyright (C) 2015 Free Software Foundation,Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or fitness FOR A PARTIculaR PURPOSE.
解决方法
我编写了以下hack,它强制在我测试的所有情况下重新加载TLS,但是会欣赏有关它可能被破坏的所有方式的反馈.
#define SafeTLS(TypeName,name) \ struct name##_SafeClass { \ name##_SafeClass& \ __attribute__ ((noinline)) \ operator=(const TypeName& other) { \ asm (""); \ name = const_cast<TypeName&>(other); \ return *this; \ } \ TypeName& \ __attribute__ ((noinline)) \ operator->() { \ asm (""); \ return get(); \ } \ operator TypeName() { return get(); } \ TypeName& \ __attribute__ ((noinline)) \ get() { \ asm (""); \ return name; \ } \ \ TypeName* \ operator&() { \ asm (""); \ return &name; \ } \ } name##_Safe
这是一个使用它的更复杂的测试用例.
#include <stdio.h> #include "TLS.h" struct Foo { int x; int y; }; __thread Foo* volatile foo; __thread int bar; SafeTLS(Foo* volatile,foo); SafeTLS(int,bar); void f2() { asm("nop"); } void f() { foo_Safe->x = 5; f2(); asm volatile("":::"memory"); // We desire a second computation of the address of foo here as an offset // from the FS register. (*foo_Safe).y = 7; bar = 7; printf("%d\n",bar); printf("%d %d\n",foo->x,foo->y); bar = 8; printf("%d\n",bar_Safe.get()); } int main(){ foo = new Foo; f(); delete foo; }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。