1

这是来自musl的源代码:

 1  __syscall_cp_asm:
 2  __cp_begin:
 3      mov (%rdi),%eax
 4      test %eax,%eax
 5      jnz __cp_cancel

 6      mov %rdi,%r11

 7      mov %rsi,%rax
 8      mov %rdx,%rdi
 9      mov %rcx,%rsi
10      mov %r8,%rdx
11      mov %r9,%r10
12      mov 8(%rsp),%r8
13      mov 16(%rsp),%r9

14      mov %r11,8(%rsp)

15      syscall
16  __cp_end:
17      ret
18  __cp_cancel:
19      jmp __cancel

我很好奇第 6 行和第 14 行的目的是什么(从链接源重新编号)。

据我了解,代码的开头测试了作为第一个参数传递的指针的目标(第 3-5 行),第 6 行然后将指针移动到r11和第 14 行然后将其移动到堆栈上使用的位置通过第 7 个参数。

这似乎没有用。这些动作有什么作用吗?

4

2 回答 2

5

这是为了支持 pthread 取消点;信号处理程序可以稍后查看堆栈。

引入此代码的提交的提交日志解释说,在系统调用之前将指针存储在堆栈上的已知位置使得“取消信号处理程序”可以确定“被中断的代码是否处于可取消状态”。(该代码的初始版本也保存了syscall指令的地址,但后来的提交改变了这一点。)

第一个 arg(asm 函数存储在堆栈中)来自它的 C 调用者, __syscall_cp_c, 传递__syscall_cp_asm(&self->cancel, nr, u, v, w, x, y, z);, whereself来自__pthread_self().


你是对的,使用不同的传入 arg 覆盖调用者的堆栈 arg 对于遵循 x86-64 System V ABI 的 C 调用者来说是不“可见的”。(被调用者拥有它的堆栈参数;调用者必须假设它们已被覆盖,因此编译器生成的代码永远不会将该内存位置作为输出读取)。所以我们需要寻找其他解释。


我认为有必要在读取该内存位置8(%rsp) 使用 2 条 mov 指令将传入的 RDI 复制到该内存位置。我们不能延迟mov %rdx,%rdi直到加载之后,因为我们需要释放 RDX 来保持 R8,释放 R8 来保持负载。在用于加载另一个 arg 之前,您可以通过使用 R10 来避免接触“额外”寄存器,但它仍然需要至少 2 条指令。

或者可以优化 arg 顺序以在后面的 arg 中传递该指针,可能最后传递调用号和最后一个寄存器 arg 中的 pthread 指针(最小的改组但避免需要对该测试/分支进行双重取消引用)或第一个stack arg(无论如何你想要它)。或者匹配第一个没有 pthread 指针的__syscall包装器的 arg 顺序。nr

于 2020-09-20T22:14:01.073 回答
-1

第 7 - 14 行中的代码按参数顺序将参数加载到系统调用。由于 RDI 在第 8 行加载,它的值保存在 R11 中,以便可以在第 14 行将其写入参数 8(在堆栈上)。

在手写的汇编代码中,通过这样组织事物可以更容易理解和维护,这超过了额外移动指令的成本。

于 2020-09-20T20:05:37.077 回答