汇编 - 线程安全局部变量

发布于 2024-11-09 02:21:42 字数 1055 浏览 4 评论 0原文

我试图在汇编程序中使用线程安全的局部变量。 我在网上搜索过,但没有找到任何简单的东西。

我目前正在使用 GCC 汇编器,因为该程序是 C 代码和汇编的混合,但最终程序将包含多平台/调用约定的代码。

目前,我已经使用 .lcomm 伪操作声明了我的变量。 据我了解,这些变量将放置在 .bss 部分中。 所以我猜它们将被所有线程共享。

有没有办法直接在程序集中使用某种 TLS 变量,或者我应该使用特定于平台的实现,例如 Windows 上的 pthread__declspec

希望它足够清楚。如果需要更多信息,请随时询问。

谢谢大家,

编辑

这是有问题的代码:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp
    movq    %rsp,   %rbp

    xor     %rax,   %rax

    popq    stack0( %rip )
    popq    stack1( %rip )

    callq   *%rdi

    pushq   stack1( %rip )
    pushq   stack0( %rip )

    leave
    ret

基本上,它用于重定向对 C 函数的调用。

C 原型是:

extern uint64_t XSRuntime_CallMethod( void ( *m )( void * self, ... ), ... );

它采用函数指针作为第一个参数,因此是 callq *%rdi,因为我正在使用系统 V ABI 对此进行测试。

汇编代码非常简单,我想保持这种方式,这样它就可以很容易维护。

问题是:如何使 stack0 和 stack1 变量线程安全。

I'm trying to have thread-safe local variables in an assembly program.
I've searched on the net, but I haven't found anything simple.

I'm currently using GCC assembler, as the program is a mix of C code and assembly, but the final program will contain code for multiple-platforms / calling conventions.

For now, I've declared my variables using the .lcomm pseudo-op.
As I understand it, those variables will be placed in the .bss section.
So I guess they will be shared by all threads.

Is there a way to have a kind of TLS variables directly in assembly, or should I use platform-specific implementations, like pthread or __declspec on Windows?

Hope it's clear enough. Don't hesitate to ask if more information is needed.

Thanks to everyone,

EDIT

Here's the code in question:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp
    movq    %rsp,   %rbp

    xor     %rax,   %rax

    popq    stack0( %rip )
    popq    stack1( %rip )

    callq   *%rdi

    pushq   stack1( %rip )
    pushq   stack0( %rip )

    leave
    ret

Basically, it's used to redirect a call to a C function.

The C prototype is:

extern uint64_t XSRuntime_CallMethod( void ( *m )( void * self, ... ), ... );

It takes a function pointer as first argument, hence the callq *%rdi, as I'm testing this with system V ABI.

The assembly code is very simple, and I'd like to keep it that way, so it can be easily maintainable.

The question is: how to make the stack0 and stack1 variables thread safe.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

别再吹冷风 2024-11-16 02:21:42

对汇编程序不太熟悉,所以:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp // save BP
    movq    %rsp,   %rbp // load BP with SP

    xor     %rax,   %rax  // clear AX

    popq    stack0( %rip )  // pop return address into STACK0
    popq    stack1( %rip )  // pop flags into stack1

    callq   *%rdi  // call the indirect procedure, so putting flags/return to         XSRuntime_CallMethod onto stack

    pushq   stack1( %rip ) // put caller flags onto stack
    pushq   stack0( %rip ) // put caller return onto stack

    leave // clean passed parameters from stack
    ret   // and back to caller

它是这样工作的,是吗?

如果是这样,直接跳转到间接过程而不是调用它不是更容易吗?然后,您不需要任何额外的变量来保存调用者标志/返回&间接过程直接返回给调用者。

只是一个建议 - 自从我做汇编以来。

如果您必须将调用者地址存储在某处,请对 SP 进行 dec(输入?),然后使用堆栈帧。其他任何事情在某些时候都可能是线程不安全的。

平均值,
Martin

嗯,使用 TLS 也许不是线程不安全的,但是递归调用呢?您最终会在 TLS 中使用另一个堆栈来解决此问题,因此您也可以使用“SP”堆栈

Martin

Not that familiar with assembler so:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp // save BP
    movq    %rsp,   %rbp // load BP with SP

    xor     %rax,   %rax  // clear AX

    popq    stack0( %rip )  // pop return address into STACK0
    popq    stack1( %rip )  // pop flags into stack1

    callq   *%rdi  // call the indirect procedure, so putting flags/return to         XSRuntime_CallMethod onto stack

    pushq   stack1( %rip ) // put caller flags onto stack
    pushq   stack0( %rip ) // put caller return onto stack

    leave // clean passed parameters from stack
    ret   // and back to caller

Is this how it works, yes??

If so, would it not be easier to just jump to the indirect procedure, rather than calling it? You then don't need any extra variables to hold the caller flags/return & the indirected procedure returns directly to the caller.

Just a suggestion - while since I did assembler.

If you have to store the caller address somewhere, dec the SP, (enter?), and use the stack frame. Anything else is likely to be thread-unsafe at some point.

Rgds,
Martin

