内核达人

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 4|回复: 0

gdb读写寄存器的秘密

[复制链接]

25

主题

25

帖子

83

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
83
发表于 7 天前 | 显示全部楼层 |阅读模式
哈喽,大家好,我就是那个不喜欢在大厂搬砖,不喜欢在研究院做研究,只喜欢创业做计算机底层课程的coder,子牙老师

讲调试器底层原理的文章、书、视频极少极少,但是调试器强大的功能吸引着无数的coder想去一探究竟,我也是其中之一。

之前写过系列文章《从零手写gdb调试器》《调试器是如何让代码停下来的》《gdb单步调试底层实现》《gdb查看反汇编底层原理》,感兴趣的小伙伴可以去看看。还做了一个系统性大课《从零带你手写gdb调试器》,想学习手写调试器的小伙伴可以加班主任vx【jvm-anan】咨询

今天来聊另一个话题:gdb调试器是如何读写线程上下文之寄存器的?(瓦特?你不知道寄存器是什么?给你最高效的学习视频!关注公众号【硬核子牙】回复【汇编教程】免费领取)

这篇文章看完,你除了对gdb调试器读写寄存器的信息了如指掌,你还能写代码随意修改进程的寄存器信息,想玩黑客必备技能!

普通coder跟黑客的区别是什么?我觉得最大的区别就是:普通coder对计算机世界的各种束缚束手无策,而黑客则是了解这些束缚的原理,进而突破束缚,实现自己的一切不为人知的目的!

国际惯例,先看读写寄存器的案例吧

info r查看所有寄存器信息(x64 CPU)
print查看具体某个寄存器,set可以修改寄存器
这些功能的底层是如何实现的呢?Linux内核在其中扮演了怎样的角色呢?我已深入Linux内核,解开了这些疑惑,分享给你

以下,enjoy

gdb拿到的是镜像
没研究之前,我就在想:当用户在gdb中输入info b,调试器会委托Linux内核去拿调试进程的寄存器数据?这怎么做的到呢?一看代码,原来Linux内核是把进程镜像中的寄存器数据给Linux内核了,然后Linux内核把返回给gdb

所以如果想知道gdb是如何拿到调试进程的寄存器信息的,问题就转化为:1、进程的寄存器信息,在Linux内核中是如何存储的?2、Linux内核是在何时保存这个信息的?

接着走

进程的寄存器信息存储
上玩底层的顶级思维:如果这个代码让你来写,你会把寄存器信息存储在哪?

存储到task_struct中?类似这样
我写的操作系统就是这么干的。如果你想学习手写操作系统,可以咨询班主任【jvm-anan】我的课程《手写64位多核操作系统》,不难,学起来非常有趣!
但是Linux内核不是这么干的!那Linux内核存储在哪呢?stack中!你怎么证明呢?我单步调试Linux内核给你看

这个环境是我自己打造的交互式单步调试Linux内核环境,玩Linux内核非常方便!全网唯一!对应的课程是《手写Ubuntu Linux发行版》+《手写gdb调试器》,感兴趣的小伙伴咨询班主任【jvm-anan】

将我写的gdb调试器上传到我写的Linux系统中,跑起来
在Linux内核中的sys_ptrace处下断点
眼尖的小伙伴发现了:这函数不是sys_ptrace啊,忽悠人呢!这个就是sys_ptrace,只不过我为了调试方便,搞成了这样
接下来调试器中执行查看寄存器的命令:info r,就可以调试Linux内核找答案了
最终找到核心代码
看copy_regset_to_user的第二个参数,这段代码涉及到Linux内核中的一个机制:regset view机制。你要理解这个机制就需要了解CPU的运行模式,这里就不展开讲了,感兴趣的自行研究或学习我的课程《手写64位多核操作系统》
还没找到最终答案,接着走,进入函数copy_regset_to_user

代码走到哪去了呢?你了解了regset view机制就知道,是这个函数

核心代码就在函数genregs_get中
__put_user就是Linux内核将数据写回用户态的一个宏,那第一个参数毫无疑问,是内存地址。看这个内存地址是否是stack的地址,就可以证明了

info r查看的是通用寄存器,走的是最后那个函数,第一个参数是寄存器信息所在的内存地址
答案找到了!task_stack_page!
这里面的公式是什么意思?因为栈是自高地址向低地址用,所以需要加THREAD_SIZE,就到了栈顶。再减去栈顶的padding,才是真正在用的栈顶。最后为什么-1?因为需要把指针拨到寄存器信息开始的地址
寄存器有那么多,在栈中是按什么顺序存储的呢?
你会发现,这个结构,跟gdb调试器获取寄存器信息传入的参数顺序是一样的!
至此,谜底揭开!进程的寄存器信息存储在stack中。注意!这个stack是进程的内核态栈!

那寄存器的信息是CPU写入的还是Linux内核写入的?又是何时写入的呢?

who在when写入
直接说答案!通用寄存器的信息是Linux内核写入的,不可能是CPU写入的

此时你是不是想问:你为什么这么笃定的说这句话?因为我写过操作系统,我了解CPU,知道CPU会做什么不会做什么。我在写操作系统的时候,寄存器的数据就是我自己写代码保存的
保存寄存器信息就是为了给寄存器去读的吗?当然不是!你应该知道,你的程序运行,是在很多个CPU时间片下完成的吧。就像你看一本书,不可能一口气读完吧,此时有人抬杠了:我可以!给你个白眼

你看一本书,每天看一点,你看后面的得回想起前面的内容吧,这叫记忆的保存与恢复

程序也是一样的,在第二个CPU时间片执行代码,得知道第一个CPU时间片,程序执行到哪了,执行出了哪些结果。所以第一个CPU时间片执行完了,需要保存寄存器信息,给第二个CPU时间片恢复记忆用。从程序的角度,这个叫线程上下文的保存与恢复

调试器,只是恰好用得上这个记忆!就像你书还没看完,别人跟你聊起,你能从记忆中把信息调取出来一样

其实就算计的世界,跟人的世界,道理是相通的!

Linux内核写入寄存器信息
Linux内核这部分代码在哪呢?得调动我写操作系统的经验了

第一个CPU时间片结束,保存上下文。为什么会结束?中断来了,线程调度要开始了!所以朦胧的知道,代码大概率在线程切换的位置。Linux内核线程切换的入口在哪呢?这里
找到答案了!
注释的意思是:切换新进程的内存空间及寄存器信息,相对的就是,保存老线程的寄存器信息。继续找核心代码
功夫不负有心人,找到了!
cool!你已经知道线程的调试器信息保存在哪了,你自己写代码把stack中的寄存器数据改掉,线程恢复运行的时候,是不是就将你修改的寄存器数据恢复到真正的CPU寄存器中了,不就达到了修改寄存器的目的!

所以我一直说,你不自己写一个操作系统就去读Linux内核,是不可能完全玩明白的。就像你都没读过书,连字都不认识,就想写一篇这么硬核的文章,是不可能做到的,一样的道理

同样,你脑子里没货,AI发展的再强大,它对你的价值,也只是那么大。因为你问不出你认知以外的问题,你也看不懂你认知以外的答案!


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|内核达人

GMT+8, 2025-12-6 13:31 , Processed in 0.049963 second(s), 21 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表