如何从ml64.exe(MSVC 64位X64汇编程序)访问线程本地存储?

时间:2012-04-20 15:58:03

标签: c windows assembly thread-local thread-local-storage

以下C函数尝试使用线程本地存储变量以线程安全的方式阻止多核代码中的递归。但是,由于有些复杂的原因,我需要在X64汇编程序(Intel X86 / AMD 64位)中编写此函数,并使用VC2010中的ml64.exe进行汇编。如果我使用全局变量,我知道如何做到这一点,但我不知道如何使用具有__declspec(线程)的TLS变量正确执行此操作。

__declspec(thread) int tls_VAR = 0;
void norecurse(  )
{
    if(0==tls_VAR)
    {
        tls_VAR=1;
        DoWork();
        tls_VAR=0;
    }
}

注意:这就是VC2010为该功能提供的功能。但是,MASM(ml64.exe)不支持代码的gs:88OFFSET FLAT:部分。

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01 

include listing.inc

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

PUBLIC  norecurse
EXTRN   DoWork:PROC
EXTRN   tls_VAR:DWORD
EXTRN   _tls_index:DWORD
pdata   SEGMENT
$pdata$norecurse DD imagerel $LN4
    DD  imagerel $LN4+70
    DD  imagerel $unwind$norecurse
pdata   ENDS
xdata   SEGMENT
$unwind$norecurse DD 040a01H
    DD  06340aH
    DD  07006320aH
; Function compile flags: /Ogtpy
xdata   ENDS
_TEXT   SEGMENT
norecurse PROC
; File p:\hackytests\64bittest2010\64bittest\64bittest.cpp
; Line 19
$LN4:
    mov QWORD PTR [rsp+8], rbx
    push    rdi
    sub rsp, 32                 ; 00000020H
; Line 20
    mov ecx, DWORD PTR _tls_index
    mov rax, QWORD PTR gs:88
    mov edi, OFFSET FLAT:tls_VAR
    mov rbx, QWORD PTR [rax+rcx*8]
    cmp DWORD PTR [rbx+rdi], 0
    jne SHORT $LN1@norecurse
; Line 22
    mov DWORD PTR [rbx+rdi], 1
; Line 23
    call    DoWork
; Line 24
    mov DWORD PTR [rbx+rdi], 0
$LN1@norecurse:
; Line 26
    mov rbx, QWORD PTR [rsp+48]
    add rsp, 32                 ; 00000020H
    pop rdi
    ret 0
norecurse ENDP
_TEXT   ENDS
END

3 个答案:

答案 0 :(得分:2)

正如您的答案表明问题出现了,在Microsoft的C ++编译器生成的汇编列表中找到MASM等效于以下两行:

mov rax, QWORD PTR gs:88
mov edi, OFFSET FLAT:tls_VAR

第一行很简单。只需将gs:88替换为gs:[88]

第二行不太明显。 OFFSET FLAT:运营商是红鲱鱼。这意味着使用相对于“FLAT”段开头的偏移量。使用32位版本的MASM,FLAT段是包含整个4G地址空间的段。这是作为32位平面内存模型的一部分用于代码和数据段的段。 64位版本的MASM不支持内存模型,它基本上总是假定64位版本的平面内存模型,因此它不支持FLAT关键字。结果,普通OFFSET运算符结束意味着相同的事情。 (实际上对于32位汇编程序,plain OFFSET通常也意味着相同的事情,因为PECOFF只支持平面内存模型。)

但是在这里使用OFFSET将无效。那是因为它会使用内存中tls_VAR地址相对于地址0的偏移量。换句话说,它会在内存中使用tls_VAR的绝对地址。这里需要的是相对于TLS数据部分开头的偏移量。

所以编译器必须在这里做一些特别的事情。为了找到答案,我将重定位转储到编译示例C代码时生成的目标文件中:

> dumpbin /relocations t215a.obj
...  
RELOCATIONS #4
                                                Symbol    Symbol
 Offset    Type              Applied To         Index     Name
 --------  ----------------  -----------------  --------  ------
 00000008  REL32                      00000000        14  _tls_index
 00000016  SECREL                     00000000         8  tls_VAR
 0000002D  REL32                      00000000         C  DoWork
...

正如您所看到的,它会为SECREL的引用生成tls_VAR类型的重定位。这使得相对于生成的可执行文件中该符号出现的部分的基础的重定位。在这种情况下,这是.tls部分,因此此重定位生成相对于用于静态TLS的部分的开头的偏移量数据

所以现在问题变成了如何让MASM生成编译器发出的相同SECREL重定位。这也是一个简单的解决方案,只需将OFFSET FLAT:替换为SECTIONREL

因此,通过这些更改(以及一些优化),您的功能将变为:

    EXTERN  tls_VAR:DWORD
    EXTERN  _tls_index:DWORD
    EXTERN  DoWork:PROC

    PUBLIC  norecurse
_TEXT SEGMENT
norecurse PROC
    push rbx
    sub rsp, 32
    mov rax, gs:[88]
    mov ecx, _tls_index
    mov rbx, [rax + rcx * 8]
    cmp DWORD PTR [rbx + SECTIONREL tls_VAR], 0
    jne return
    mov DWORD PTR [rbx + SECTIONREL tls_VAR], 1
    call DoWork
    mov DWORD PTR [rbx + SECTIONREL tls_VAR], 0
return:
    add rsp, 32
    pop rbx
    ret
norecurse ENDP
_TEXT ENDS
    END

答案 1 :(得分:1)

我能够解决这个问题。我在assember中的实现效率低于C编译器生成的代码,但因为我无法弄清楚如何使用以下两种寻址模式:

  1. mov rax,QWORD PTR gs:88
  2. mov edi,OFFSET FLAT:tls_VAR
  3. 对于(1),我必须将88加载到rax并使用gs:[rax]来访问线程的TLS-base。

    对于(2),MASM中缺少OFFSET FLAT(ml64.exe)意味着我必须更加聪明。我通过从TLS-base中减去_tls_start来计算偏移量,该线程可以应用于汇编程序中的TLS变量以访问其线程本地值。

    PUBLIC  norecurse
    EXTRN   _tls_index:DWORD
    EXTRN   _tls_start:DWORD
    EXTRN   tls_VAR:DWORD
    EXTRN   DoWork:PROC
    
    _TEXT   SEGMENT
    
    norecurse           PROC
        ; non-volatile
        push            rbx
        sub             rsp,32
    
        ; The gs segment register refers to the base address of the TEB on x64.
        ; 88 (0×58) is the offset in the TEB for the ThreadLocalStoragePointer member on x64
        mov             rax,88
        mov             edx, DWORD PTR _tls_index
        mov             rax, gs:[rax]
        mov             r11, QWORD PTR [rax+rdx*8]
        lea             r10, _tls_start
        ; r11 will be the the offset-adjusted TLS-Base
        sub             r11, r10
    
        ; ebx will be the the thread local address of tls_VAR
        lea             rdx, tls_VAR
        lea             rbx,[r11+rdx]
    
        cmp             DWORD PTR [rbx], 0
        jne             @F
    
        mov             DWORD PTR [rbx], 1
    
        call            DoWork
    
        mov             DWORD PTR [rbx], 0
    @@:
    
        add             rsp,32
        pop             rbx
    
        ret
    norecurse       ENDP
    
    _TEXT   ENDS
    
        END
    

    我希望看到更有效的方法或指示如何实际使用我无法用MASM(ml64.exe)解决的两种寻址模式。

答案 2 :(得分:0)

查看TlsGetValue,TlsSetvalue和朋友。