欢迎光临散文网 会员登陆 & 注册

【知乎】x86架构&linux内核系列(五-六)

2023-12-03 07:00 作者:失传技术  | 我要投稿

x86架构&linux内核系列(五) 番外篇------聊聊寄存器和Linux crash分析


雪瀑牵裳


IT 老棒槌

8 人赞同了该文章

呃,上一篇结尾后,工作上的事情让我不能分心继续写专题。
既然我们已经聊了一下CPU的core,所以我们就可以初步说说寄存器了。而寄存器又和os的crash分析密切相关。因此,我们恰似站在一个叉路口,虽然我们的计划仍然是先沿着intel x86架构这个方向走往下走,但是也不妨站在这个岔路口歇歇,向另外的方向看linux crash分析的风景和故事。
一、啥是寄存器(register)
寄存器位于CPU内部,用于存放程序执行中用到的数据和指令,CPU从寄存器中取数据,相比从内存中取快得多。
95~99年我上大学时学计算机结构,那是我还真的把寄存器当作CPU内部的“器”。

而现在,我越来越喜欢将寄存器认为是:在CPU内部有专用的快速存储空间。Intel严格规定了这些快速存储空间内每个地址的用途。而且Intel为了好理解,将空间内特定地址的几个字节,起了个好理解的名字,这个名字就是寄存器的名字。
——所以,寄存器实际上就是CPU内部的专用快速存储空间内,某几个字节的地址(可以表示为某基址加上不同的偏移量(这个“偏移量”就是 intel eds 里总是伴随寄存器出现的、让人不好理解的一个词,呃,offset!))。

一个物理CPU/socket内部,有cores部分和uncore moudules部分。 CPU里的寄存器有太多太多,intel eds第二本 3200页,第三本 900页,讲的是CPU socket uncore modules的各个寄存器+core里的MCA状态寄存器。至于core里的程序寄存器,我还没有看过相关的文档呢。估计怎么着也有几百页吧。所以这么看skx CPU 的全部寄存器资料,应该至少有4000页了。
——这4000页就相当于X86知识体系结构的字典。

又要讲个小故事了:半年前我曾接到一个指示,让我先看一份英文原版的、据说有个十多页的intel的文档,然后用个把月时间整理出个诊断工具出来。这话说的是轻描淡写,但是我一打开那个文档,嚯!就是他娘的intel eds 第二卷——全英文的3180页!而且公司内部也没有intel x86架构入门培训资料让我先铺垫铺垫入个门。这事儿恶心的,就像一大仙儿拍下脑袋,扔给一个小学生一本新华字典,让他自己学着写作文……不过还是要感谢司内的这些仙儿们,虽然他们只负责拉通,但最终还是激励了我,通过各种圈内渠道,补全自己的知识库,做了给人的技术积累。当然,这事儿也让我对某些所谓浸淫多年x86领域的群体,又有了一个新认识。

下面是我所接触过的三大类寄存器。

二、core内部和操作系统、crash分析相关的寄存器

这部分寄存器包括:通用寄存器 和 栈寄存器。

(一)通用寄存器:

例如ax/bx/cx/dx/di/si这些,如果按照目前64位的说法,这些寄存器前面加上”r”或“e”表示64位。那么以上这些寄存器就对应称为:rax/rbx/rcx/rdx/rdi/rsi 。
另外还有一堆的rXX,XX代表数字。
以上这些寄存器在程序指令中,用于存储变量(其实是变量所在的栈地址)并用于变量赋值/运算。其中,有一些寄存器也有一些特定用法的,例如:
Rdi和rsi,常用于 函数外部向函数内部传参。
rax,常用与函数返回值。

假设有一天,你有幸进行linux crash分析,那么使用crash工具对嫌疑进程进行反编译,你所看到的冰山一角就是这个样子滴:(里面有edx,rxx,rbp…)


然后对linux crash分析的大体过程就是:
1、你需要根据上图反编译出来的汇编语言中的寄存器的命令逻辑 对照 内核C语言程序的指令逻辑,然后将二者逻辑完全印证理解;——逻辑流分析
2、然后再根据下面论述的Core 内的数据栈,以及栈寄存器的值,找到C语言指令上下文、以及函数调用上下文中的变量、参数(其实在linux里,绝大部分参数都是结构体)的变化和传递过程。——数据流分析