Well, with a TLS maybee not thread-unsafe, but what about any recursive calls? You end up with another stack in the TLS to cover this, so you may as well use the 'SP' stack

Martin

无悔心 2024-11-16 02:21:42

您认为编译器如何实现线程局部变量?尝试使用 -S 或 /FAs 编译这样的程序,您就会看到。提示:它必须依赖特定于操作系统的 API 或其他详细信息才能访问 TLS 存储。有时,准备步骤隐藏在 CRT 中,但没有单一的方法可以完成。

例如,最近的 MSVC 是这样做的:

_TLS    SEGMENT
?number@@3HA DD 01H DUP (?)             ; number
_TLS    ENDS
EXTRN   __tls_array:DWORD
EXTRN   __tls_index:DWORD
_TEXT   SEGMENT
[...]
mov eax, DWORD PTR __tls_index
mov ecx, DWORD PTR fs:__tls_array
mov edx, DWORD PTR [ecx+eax*4]
mov eax, DWORD PTR ?number@@3HA[edx]

正如您所看到的,它使用由 CRT 初始化的特殊变量。

在最近的 Linux 上,GCC 可以使用特定于 TLS 的重定位:

.globl number
    .section    .tbss,"awT",@nobits
number:
    .zero   4
    .text
    [...]
    movl    %gs:number@NTPOFF, %eax

如果您想要可移植性,最好不要依赖此类特定于操作系统的细节,而是使用通用 API,例如 pthread 或使用 Martin 提出的基于堆栈的方法。但我想如果你想要可移植性你就不会使用汇编器:)

How do you think the compiler implements thread-local variables? Try compiling such a program with -S or /FAs and you'll see. Hint: it has to rely on OS-specific APIs or other details to get access to TLS storage. Sometimes the preparation steps are hidden in the CRT but there's no single way to do it.

For example, here's how recent MSVC does it:

_TLS    SEGMENT
?number@@3HA DD 01H DUP (?)             ; number
_TLS    ENDS
EXTRN   __tls_array:DWORD
EXTRN   __tls_index:DWORD
_TEXT   SEGMENT
[...]
mov eax, DWORD PTR __tls_index
mov ecx, DWORD PTR fs:__tls_array
mov edx, DWORD PTR [ecx+eax*4]
mov eax, DWORD PTR ?number@@3HA[edx]

As you can see, it uses special variables which are initialized by the CRT.

On recent Linux, GCC can use TLS-specific relocations:

.globl number
    .section    .tbss,"awT",@nobits
number:
    .zero   4
    .text
    [...]
    movl    %gs:number@NTPOFF, %eax

If you want portability, it's best not to rely on such OS-specific details but use a generic API such as pthread or use stack-based approach proposed by Martin. But I guess if you wanted portability you wouldn't use assembler :)

咋地 2024-11-16 02:21:42

?? “经典”局部变量,即。通过堆栈偏移访问的参数/变量/结果本质上是线程安全的。

如果您需要与平台无关的“TLS”,请在恢复所有线程之前将一些合适的结构/类实例传递到所有线程中,作为创建参数,在线程字段中,将第一条消息传递到线程输入队列或其他任何内容。 ..

Rgds,
马丁

?? 'Classic' local variables, ie. parameters/variables/results accessed by stack offsets, are inherently thread-safe.

If you need a 'TLS' that is platform-agnostic, pass some suitable struct/class instance into all threads, either as the creation parameter, in a thread field before resuming all the threads, first message to the threads input queue or whatever...

Rgds,
Martin

空城缀染半城烟沙 2024-11-16 02:21:42

如前所述,局部变量(基于堆栈)本质上是线程安全的,因为每个线程都有自己的堆栈。

所有线程都可以访问的线程安全变量(不是基于堆栈的)可能最好使用自旋锁(或 Windows NT 引擎中的等效项,关键部分)来实现。这样的变量必须在访问前锁定,访问后再解锁。一种变体可能是读取是免费的,但写入必须通过锁定/解锁来构建。

据我所知,只有编译器本身不实现线程安全变量。相反,它们提供了访问所需操作系统功能的库函数。

As previously mentioned, local variables (stack-based) are inherently thread-safe because each thread has its own stack.

A thread-safe variable which is reachable by all threads (not stack-based) is probably best implemented using a spin-lock (or the equivalent in the Windows NT engine, a critical section). Such a variable must be locked before access, accessed and then unlocked. One variant could be that reads are free, but writes must be framed by locking/unlocking.

AFAIK only compilers themselves don't implement thread-safe variables. Instead they provide lib functions which access the required OS functionality.

迷你仙 2024-11-16 02:21:42

您可能应该使用(调用)TlsAlloc & TlsFree (或其他操作系统的等效项)来执行类似的操作。返回的索引可以一次性存储在全局集合中,只读变量以便于使用。

根据变量保存的内容以及使用它们的代码的作用,您可能能够摆脱原子操作,但这可能会产生其自身的问题。

You should probably be using(placing calls to) the TlsAlloc & TlsFree (or their other OS equivalents) to do something like this. the returned indices can be stored in global set once, read-only vars for easy use.

Depending on what the vars hold and what the code using them does, you might be able to get away with atomic ops, but that might yield problems of its own.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文