【问题标题】:How Do I Access Thread Local Storage From ml64.exe (MSVC 64-bit X64 Assembler)?如何从 ml64.exe(MSVC 64 位 X64 汇编器)访问线程本地存储?
【发布时间】:2012-04-20 15:58:02
【问题描述】:

以下 C 函数尝试使用线程本地存储变量以线程安全的方式防止多核代码中的递归。但是,由于有些复杂的原因,我需要在 X64 汇编器(Intel X86 / AMD 64 位)中编写此函数,并使用 VC2010 中的 ml64.exe 进行汇编。如果我使用全局变量,我知道如何执行此操作,但我不确定如何使用具有 __declspec(thread) 的 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

【问题讨论】:

  • 您是否查看了 VC 生成的用于 tls 变量访问的代码并在您的程序集中重现该代码?
  • 你应该标记这个 MSVC 或 windows 什么的。其他所有问题的答案都会大不相同。
  • @Mat:是的,我做到了。我不知道如何在 ml64.exe 中使用 gs 的常量偏移量,但我可以将常量移动到寄存器并以这种方式访问​​它。几个额外的步骤,但它可能会起作用。

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


【解决方案1】:

正如您的回答表明问题归结为在 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 位平面内存模型的一部分。 MASM 的 64 位版本不支持内存模型,它本质上总是假定平面内存模型的 64 位版本,因此它不支持 FLAT 关键字。结果,普通的OFFSET 运算符结束了相同的含义。 (实际上对于 32 位汇编器,普通的 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

【讨论】:

  • 谢谢...我今天要试试这个。
  • 我遇到了这个问题。我不能做mov QWORD PTR [rbx + SECTIONREL tls_VAR],r12,而是必须做这三个指令:mov eax,SECTIONREL tls_VAR; add rax,rbx; mov QWORD PTR [rax],r12
  • 我认为这是因为SECTIONREL tls_VAR 是一个双字??无论如何,单指令版本给出“错误A2024:指令的操作数大小无效”
  • 哦,看起来它可以正常使用常量(即 1),但不能使用寄存器作为源操作数。
  • @Adisak 看起来像 MASM 中的错误。如果操作数的大小不是 32 位,则 SECTIONREL 无法正确处理寄存器偏移量。您可以将解决方法简单地分为两条说明:mov eax, SECTIONREL tls_VAR; mov [rbx + rax], r12
【解决方案2】:

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

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

对于 (1),我必须将 88 加载到 rax 并使用 gs:[rax] 来访问线程的 TLS 基础。

对于 (2),MASM (ml64.exe) 中缺少 OFFSET FLAT 意味着我必须更加聪明。我通过从线程的 TLS 基数中减去 _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),我会接受这个答案。
  • 这对我很有帮助,尤其是模仿 OFFSET FLAT 部分。很遗憾你不能多次投票给一个答案。
  • @arrowd 很高兴它有所帮助——我花了一段时间才弄清楚这一点。这并不是最直接的解决方案。如果您能弄清楚如何将 OFFSET FLAT 与 TLS 一起使用,请在此处发布答案:-)
【解决方案3】:

查看TlsGetValue、TlsSetvalue 和朋友。

【讨论】:

  • 这对 __declspec(thread) 根本不起作用。这是一种完全不同的动态 TLS 机制,需要 TlsAlloc 并且不适用于静态初始化的 __declspec(thread) TLS 变量。
  • 我的意思是使用 TlsAlloc/TlsGet/Set 而不是使用 __declspec(thread)。您是否尝试从其他地方访问 __declspec 声明的变量?
  • 我需要使用 __declspec(thread) 因为我需要静态初始化值。
  • 变量在 C 代码中声明为 __declspec(thread),我需要从汇编程序中访问它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多