是不是有点看不懂?
那就换个说法:如果我们把Linux crash分析 对比 病人诊断 那么:
反编译出来的汇编指令 对应着 病人哼哼;
C语言指令上下文 对应着 病理推断;
变量的变化、尤其是函数传参过程中参数的变化 对应着 各项医学仪器检查结果;
Linux内核知识 对应着 人体组织结构图;
Crash分析案例库 对应着 医院里的案例参考;

(想想看,操作系统crash分析的知识体系是不是呼之欲出了?)
——以上这五者,就构成了 crash分析艺术。
所以有个redhat原厂专家在做技术讲座的时候曾说过,不同的人分析同一个crash,有可能分析方向不同,分析过程不同,甚至,甚至最后的结论也有差异。但是单从crash分析过程讲,可能每个人的分析都是有道理的。——所以就用了艺术这个词。因为艺术的形式是多样的。
所以,如果没有上面5个方面的基础,linux 专家抽个半天时间给你讲crash分析,听众也就是凑个热闹,开个眼界,不明觉厉而已。

思考:
1、有哥们曾经问我:你教我怎么分析redhat crash吧。当时我该怎么回答?只能叹气啊。
2、有想象力的某些人,想让我做个工具:这头输入crash dump文件,那头直接出结果。我该怎么做?这可是未来的AI主题啊。
——嗯,在心底真的佩服无知者无畏。

(二)上下文相关寄存器:
在linux crash分析中常用,描述了进程函数的栈数据结构。

Crash分析常见以下三种:
Rbp/ebp寄存器:栈的基地址
rsp/esp寄存器:栈的当前地址也有叫栈顶的;
rip寄存器:当前执行指令的下一条指令;


进程空间图先放这里,以后到redhat crash分析系列来讲:

上图0x015fbfff就算rbp了;0x015fa878就算rsp了;Current箭头 (靠近0x015fa000)的地方,右侧,相当于当前的那条代码),就算rip了;

其实这个图很粗,首先这是32位用户态的进程数据结构,地址格式都有些过时了。另外这幅图粗的连segment都没列出来。先这么看吧。

曾经某case的 “嫌疑进程”在把系统搞死之前留下这样的的“遗书”——又叫call trace:

——可以自己先猜猜,里面都暗示了啥,参悟一下吧,以后另一个系列会细讲。

看上面的图片,ffffxxxxxxxxxxxx打头的以及ffffffffxxxxxxxx等一片片Hex数字像天书。

呃,如果你想明白这些天书代表什么意思,最好搞明白以下机制和数据结构:

x86服务器架构 系统地址映射、

Linux 64位空间地址结构、

用户态/内核态、

内核态下的虚拟地址与物理地址的映射、

Page_offset与text_offset。


而以上这些仍只是基础,接下来还是会对 linux crash 分析感到云里雾里的。那我就再讲个故事、再类比一下linux crash分析,又叫vmcore分析:
话说一头猪妈妈,产下猪仔仔后,突然晕过去了——linux crash了。
然后一个兽医来了 ——linux专家响应这个case。
这位兽医能听懂猪的语言——专家要懂点汇编。
这位兽医当然也能懂人话——专家要懂C语言。
这位兽医熟读本山大叔的著作:《论母猪的产后护理》——专家有深厚的linux内核知识。
这位兽医是有十多年经验的当地头牌兽医——专家有丰富的案例库供查询。
然后兽医根据这头猪醒了以后的哼哼唧唧,详细还原了猪妈妈晕倒前后每一个细节和过程——这就是crash文件解析。
然后兽医又发现,猪妈妈产下的猪仔仔居然有点像小象——其他的外部日志和异常。

最终兽医得出以下结论:
由于猪妈妈生下了一头长的像小象的猪仔仔,怕猪爸爸生气,导致心理压力过大,然后茶不思饭不想,出现了低血糖现象,所以晕了过去……
这大概就是crash分析了。

