mysql上下文切换_如何在用户态完成一次上下文切换

X64栈幁结构

X64总体的栈幁总体如下图

3c50f96a3ba0e1242297882bd92c78fd.png

GCC没有优化的情况下的反编译

005915d096c3275034fb61c0257a9b8d.png

栈幁模型详述

读者通过上图观察可以看到函数的第一个参数放在RDI寄存器,第二个参数放在RSI寄存器 ... 后续参数放在何处,相关调用约定可以查看AMD 64 调用约定

读者从上图可以看出 ,在汇编层面上,由函数的被调用方保存 rsp rbp 指针, 函数a b c 在汇编层面都是首先将 rbp入栈(push rbp) 然后对rsp操作 (例如sub rsp , 16;)并都会在函数返回前分别 将rbp出栈 (pop rbp) 还原rsp (例如 add rsp, 16;)

另外X86864 fastcall约定使用RAX寄存器保存返回值

总结一下汇编的函数的模板大致如下

保护当前帧的调用方的rbp寄存器

4be7eb40e60b310ffb8aa763c0bb21d4.png

将当前的rsp指针作为当前帧的rbp指针,防止破坏当前调用方帧的临时变量

905858a9a571051e1edb682bd90dabc4.png

将rsp指针减去固定的数量 并存回rsp寄存器, 防止接下来当前帧中的被调用方破坏当前帧中的临时变量

39a92552db8eff0859bbb2688420df8c.png

返回前将当前帧需要返回的结果写入RAX寄存器,这一步比较隐晦 因为只有函数c 出现了这个步骤,在实际中函数返回都会依照约定写入RAX寄存器,由于函数b本身并没有对函数返回结果(RAX寄存器的值)进行加工,因此函数b返回的时候RAX寄存器的值并不变化,同理a也是如此。

分别恢复rsp rbp寄存器 ,如下图中 add rsp, 16 跟 pop rbp

01a2016fe6a385f96b5e7a9ce5c22d8e.png

通过上面几个套路模板,基本上我们就能使用汇编来编写汇编函数了,然后记住要非常小心地操作寄存器,避免破坏调用方的栈幁(函数中的临时变量)。

如何在用户态完成一次上下文切换

X64下一个线程在运行的时候,有一个PC寄存器指向当前线程的汇编代码的位置,我们需要通过更换PC寄存器中的值 让CPU接下来从PC寄存器中新的位置运行汇编代码

另外我们希望 从当前的上下文切换到另外一个上下文后,CPU能够切换回来继续正常运行,根据前面所说,那么我们需要保护当前上下文的 RSP RBP 指针,并且保证当前上下文的整个栈幁的区域不会被另外一个上下文给破坏

切换的具体步骤

注册信号,让操作系统 接下来切换上下文到 handler函数

//全局变量

char *buffer;

int pagesize;

void allocate_memory() {

/*

* 初始化信号量结构体

*/

struct sigaction sa;

sa.sa_flags = SA_SIGINFO;

sigemptyset(&sa.sa_mask);

// 注册 handler 函数 ,当我们触发信号的时候 操作系统会将切换到 handler 这个函数上下文来运行代码

sa.sa_sigaction = handler;

if (sigaction(SIGSEGV, &sa, NULL) == -1)

handle_error("sigaction");

pagesize = sysconf(_SC_PAGE_SIZE);

if (pagesize == -1)

handle_error("sysconf");

// 初始化buffer指针指向的内存区域

buffer = memalign(pagesize, 4 * pagesize);

if (buffer == NULL)

handle_error("memalign");

printf("Start of region: 0x%lx\n", (long) buffer);

//设置buffer指针为只读,接下来如果访问到buffer指针指向的内存区域就会触发信号

if (mprotect(buffer, pagesize * 4,

PROT_READ) == -1)

handle_error("mprotect");

}

触发操作系统信号前 做一些准备工作

int main(int argc, char *argv[]) {

/*

* allocate memory and set memory access READ

*/

allocate_memory();

char *p = buffer;

// 初始化当前函数调用帧中的3个临时变量

uint64 local_pc = 0;

uint64 local_rsp = 0;

uint64 local_rbp = 0;

//以下汇编代码是我根据C语言反编译后确定 三个local_变量的位置

__asm__(".intel_syntax;"

// 将当前CPU运行的代码的位置写入 RAX寄存器 方便handler中切换回来

// 其实这个rax寄存器中最终存放的内存地址 也就是 lea rax, [rip] 这个汇编代码在内存中的位置。

"lea rax, [rip];"

//保存RAX寄存器 到 local_pc变量

"mov [rbp-0x20], rax;"

//保存RSP寄存器 到 local_rsp变量

"mov [rbp-0x28], rsp;"

//保存RBP寄存器 到 local_rbp变量

"mov [rbp-0x30], rbp;"

);

//写入全局变量

pc = local_pc;

rsp = local_rsp;

rbp = local_rbp;

for (int i = 0; i < 4; i++) {

//当我们操作p指针也就是buffer指针的时候,就会触发信号

*(p) = 'a';

p++;

}

*(p) = '\0';

printf("p = %s\n", buffer);

//printf("%x",local_pc);

/*

* if we didn't restore those registers,

* it should not happen.

*/

printf("Loop completed\n");

exit(EXIT_SUCCESS);

}

信号触发后

static void handler(int sig, siginfo_t *si, void *unused) {

/*

* 打印受保护的内存地址

*/

printf("Got SIGSEGV at address: 0x%lx\n",

(long) si->si_addr);

// 取消buffer指针指向内存区域的内存保护权限

if (mprotect(buffer, pagesize * 4,

PROT_READ | PROT_WRITE) == -1)

handle_error("mprotect");

// 此时线程的控制权 已经归JVM代码掌控,JVM可以挂起当前线程,等完成GC垃圾回收工作后再恢复状态

// 老办法 将之前全局变量保存的寄存器值恢复到当前函数帧中的临时变量

uint64 local_rsp = rsp;

uint64 local_pc = pc;

uint64 local_rbp = rbp;

// 具体三个 local_xxx 变量的地址,依旧是我通过反编译C程序确定的

// 还是老办法 该怎么写到全局变量的 又怎么通过本地变量 local_xxx 写回到寄存器里面去

__asm__(".intel_syntax;"

"mov rsp,[rbp-0x20];"

"mov rax,[rbp-0x28];"

"mov rbp,[rbp-0x30];"

// 此处非常关键 直接jmp 调回去 让CPU回到之前main函数的上下文继续执行代码

"jmp rax;"

);

//never happen

printf("rsp:%x", local_rsp);

printf("pc:%x", local_pc);

printf("rbp:%x", local_rbp);

exit(EXIT_FAILURE);

}

整个流程的DEBUG的GIF如下

8b87f24ab784ea1edf24489f5b5942be.gif

联系我

如果有不懂的地方,欢迎通过留言联系我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值