Wednesday, December 30, 2009

查找X86_64下内核代码段物理地址的方法

查找X86_64下内核代码段物理地址的方法

一. 目标: 我想找出X86_64 LINUX 2.6.18的内核代码段的物理地址. 找出这个地址有什么用呢? 一般人是用不到的. 不过如果你知道什么是DMA ATTACK. 同时又想用这个方法来攻击X86_64 LINUX的话, 你也许就会用到内核的物理地址了.

二. 方法:

错误的方法:

a. 在最开始的时候, 我一下子就想到了内核里面提供的VIRT_TO_PHYS函数. 看它的名字,就是把虚拟地址转成物理地址的. 同时,内核代码段的虚拟地址很容易获得,直接 grep _text System 就可以了.

但是当我按照上面的思路写了一个kernel module之后,发现得到的地址明显不对. 因为得到的物理地址已经超过了我机器上已经插的内存条的总量. 后来仔细看了下VIRT_TO_PHYS的说明,发现它只是对KMALLOC() 分配的内存有效. 不过在X86-32 体系下, 它好像是可以用在内核代码段上的了 .

不管怎么说,此路不通. 那么只好用其他办法了. 在大概20多天的时间里,我一直没找到好的方法, 直到今天偶然看到了ZX_WING 大侠的这篇文章: http://linux.chinaunix.net/bbs/thread-1032711-1-1.html
(btw:同时要感谢把这篇文章顶起来的兄弟)

正确的方法:

b. 看了ZX_WING老大的LINKER SCRIPT一文之后, 你就会发现, 内核加载的物理地址是在LINKER SCRIPT里面指定的. 那么接下来就是看这个SCRIPT了.
c. 首先找X86_64的连接文件, 在2.6.32里面的时候,X86 32 和64的已经合并成一个文件了. 不过在我用的2.6.18里面还是分开的2个文件. 他的位置在ARCH/KERNEL/X86_64 (或者X86)/下面.
d. 下面是其中的一小段:
http://lxr.linux.no/#linux+v2.6.18/arch/x86_64/kernel/vmlinux.lds.S
SECTIONS
17{
18 . = __START_KERNEL;
19 phys_startup_64 = startup_64 - LOAD_OFFSET;
20 _text = .; /* Text and read-only data */
21 .text : AT(ADDR(.text) - LOAD_OFFSET) {

明显用到了宏__START_KERNEL, 那么就把它相关的找出来吧. 分散在几个.H文件里面, 如下:#define __PHYSICAL_START CONFIG_PHYSICAL_START
#define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START)
#define __START_KERNEL_map 0xffffffff80000000UL
#define LOAD_OFFSET __START_KERNEL_map
关于CONFIG_PHYSICAL_START的说明如下:
http://lxr.linux.no/#linux+v2.6.18/arch/x86_64/Kconfig#L493
config PHYSICAL_START
494 hex "Physical address where the kernel is loaded" if (EMBEDDED CRASH_DUMP)
495 default "0x1000000" if CRASH_DUMP
496 default "0x200000"
497 help
498 This gives the physical address where the kernel is loaded. Normally
499 for regular kernels this value is 0x200000 (2MB). But in the case
500 of kexec on panic the fail safe kernel needs to run at a different
501 address than the panic-ed kernel.

从上面可以看出PHYSICAL_START 的默认地址是 0X200000. 同时KERNEL 代码的起始虚拟地址是0XFFFFFFFF80000000. 那么这2个地址对应的内容应该是一样的. 只不过前者是物理地址,后者是虚拟地址. 最后, 装个X86_64的QEMU虚拟机验证下(我用的是QEMU版本是0.11.91,老的版本,比如0.9.x, 0.10等好像支持的不好, 我总是装不上X86_64版本的CENTOS 5.1).按ALT+CTL+2, 切换到QEMU MONITOR, 然后输入下面2个命令:
x /10x 0xffffffff80000000
xp /10x 0x200000
发现内容是一样的. 证明前面的推理完全正确.

不过还是有个地方不太明白:
从下面三行来看:.
= __START_KERNEL;
19 phys_startup_64 = startup_64 - LOAD_OFFSET;
20 _text = .;
_text的其实地址似乎应该是__START_KERNEL, 也就是0XFFFFFFFF80000000+0X200000 = 0XFFFFFFFF80200000. 但是我用GREP _TEXT 得到的结果是0XFFFFFFFF80000000. 没有用到PHYSICAL_START. 不知道为什么会这样? 哪位可以解释下? 多谢

Sunday, December 20, 2009

INTEL手册中,APIC部分阅读笔记

INTEL手册中,APIC部分阅读笔记

以前看手册的时候没仔细看APIC这部分,最近要用到,所以又看了下。几分钟前刚看完,趁热打铁,把有印象的东西记录一下。免得自己以后忘记,也希望对新手有所帮助。不对的地方也欢迎指正。


参考资料:
INTEL手册的下载连接看这里:
http://biosren.com/thread-92-1-1.html
APIC部分在第三册A的 CHAPTER 10.

正文:
APIC的全写是Advanced Programmable Interrupt Controller,用来管理中断的。注意不要和ACPI(Advanced Configuration and Power Interface电源管理)搞混。APIC的前身是PIC,比如8259A.不过现在已经很少用了。

APIC实际上分成2类。一类叫LOCAL APIC,直接连在PROCESSOR上,每个PROCESSOR一个。还有一类叫I/O APIC,用来管理外设过来的中断,一般一个机器里面就一个(即使是多核系统)。在INTEL IA32手册中讲的是LOCAL APIC,下面说的APIC都是指LOCAL APIC。所谓的LOCAL,应该是相对PROCESSOR而言的,因为离PROCESSOR比较近,所以叫LOCAL.

LOCAL APIC可以处理以下的中断来源:
1)本地相连的I/O设备。比如直接连在LINT0,LINT1管脚上的设备。不过我不知道一般什么样的设备是这么连的。
2)外部的I/O设备。这些设备产生的中断先经过I/O APIC,然后再通过LOCAL APIC到达处理器。
3)Inter-processor interrupts (IPIs) 处理器之间的中断。现在多处理器结构已经很常见了。当一个处理器想中断另外一个的时候,就可以用IPI。
4)APIC定时器中断。APIC上自带了定时器,这个在OS中也是很常用的。
5)Performance monitoring counter interrupts,性能监视计数器中断。看来INTEL还是很替软件开发人员考虑的,直接在硬件上作了个性能监视计数器。
6)温度传感器中断。估计是防止CPU温度过高用的。在PENTIUM 4 AND XEON的处理器上有。
7)APIC内部错误中断。