现在,只能通过类比来感受一下了。具体的,以后若开linux内核专题再讲。

好了,linux的crash的风景看完、故事听完,发现intel CPU x86 架构仍是基础。

所以我们还是继续返回到intel CPU x86 架构吧。

三、Socket Core MSR寄存器
用于MCE 处理,记载了MCE发生时,CPU,MEM各个硬件的状态和故障指向。在讲完所有的CPU uncore modules 后,再开始详细的聊这部分内容。
MCE是整个x86诊断体系的基础,是服务器诊断ts的重要组成部分。

四、Socket uncore部分的CSR 寄存器
用于cpu uncore 部件的运行以及状态记录。常用与高级诊断。
1、 和MCE结合,判断multi MCE或Nest MCE的 root cause。
2、 如果没有出现MCE,就要通过CSR判断是否出现了更严重的IERR,以及原因。
在讲完所有的MCA寄存器后,再开始详细的聊这部分内容。

好了,本次的正式内容又结束了。

有位哥哥,在朋友圈里转发我的技术文章时,吹嘘我是IT圈里的文艺青年。虽然吹的不是太走心,但是在这里,弟弟我还是要头拱地配合一下的。
上周某个夜晚,一个人听了首老歌《董小姐》,很有感觉。于是就顺着感觉写了下面长短句。

听《董小姐》
今晚,
听董小姐的歌。
慵懒的呓语,
仿佛指尖上玩火。
半杯红酒,
酡然挥不去 有些颓废,
不知从前往事,
还能挽留几丝余味?
轻嗅手中烟草,
假装戒了好多年,
安静与躁动之间,
无言对无眠。
一丢丢放肆,
才要点燃,
却已是结尾,
温柔断弦。

编辑于 2019-05-14 21:22

「真诚赞赏,手留余香」

还没有人赞赏,快来当第一个赞赏的人吧!

x86-64


我眼中的x86地图,聊聊“”地址”那些事儿


雪瀑牵裳


IT 老棒槌

关注他

23 人赞同了该文章

一直想写,却一直不知道该如何来写这篇关于“地址”的文。

“地址”这个概念似乎很简单,稍有计算机基础的人都会接触。

这个概念又很混乱。网上各种文章,张冠李戴的毛病是一种,常常随意省略概念前的主语、定语的毛病又是一种。所以通读文章的时候,常搞得人精神分裂。

然而地址这个概念又无处不在,几乎贯穿了我所接触的技术领域:从某个硬件module(例如CPU,DIMM/IMC,cache,mmu,iommu)向上到不硬不软的bios/uefi,乃至软件OS内核,甚至再向上到达更软的应用程序。

这个概念写起来委实不易。

因为计算机中的“地址”这个概念,依据不同的“空间层次”或者说“平面”,其实也分成好几类。

所以,当我要写XXXX地址的时候,觉得必须先要把XXXX说明白,然后才能讲xxxx地址。
然后当我另起一段想说新的YYYY地址的时候,我又要先说什么是YYYY……

所以本来计划十多分钟的技术贴,最后能洋洋洒洒写出另外一整套系列。

我又一直想写它,因为这个概念仿佛就是一个里程碑,或者是我脚下的重要阶梯。
先前,正是因为我不断的理解这个概念、重复又重复,所以才不知不觉的从硬件、从PCIE、从CPU一堆的MSR寄存器和CSR寄存器,跳进了OS内核。

今天尝试写一下吧,欢迎进入我的x86世界大观。


本文所涉及的硬件平台指的是Intel purley平台以及Intel xeon skylake CPU;
操作系统缺省指的是linux。

想象一下,此时我正站在一个路口,这个路口称为x86硬件架构。从这个路口往前往后往左往右看,都可以到达不同的地方。

有的方向,会进入另外的专业领域,我们会尝试走几步看看风景就好,然后回头。因为若是走到了百花深处,又是另外一片世界了。

有的方向,我会尝试一直走下去。



一、起点:x86硬件架构。路口的指示牌——物理地址

我先从脚下的x86硬件架构说起吧。我们站的地方,CPU的角度,就有一个核心的地址概念,那就是“物理地址”,有的时候又叫“系统地址”。


