加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c – 是否可以在跨内核线程迁移后强制重新加载thread_local变量

发布时间:2020-12-16 06:53:21 所属栏目:百科 来源:网络整理
导读:我在内核和线程之上实现用户线程并观察到,当用户线程在内核线程之间迁移时,即使变量也被标记为volatile,也会从先前的内核位置读取thread_local变量. 由于编译器仅将用户级swapcontext视为函数调用,下面的示例演示了简单函数调用的问题. #include stdio.hstru
我在内核和线程之上实现用户线程并观察到,当用户线程在内核线程之间迁移时,即使变量也被标记为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("%dn",bar);
    printf("%d %dn",foo->x,foo->y);

    bar = 8;
    printf("%dn",bar_Safe.get());
}

int main(){
    foo = new Foo;
    f();
    delete foo;
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读