APIC可以看成是一个独立的硬件,有自己的一堆寄存器,叫做local vector table or LVT。可以进行读写来控制APIC的某些特性和设置。

APIC实际上又可以细分为3个版本。(这里插一句,以前只觉得软件版本特别多,对硬件没啥很深的感触。现在看看INTEL的东西,版本也不少。)最早的P6家族用的是APIC。PENTIUM 4 AND XEON 用的是xAPIC,然后还有个x2APIC。以后不知道是不是再出什么x4APIC OR x8APIC。

检测APIC版本可以用CPUID指令。

在xAPIC模式中,它的寄存器是通过内存映射的方式映射到一段物理地址。有一个默认值。为了防止同其他地址冲突,这个基地址又可以重新指定到另外一个地方。估计BIOS开发人员在对付APIC时会用到这个功能。

在x2APIC模式中,取消了内存映射方式来读取APIC的寄存器,而是采用了MSR的方式。MSR的全写是Model-specific register。也就是每个型号特有的寄存器。这样的好处是不用再担心内存地址的冲突问题。

不同的APIC模式的开启,关闭以及状态切换之间又有一些规则。如果你要自己改这些地方,就要按照规定来。同时注意在x2APIC模式下,写入寄存器的时候不保证顺序,所以要自己小心,比如用个BARRIER之类的。

最后说下MSI(MESSAGE SIGNALLED INTERRUPTS). 在PCI 3.0中已经有了MSI,不过是可选的。到了PCI EXPRESS,这个就变成必须支持的了。实现的方式主要靠2个寄存器:a Message Data Register(MDR) and a Message Address Register(MAR)。当要发送MSI的时候,PCI设备往MAR写一个MDR规定好的数据即可。具体的含义可以看手册。

PCI EXPRESS 和 (传统)PCI 的简略区别