一说起地址,好多人总是先将地址这个概念和主存(内存条/DIMM/DRAM)的空间联系起来。(这里不讲制造工艺,所以“内存条”,DIMM和DRAM在这里等同)。
其实我们不妨将目光的范围放大一些:其实物理地址/系统地址是标识整个计算机“空间”的(这里不包括IMC内部的地址空间,和IO port 地址空间)。

而我们习惯说的主存的空间,是计算机“整体空间”的一部分。当然,主存的空间,是用户人最容易直接感知到的空间,但主存的空间绝对不是计算机“空间”的全部。除了物理内存的空间以外,重要的还有PCI(e)空间(pci和pcie不分了)、bios空间(ROM和ram就不分了)等等。

而物理地址这个概念,咱先从Intel的文档里讲起。
引用Intel的eds原文:
The Skylake Server is thenext generation of 64-bit, multi-core server processor builton 14-nm process technology.The processor supports up to 46 bits of physical address space and 48 bits of virtual address space.

——skylake CPU,支持46位物理地址,48位虚拟地址。那么我们私下说,其实现在的“x86_64位架构” 的说法都不算严格,严格来说应该叫“32位enhanced”才对:
1、Intel的CPU uncore-module的 CSR目前还都是32位的。
2、CPU中64位的MSR,命名还保持者IA32_的开头。
3、目前的寻址,物理达到46位,虚拟达到48位。

物理地址,是bios(其实正规文档中说的是firmware,但是我认为就是bios)盘整了一遍计算机硬件后,将所有硬件的存储空间(不算硬盘的存储空间。在“主机架构”的眼里,总把硬盘当作没有地位的通房大丫头)进行了统一的编址,这个编址就是物理地址。

Intel Purley平台整个物理地址的样子,Intel的文档中给了下图:



(仔细看看这个图我们可以发现,在某个架构实现之初,就连专家的眼光都是局限的:专家认为不可能超越的界限,几年后却被轻易的超越了,好尴尬啊。然后为了兼容,后面的架构更新,就不得不像打补丁一样对老架构缝缝补补,直到被起点更高的、重新设计的新架构替代)


就如上面这幅图,当物理地址从传统的32位向现代64位扩展以后,有些尴尬了:
1、传统的pcie的地址空间卡在了高位(地址>32位)内存空间和低位内存空间之间。
2、高位(地址>32位)内存空间又卡在了高位的pci的地址空间(基本都是GPU计算)和传统的pci地址空间之间。
对于恨不得天天进行碎片整理、有技术强迫症的人,上面这幅图实在令人泪奔。
然后,整个物理地址的各个部分(内存部分,pci(e)部分,bios部分等等)的起始、结束地址,又被写回到CPU里的 addr region寄存器组里,这些addr region寄存器组所记录的关键的起始、结束地址,组成了CPU物理地址decoder所依据的数据结构字典。


二、从起点:intel x86 CPU架构,向下走:从物理地址到某一类硬件模块




我们跟随着CPU的逻辑,拿着物理地址,去找寻对应的硬件:

1、若是物理地址指向了内存
该物理地址是代表了物理内存还是PCIE卡?
——喔,根据addr region寄存器组,decoder判断这个物理地址是内存的地址空间。

该地址在哪个硬件上?
——喔,根据SAD/TAD解析规则 + 内存interleaving规则 + UMA/NUMA算法规则,物理地址落到了CPU 某个 IMC(内存控制器)的某个channel,然后落到per-channel地址。

迄今为止,笔者也只能将物理地址手撸到 内存channel编号(也就是能将物理地址所代表的故障定位到一个channel)。而接下来解析per-channel地址,乃至rank地址,内存column/raw地址,这是专业内存领域的范畴了,我还没有走到。
(此处可以单写一个专题:物理地址到物理内存的映射解析)
网上有类似的文章:

publ_2017_hillenbrand_xeon_decodingfor system addr.bdf,
俩老外拿着一台服务器哼哧哼哧的换内存条以及dump,硬把Intel上一代grantley 平台的物理地址映射到内存条的算法和数据结构给反向分析出来了,niubility!!!!
呃,我也同样傻过,我也读过skylake CPU的addr region寄存器里的数据结构:缺省情况下,内存物理地址在硬件间的interleave粒度是256B......数据太多就不啰嗦了。

