I have experimented with a userspace thread using C++. In the current version of the code, I am only able to point stack pointer to a specific function pointer but not sure how to place arguments.
Here is the basic implementation without function argument for the child thread. In the current implementation, I kept only two threads.
// File: main.cpp
/* Ubuntu 18.04 **x86_64** */
/* Compiler gcc version 7.5.0 */
/* Linux 5.6.2 */
/* g++ -g -std=c++11 main.cpp ContextSwitch.S */
#include <iostream>
const int __stack_size = 1 << 20; // 1 MB
#define PrintF printf("%d : %s\n",__LINE__,__FUNCTION__);
// A Set of Registers to Represent the Context of Thread
// These register values are presered across function calls
// in x86-64 ABI
struct Context {
uint64_t rsp;
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t rbp;
uint64_t rbx;
};
// This is a function defined inside an Assembly file
// According to x86-64 ABI convention
// rdi == curr and rsi == next
extern "C" void ContextSwitch(Context *curr,Context *next);
// Lets take only two Thread
static struct Context Main;
static struct Context Child;
// This function helps to get back control from Child Thread
// When Child thread completes its assigned function
// this following function will be invoked
static void sched() {
PrintF
// now please switch back to Main thread
ContextSwitch(&Child,&Main);
std::cout <<"Not Reachable !" << std::endl;
}
// Child thread is going to run this function
void func() {
PrintF
// some dummy calculation inside this function
int aa[100]; // 400 Bytes
for(int i = 0;i<100;i++) {
if(i%20 == 0) {
aa[i] = 2*i;
}
}
// on return from this function
// ret instruction will be called
// due to which rip will point to the value of
// top 8 byte of childStack
// which is basically sched function
}
typedef unsigned long long ULL;
int main() {
// allocate stack memory for the Child Thread
char * childStack = new char[__stack_size];
// sched() will be pointed by rip when func() returns
*(ULL *)&childStack[__stack_size - 8] = (ULL)sched;
// func() will be pointed by rip just before ContextSwitch() returns below
*(ULL *)&childStack[__stack_size - 16] = (ULL)func;
// when ContextSwitch tries to return from the call below,
// rsp value will be loaded in rip due to ret call
// which makes rip == addr of func()
Child.rsp = (uint64_t)&childStack[__stack_size - 16];
ContextSwitch(&Main,&Child);
std::cout <<"Main is schduled again!" << std::endl;
delete[] childStack;
}
// File: ContextSwitch.S
.globl ContextSwitch
ContextSwitch:
movq %rsp, 0x00(%rdi)
movq %r15, 0x08(%rdi)
movq %r14, 0x10(%rdi)
movq %r13, 0x18(%rdi)
movq %r12, 0x20(%rdi)
movq %rbp, 0x28(%rdi)
movq %rbx, 0x30(%rdi)
movq 0x00(%rsi), %rsp
movq 0x08(%rsi), %r15
movq 0x10(%rsi), %r14
movq 0x18(%rsi), %r13
movq 0x20(%rsi), %r12
movq 0x28(%rsi), %rbp
movq 0x30(%rsi), %rbx
ret
This implementation executes in my machine with the following output:
54 : func
43 : sched
Main is schduled again!
Now I tried with argument int
argument in func()
. (Although have not actually passed anything)
// Child thread is going to run this function
void func(int arg1) {
PrintF
// some dummy calculation inside this function
int aa[100]; // 400 Bytes
for(int i = 0;i<100;i++) {
if(i%20 == 0) {
aa[i] = 2*i;
}
}
// on return from this function
// ret instruction will be called
// due to which rip will point to the value of
// top 8 byte of childStack
// which is basically sched function
}
GDB disassembly shows that the argument int arg1
is expected in the lower 32
bits of %rdi
.
Dump of assembler code for function func(int):
0x0000000000000a69 <+0>: push %rbp
0x0000000000000a6a <+1>: mov %rsp,%rbp
0x0000000000000a6d <+4>: sub $0x1c0,%rsp
0x0000000000000a74 <+11>: mov %edi,-0x1b4(%rbp)
If I try with three arguments as in void func(int arg1,int arg2,int agr3)
then
0x0000000000000a74 <+11>: mov %edi,-0x1b4(%rbp)
0x0000000000000a7a <+17>: mov %esi,-0x1b8(%rbp)
0x0000000000000a80 <+23>: mov %edx,-0x1bc(%rbp)
I also found online that, in x86_64
arguments are passed on stack also (sometimes, I am not sure about this).
My question is how to pass arguments to func
reliably ? Do I need to manually copy arguments into rdi
,rsi
and rdx
as we did for context switch? What about more number of arguments and other types of arguments like reference type
for example lvalue reference
or rvalue reference
in C++?
Thanks!