[libc-commits] [libc] [libc] Implement getcontext and setcontext for x86_64 (PR #192343)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Thu Apr 16 08:56:16 PDT 2026


================
@@ -34,7 +34,124 @@ void basic_stub_test() {
   ASSERT_TRUE(true);
 }
 
+void register_preservation_test() {
+  ucontext_t ctx;
+  static volatile int jumped = 0;
+
+  register long r12_val asm("r12") = 0x1212121212121212;
+  register long r13_val asm("r13") = 0x1313131313131313;
+  register long r14_val asm("r14") = 0x1414141414141414;
+  register long r15_val asm("r15") = 0x1515151515151515;
+
+  register void *rdi_val asm("rdi") = &ctx;
+
+  asm volatile("call *%[getcontext_ptr]"
+               : "+r"(rdi_val), "+r"(r12_val), "+r"(r13_val), "+r"(r14_val),
+                 "+r"(r15_val)
+               : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+               : "memory", "rax", "rcx", "rdx", "rsi");
+
+  if (!jumped) {
+    jumped = 1;
+
+    // Modify registers to ensure they are restored from context
+    asm volatile("movq $0, %%r12\n\t"
+                 "movq $0, %%r13\n\t"
+                 "movq $0, %%r14\n\t"
+                 "movq $0, %%r15\n\t" ::
+                     : "r12", "r13", "r14", "r15");
+
+    register const ucontext_t *rdi_set asm("rdi") = &ctx;
+    asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+                 [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+                 : "memory");
+
+    ASSERT_TRUE(false); // Should not reach here
+  }
+
+  ASSERT_EQ(r12_val, (long)0x1212121212121212);
+  ASSERT_EQ(r13_val, (long)0x1313131313131313);
+  ASSERT_EQ(r14_val, (long)0x1414141414141414);
+  ASSERT_EQ(r15_val, (long)0x1515151515151515);
+}
+
+void test_rbx_rdx() {
+  ucontext_t ctx;
+  static volatile int jumped = 0;
+
+  register long rbx_val asm("rbx") = 0xBBBBBBBBBBBBBBBB;
+  register long rdx_val asm("rdx") = 0xDDDDDDDDDDDDDDDD;
+
+  register void *rdi_val asm("rdi") = &ctx;
+
+  asm volatile("call *%[getcontext_ptr]"
+               : "+r"(rdi_val), "+r"(rbx_val), "+r"(rdx_val)
+               : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+               : "memory", "rax", "rcx", "rsi");
+
+  if (!jumped) {
+    jumped = 1;
+
+    asm volatile("movq $0, %%rbx\n\t"
+                 "movq $0, %%rdx\n\t" ::
+                     : "rbx", "rdx");
+
+    register const ucontext_t *rdi_set asm("rdi") = &ctx;
+    asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+                 [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+                 : "memory");
+
+    ASSERT_TRUE(false);
+  }
+
+  ASSERT_EQ(rbx_val, (long)0xBBBBBBBBBBBBBBBB);
+  ASSERT_EQ(rdx_val, (long)0xDDDDDDDDDDDDDDDD);
+}
+
+void test_r8_r11() {
+  ucontext_t ctx;
+  static volatile int jumped = 0;
+
+  register long r8_val asm("r8") = 0x0808080808080808;
+  register long r9_val asm("r9") = 0x0909090909090909;
+  register long r10_val asm("r10") = 0x1010101010101010;
+  register long r11_val asm("r11") = 0x1111111111111111;
+
+  register void *rdi_val asm("rdi") = &ctx;
+
+  asm volatile("call *%[getcontext_ptr]"
+               : "+r"(rdi_val), "+r"(r8_val), "+r"(r9_val), "+r"(r10_val),
+                 "+r"(r11_val)
+               : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+               : "memory", "rax", "rcx", "rdx", "rsi");
+
+  if (!jumped) {
+    jumped = 1;
+
+    asm volatile("movq $0, %%r8\n\t"
+                 "movq $0, %%r9\n\t"
+                 "movq $0, %%r10\n\t"
+                 "movq $0, %%r11\n\t" ::
+                     : "r8", "r9", "r10", "r11");
+
+    register const ucontext_t *rdi_set asm("rdi") = &ctx;
+    asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+                 [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+                 : "memory");
+
+    ASSERT_TRUE(false);
+  }
+
+  ASSERT_EQ(r8_val, (long)0x0808080808080808);
+  ASSERT_EQ(r9_val, (long)0x0909090909090909);
+  ASSERT_EQ(r10_val, (long)0x1010101010101010);
+  ASSERT_EQ(r11_val, (long)0x1111111111111111);
----------------
labath wrote:

Just in case you run into problems here: I found this in the [gcc docs](https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html) (it's gcc, but I think it's likely clang behaves the same way):

"""
```
register int *p1 asm ("r0") = …;
register int *p2 asm ("r1") = …;
register int *result asm ("r0");
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
```

Warning: In the above example, be aware that a register (for example r0) can be call-clobbered by subsequent code, including function calls and library calls for arithmetic operators on other variables (for example the initialization of p2). In this case, use temporary variables for expressions between the register assignments:

```
int t1 = …;
register int *p1 asm ("r0") = …;
register int *p2 asm ("r1") = t1;
register int *result asm ("r0");
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
```
"""

It's possible that whatever function is used to implement ASSERT_EQ may clobber some of these register values. It sounds like that can be avoided by copying the value into a regular (non-register) variable before checking it's value.

https://github.com/llvm/llvm-project/pull/192343


More information about the libc-commits mailing list