我们回头返回到起点吧。

2、若是物理地址指向了PCI
该物理地址是代表了物理内存还是PCIE卡?
——喔,根据addr region寄存器组,decoder判断这个物理地址是PCI的地址空间。
该地址在哪个硬件上?
——喔,根据mmcfg 找到对应的pci的 bus地址:bus:dev:fun格式的地址和 mmio空间。这个bus地址才是pci设备真正的位置标识啊。(其实有些pci还有另外一种地址,叫ioport地址。这个ioport地址不属于物理地址的范畴,是属于另外一个空间,这里不多说了)
对pcie mmio空间的访问,即是对PCIE设备的访问。同时CPU的IOMMU 模块又会将mmio空间 和所分配的物理内存buffer连接起来形成dmar表,为DMA功能服务。
(此处可以单写两个专题:1、物理地址到PCI设备的映射解析2、PCI三大地址空间)


我们回头返回到起点吧。



三、从起点:x86硬件架构,向上走:从物理地址到操作系统


物理地址是硬件资源可访问空间的系统编址,所以物理地址直接代表着硬件资源,Intel文档中将物理地址又称为系统地址。
在以前的技术贴中,我曾经将bios比作开天辟地的盘古大神。当bios完成所有的硬件编排工作以后,就藏到角落里修仙去了,而操作系统接过管理的指挥棒。
到了操作系统的领域,物理地址又叫实地址。
操作系统在引导的初始阶段,基本内核会使用物理地址来进行boot和引导(那时的世界还很简单),此时又叫实模式。
等到了操作系统启动下一阶段,就开始使用另外一套地址空间了——也就是虚拟地址所代表的虚拟空间,此时就叫保护模式(世界开始变得复杂了)。
也许是因为资源实在有限,总也满足不了软件的需求吧,或许是因为软件总是有好有坏,坏的家伙会威胁到整个系统。所以当操作系统完全启动以后,我们能看到的操作系统部分,以及之上的软件都使用另外一套地址体系:虚拟地址,不再和硬件资源发生直接联系了。
以Linux为例,每个软件执行个体:线程,它的运行分为用户态和内核态。
用户态就是指线程在自己的用户地址空间里执行着自己的运算代码。

(喔,这里可以再写一个专题:linux进程空间分布)


而内核态就是指线程通过系统调用申请了硬件资源,这个申请在操作系统内核的处理过程。
而无论用户态还是内核态,我们通过工具所抓取到的地址都是用虚拟地址标识的。


例如我们用C语言写了一个程序,printf一个合法的用户空间的指针变量,打印出来的地址,是用户空间的虚拟地址。



又例如我们通过探针或者dump分析看到的内核中的ffffffff8110cb3d这样的形式,这是内核空间的虚拟地址。


当然上图表示的是内核代码段的地址,而内核的数据段又是另一个地址段了。

用户空间的虚拟地址更“虚”,甚至有些虚拟地址还对应不到物理地址上(以内存硬件资源为例)。就以内存为例,这就是Linux系统所谓的内存空间懒分配原则:
按照线程的指令需求,系统分配器给线程分配了一段虚拟地址空间(例如语句定义了一个数组);
但这段虚拟地址空间不会立刻映射到物理地址(内存)上,此时也就没占用内存物理资源;
直到这段虚拟地址空间需要写入数据的时候(例如数组赋值),系统才真正分配、对应到物理地址所代表的内存空间上。
这种懒分配的方式,放到存储上或者云系统上,很像所谓的thin provision。
(这里可以再写一个专题:linux空间分配、缺页中断以及page映射机制)

若是虚拟地址已经映射到了以page为单位物理地址,那么从虚拟地址到物理地址的翻译,却需要从虚拟地址开始,通过内存中的pdt表,进行一级二级三级四级五级等多级映射,才能获得物理地址。所以有些文档,将用户空间的虚拟地址称为线性地址。



(这里可以再写一个专题:线性地址到实地址的转换。)

