gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 11:36:28 24 4
gpt4 key购买 nike

以下 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

最佳答案

正如您的回答所表明的那样,问题归结为在 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 位汇编器,普通的 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
...

如您所见,它为 tls_VAR 的引用生成了 SECREL 类型的重定位。这使得重定位相对于该符号出现在生成的可执行文件中的部分的基址。在本例中是 .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

关于c - 如何从 ml64.exe(MSVC 64 位 X64 汇编程序)访问线程本地存储?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10249522/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com