PCI EXPRESS 和 (传统)PCI 的简略区别

术语表:
PCIe: PCI EXPRESS
PCI: 传统 PCI.

简介:
我以前做过PCI BUS的一些东西,后来停了一段时间,错过了PCI-X.现在又开始要做外设了,于是开始看PCI EXPRESS. 在看PCIe SPEC的时候,有意识的把新的PCIe和PCI比较.这样加深了解. 下面的都是个人观点,适合那些从PCI直接转到PCIe的人.本人水平有限,文中或许有不对的地方,欢迎大家指正.

1) LAYERED DESIGN(分层式设计). PCIe很像网络,分成3层,从上到下为 TRANSACTION LAYER, DATA LINK LAYER, PHYSICAL LAYER. PCI是不分的. 分层设计的好处是灵活.比如每一层可以有不同的协议实现. 同时只要2层的接口没变,那么改下层的实现的时候,可以不改上层. 从PCI EXPRESS 1.0 到3.0 (还没正式出), 每LANE的速度每次都翻一倍.估计其中也有分层设计带来的便利.另外,我觉得因为PCIe采用了串行总线,而不是象PCI那样用并行总线,这应该也是导 致网络式设计的一个重要原因.

坏处:实现起来比较复杂?

一点感想:刚看PCI EXPRESS规范的时候,差点以为错下了一个讲网络的文件.因为里面充斥了网络术语,比如3个分层,PACKET,SWITCH等. 这点很出乎我的意外.不过仔细想了下,这样设计确实有上面说的好处.以前还和一位老师讨论过直接在网卡上做流量控制,错包重发等.那时他的结论就是如果这 样做了,最后就变成了TCP/IP. 没想到PCI EXPRESS差不多也是这样做的.意外啊意外.


2) ADDRESS SPACE(地址空间). PCIe有5个ADDRESS SPACE. PCI有3个.PCIe除了继续保留PCI中的IO,MEMORY, CONFIGURATION外,又加了2个.那就是 TRUSTED CONFIGURATION 和MESSAGE. 看来现在安全设计越来越重要了. MESSAGE 那个估计就是为了取消中断引脚的(这样可以降低成本).

Wednesday, December 9, 2009

A problem when insalling Xen kernel devel package on CentOS5

Problem: I want to compile kernel modules on Dom0 of xen. So I installed xen development package using following command:
yum install kernel-xen-devel
However, after that, I still cannot compile the module. It turns out that the version of kernel-xen-devel is the newest (2.6.18-164.6.1) one while my kernel is the old one (2.6.18-128). It seems that the yum just insalled the newest package regardless of my current kernel.

Solution: install the new xen kernel (or update, but it takes a lot of space)

yum update kernel-xen

Monday, December 7, 2009

调试KERNEL时,找出当前进程信息的方法

我们知道,一个常用的内核数据结构叫做 current. 它指向当前的进程,包括很多有用的信息,比如PID, 进程名等。如果是自己写内核代码,那么可以直接引用current。 但是如果是调试的话,直接p current是不行的。因为current是一个宏。

那么这时如何找出current呢?在Linux 2.6版本中,current是放在堆栈(STACK)的最低下。假如内核栈是8K。起始地址为0XC100,2000. 那么current就在0XC100,0000 (堆栈是倒着长的). 根据这个情况,可以用如下方法找出current。
1)找出ESP。p $esp. 假设输出为0XC123,4566
2) 把esp与上0XFFFF,E000 (低13位置为0)。 得到0XC123,4000. 这个值就是CURRENT的值了。可以转换后进行使用,比如
3) p ((* struct thread_info) 0xc1234000)->task->comm

以上方法在X86-32, ARM上都可以。 其他体系结构不太清楚,似乎也可以。或者那位知道的可以补充。

Sunday, December 6, 2009

printk format for 64bit

---------------------------------------------------------
int %d or %x
unsigned int %u or %x
long %ld ot %lx
unsigned long %lu or %lx
long long %lld or %llx
unsigned long long %llu or %llx
size_t %zu or %zx
ssize_t %zd or %zx

ref:
http://groups.google.com/group/linux.kernel/browse_thread/thread/2ea0571f2f5a72da/22a6f3295e25688b?lnk=raot&pli=1