而内核空间的虚拟地址相对“”实”一些,内核虚拟地址减掉一个固定的offset偏移量,一步到位就得到了物理地址。所以有些文档将内核虚拟地址称为段偏移地址或者称为直接映射地址。



(这里可以再写一个专题:linux 32位和64位系统空间详解)
那么到底是谁会负责操作上文说的“虚拟地址到物理地址的翻译”?
答案是:CPU内部的MMU module。
于是,顺着来时的路向前走,我们没有回头,从高层面的操作系统和软件继续向前走,沿着代码执行的这条路,却回到了硬件最底层的CPU。



轮回啊……

四、从操作系统回到CPU,聊聊MMU和三级cache。

整个的路程画了个大圈圈。



Skylake CPU内有三级缓存。L1 又叫FLC,分为DCU,ICU;L2又叫MLC;L3又叫LLC.
CPU core在load和执行新程序语句的时候,靠近处理核心的L1 cache里面,获取的地址变量(语句带的)都是虚拟地址。
当CPU core需要对该地址进行寻址、做数据操作的时候:
首先要通过MMU实现虚拟地址到物理地址的翻译;
再通过addr decoder进行物理地址到硬件的寻址。



为了加速以上虚拟地址到物理地址翻译的过程, CPU里出现了TLB缓存:将曾经用过的地址翻译条目放在这个缓存中。——我们就有可能直接通过虚拟地址拿到物理地址,就不用再多级映射翻译了。
为了加速数据读取/写入的速度,CPU里设计了三级cache。——我们就不用每次都访问主存来获取数据了,以前曾访问过的热数据直接就从CPUcache中获取了。



看看下图CPU cache与主存的访问延迟对比吧



(这里又可以写两个专题:CPU cache实现和MMU工作原理。)

所以这一路走来,就是个轮回,从x86架构到操作系统,又从操作系统的指令执行回到了CPU,画了一个圈又回到了起点。


五、写在本篇最后
本篇所有技术内容,都是自学总结的。自学啊自学,龟速又很可能发生错误。

玩了两年年的x86,潜心看了看Intel CPU架构和linux内核。最终形成了本篇世界观。

我的世界观,毕竟是自己的理解和总结,错误总会有,但至少我努力的从世界是什么样的角度思考着世界为什么这样。

本文中所有经过的地方,其实都是这两年踩过的技术点。为了尽快的形成一个知识小体系,好多地方踩的不深,留下了许多没填坑。
写下此篇技术总结,也算整理了这两年的技术体系总纲。数了数有多少坑,好像这一路坑不少,未来努力,填完。
最近在看Gregg的走向性能之巅性能调优的书,发现dtrace工具似乎和centos兼容性不太好,又得将书中dtrace语句翻译成systemtap语句,又是个超级大坑。

本文图片借用了几张 the truman show的海报,怀念当年,懵懂的看着世界是什么样的青春。


另外,更多未更新的知识内容,请关注公众号:雪瀑牵裳的树屋,那里有intel x86 cpu架构,有linux内核,还会有openstack,openshift等云计算方面的内容。


编辑于 2020-05-31 17:17

「真诚赞赏,手留余香」

赞赏

还没有人赞赏,快来当第一个赞赏的人吧!

x86


CPU 指令集


ARM 架构


发布一条带图评论吧


5 条评论

默认

最新

花满楼

"呃,我也同样傻过,我也读过skylake CPU的addr region寄存器里的数据结构:缺省情况下,内存物理地址在硬件间的interleave粒度是256B......数据太多就不啰嗦了。"-----这个有解析过程吗,求文档

2022-06-28

回复喜欢

L-indica

讲的好棒啊

2021-10-29

回复喜欢

Mr.Z

写得好棒啊!

2021-04-29

回复喜欢

仕明

同时CPU的IOMMU 模块又会将mmio空间…
//这里应该是笔误吧,iommu是属于pci设备的

2021-01-29

回复喜欢

ZZ ZZ

讲得真好

2020-09-25



【知乎】x86架构&linux内核系列(五-六)的评论 (共 条)

分享到微博请遵守国家法律