Rick's Home Never too late to learn
Windows internal: Memory 2
Posted by Rick at 2014-03-22 with tags windows
地址转译
x86虚拟地址转译
- CR3寄存器,页目录物理基址
- 标准非PAE,两级页表: 页目录索引(10位)、页表索引(10位)、字节索引(12位)
- PDE: 每个PDE表有1K个表项,每项4字节
- PDE: 每个PTE表有1K个表项,每项4字节
- PAE,三级页表: 页目录指针索引(2位), 页目录索引(9位)、页表索引(9位)、字节索引(12位)
- PDE: 每个PDE表有512个表项,每项8字节
- PDE: 每个PTE表有512个表项,每项8字节
在非PAE模式下的地址转译
-
!vtop PFN VirtualAddress, 最快捷的方式
首先获取PFN, 有两种方式, 从!process中查看DirBase, 或用r cr3查看, PFN就是cr3中的值右移12位 0: kd> r cr3 cr3=00185000
0: kd> !vtop 185 845ecf68 X86VtoP: Virt 845ecf68, pagedir 185000 X86VtoP: PDE 185844 - 001c4063 X86VtoP: PTE 1c47b0 - 045ec121 X86VtoP: Mapped phys 45ecf68 Virtual address 845ecf68 translates to physical address 45ecf68. // 比较虚拟地址内容及物理地址内容,完全一样,所以是正确映射的 0: kd> !dd 0x45ecf68 # 45ecf68 fefc45c7 6affffff 56006a01 046a59e8 # 45ecf78 c0efe800 08c2ffe6 ec458b00 8be44589 0: kd> dd 845ecf68 845ecf68 fefc45c7 6affffff 56006a01 046a59e8 845ecf78 c0efe800 08c2ffe6 ec458b00 8be44589
-
!pte, 直接获得PTE内容,然后计算物理地址
0: kd> !pte 845ecf68 VA 845ecf68 PDE at C0300844 PTE at C02117B0 contains 001C4063 contains 045EC121 pfn 1c4 ---DA--KWEV pfn 45ec -G--A--KREV //The number 0x45ec is the page frame number (PFN) of this PTE physical address = 0x45ec*0x10000(4k, 页大小) + 0xf68(页偏移) = 0x45ecf68
-
manual convert
0: kd> .formats 845ecf68 Evaluate expression: Binary: 10000100 01011110 11001111 01101000 Page directory index = 0y1000010001 = 0x211 Page table index = 0y0111101100 = 0x1ec Byte index = 0y111101101000 = 0xf68 // 查看PTE内容, 两种方式 1. 计算PTE的虚拟地址 PTE virsual address = PTE_BASE (On a non-PAE system, this is 0xC0000000) + (page directory index) * PAGE_SIZE (0x1000, 4K) + (page table index) * sizeof(MMPTE) (This is 4 bytes on non-PAE x86 systems) = 0xc0000000 + 0x211 * 0x1000 + 0x1ec * 4 = 0xC02117B0 0: kd> dd C02117B0 l1 c02117b0 045ec121 pnf = 0x45e 2. 计算PTE的物理地址 0: kd> r cr3 cr3=00185000 PDE physical address = 0x185 * 0x1000 + 0x211 * 4 = 0x185844 0: kd> !dd 0x185844 L1 # 185844 001c4063 PDE content: pfn -> 0x1c4 PTE physical address = 0x1c4 * 0x100 + 0x1ec * 4 = 0x1c47b0 0: kd> !dd 0x1c47b0 L1 # 1c47b0 045ec121 PTE content: pfn -> 0x45e // 计算物理地址 physical address = pnf * 0x1000 + byte index = 0x45e000 + 0xf68 = 0x45ef68
在PAE模式下的地址转译
- !vtop 失败,这个方法不好用
-
!pte
1: kd> r cr4 cr4=000406f9 bit 5:1 PAE开启 1: kd> !pte 84e13a68 VA 84e13a68 PDE at C0602138 PTE at C0427098 contains 00000000039C1863 contains 000000007D413963 pfn 39c1 ---DA--KWEV pfn 7d413 -G-DA--KWEV //The number 7d413 is the page frame number (PFN) of this PTE physical address = 0x7d413*0x1000(4k, 页大小) + 0xa68(页偏移) = 0x7d413f68 1: kd> dd 84e13a68 84e13a68 00d00003 00000000 84ebc3f8 00000000 1: kd> !dd 7d413a68 #7d413a68 00d00003 00000000 84ebc3f8 00000000 转译成功
-
manual convert
1: kd> .formats 84e13a68 Binary: 10000100 11100001 00111010 01101000 Page directory pointer index = 0y10 = 0x2 Page directory index = 0y000100111 = 0x27 Page table index = 0y000010011 = 0x13 Byte index = 0y101001101000 = 0xa68 计算PTE的虚拟地址 PTE virsual address = PTE_BASE (this is 0xC0000000) + (page directory pointer index) * (PAGE_SIZE * PAGE_ENTRY_NUM) (0x1000, 0x200)(页大小为4k,一个页目录中页表项占8字节,共有512个页表项) + (page directory index) * PAGE_SIZE (0x1000, 4K) + (page table index) * sizeof(MMPTE) (This is 8 bytes on PAE x86 systems) = 0xc0000000 + 0x2 * 0x1000 * 0x200 + 0x211 * 0x1000 + 0x13 * 8 = 0xC027098 1: kd> dd C0427098 l1 c0427098 7d413963 pnf = 0x7d413 pyhsical address = pnf * 0x100 + Byte index = 0x7d413a68
x64虚拟地址转译
使用四级页表方案, 手动比较麻烦, !vtop不靠谱,最好用的还是!pte
1: kd> !PTE fffff803`5b2be43c
VA fffff8035b2be43c
PXE at FFFFF6FB7DBEDF80 PPE at FFFFF6FB7DBF0068 PDE at FFFFF6FB7E00D6C8 PTE at FFFFF6FC01AD95F0
contains 0000000000384063 contains 0000000000345063 contains 000000000034D063 contains 00000000020BE121
pfn 384 ---DA--KWEV pfn 345 ---DA--KWEV pfn 34d ---DA--KWEV pfn 20be -G--A--KREV
只关心PTE的pfn即可, pnf = 0x20be
pyhsical address = pnf * 0x1000 + 0x43c = 0x20be43c
1: kd> !dd 0x20be43c
# 20be43c f8b60f44 c89f8b48 0f000000 0b7477ba
1: kd> dd fffff803`5b2be43c
fffff803`5b2be43c f8b60f44 c89f8b48 0f000000 0b7477ba
可以看到虚拟地址内容与物理地址完全相同,是映射关系
====================================================================
以上看到,最好的地址转译方式是用!pte, 只关心PTE的pnf即可
使用到的windbg命令
- !pte [virtual address] 查看PDE和PTE内容
- d用来查看虚拟地址下的内容,!d用来查看物理地址下的内容
- !vtop windbg提供的地址转译命令,但是好像只有在x86且non-PAE模式不才好用
- .formats 以不同的进制输出数据
- r [cr*] 显示特定寄存器中的值
Windows internal: Memory 1
Posted by Rick at 2014-03-21 with tags windows
Keywords
-
PTE: Page Table Entry 系统页表项 利用系统页表项,可以动态地映射系统页面。每个虚拟地址都跟一个系统空间中的结构联系在一起, 该结构被称为PTE,其中包含了该虚拟地址所对应的物理地址。
-
PDE: Page directory entries 页目录项 每个PDE是4字节长,页目录描述了该进程所有可能的页表的状态和位置
-
PAE: Physical Address Extension 物理地址扩展 Intel x86 Pentium Pro处理器引入的一种内在映射模式,通过适当的芯片集,PAE模式可以使得当前的 Intel x86处理器上可以访问多达64GB的物理内存,x64处理器上可以访问多达1024GB的物理内存。
-
PFN: Page frame number 页面帧编号
Windows内存管理器
它是执行体的一部分,位于ntoskrnl.exe中
主要任务
- 将一个进程的虚拟地址空间转译或者映射到物理内存中
- 当内存过度提交时,将内存中的有些内容转移到磁盘上;并且在以后需要这些内容时,再将它们读回到物理内存中
核心服务
- 内存映射文件
- 写时复制
- 为应用程序使用大的、稀疏的地址空间提供支持
- PAE
共享内存与写时复制
- 例如两个进程使用了同样的DLL, 只需要将引用该dll的代码页面加载到物理内存中一次,然后所有映射了些dll的进程之间共享这些页面
- 每个进程仍然维护它的私有内存区域,在其中存储私有数据,但是程序指令和非修改的数据页面可以被共享。其中可执行映像中代码页面是以“仅可执行”的方式映射,可写的页面被映射为“写时复制”
写时复制
- 定时复制页面保护机制是一种优化,内存管理器利用它可以节约内存
- 写时复制是“延时计算”,使得只有当绝对需要的时候才执行一个昂贵的操作,如果该操作不需要的话,就不会浪费时间
- Unix系统中的fork函数就是很好的例子
- 应用: 两进程共享dll, 调试器中实现断点
页面(默认4K, 0x1000)
- 虚拟地址空间被分成以页面为单位
- 页面是硬件级别上的最小保护单位, 也是内存管理器操作的最小单位, 更小单位的分配操作要用堆管理器
堆管理器, heap manager
- 负责管理大内存区域中的内存分配,这些大的内存区域是通过一些页面粒度的内存分配函数来保留的。堆管理器的分配粒度相对较小:32位系统上为8字节,64位为16字节
- Windows API有专门的堆函数Heap*, 如HeapCreate,它从内存管理器中得到一块大的内存区,用于堆分配.
- 每个进程至少有一个堆(默认的进程堆,可以通过GetProcessHeap来获得), 也可以调用HeapCreate显示的创建额外的私有堆
- 堆管理器是一个两层结构:可选的前端堆层和核心堆层
- 核心堆层:处理基本功能,包括段内部的内存块的管理、段的管理、堆的扩展策略、提交内存和解除提交,以及大块的管理
- 可选的前端堆有两种类型:预读列表(look-aside list)和低碎片堆(LFH, Low Fragmentation Heap)
- 预读列表
- LFH
- 堆调试工具: pageheap
系统堆, 也称为系统内存池
- 进程中的内存分配主要由堆管理器来分配,但内核组件的分配有单独的内存池
- 系统初始化时,内存管理器创建了两种类型的动态大小的内存池,内核模块组件从这两种内存池中分配系统内存
- non-paged 非换页池, paged 换页池
- API: ExAllocatePoolWithTagExAlloc, ExFreePoolWithTag
- Windows也提供了一种快速的内存分配机制为内核模块使用:Look-Aside List
- 一般内存池分配操作可以是不同大小的,而预读列表只包含固定大小的内存块
- Look-Aside List分配速度更快,而一般内存池分配更灵活
- API: ExInitializeNPagedLookasideList, ExInitializePagedLookasideList
- 查看系统的预读列表: !lookaside
用于扩展进程的内存使用空间的方法
- windows32中每个进程默认最大能使用2GB虚拟地址空间(可能设为3GB)
- AWE(address windowing extensions), 由windows提供的一组API实现
- PAE(physical address extension)
Windows上查看内存信息的工具
- 任务管理器中的Performance
- Pmon.exe
- Pstate.exe
- Poolmon
- 内核调试器中!vm, !memusage, !poolused
The Linux Kernel Mode Programming --- Part3
Posted by Rick at 2013-10-27 with tags Linux
Modules VS Programs
Begin and End
应用程序开始于一个main()函数,执行一些指令后就结束。 模块的工作方式略有不同。一个模块开始于init_module()或是由宏module_init()指定的初始化函数。这个函数是模块的入口函数,它告诉内核该模块提供了哪些功能并使内核在需要的时候调用模块的功能函数。该入口函数执行完成时就退出,内核只有在需要的时候才会调用模块提供的代码。 所有的模块在从内核卸载时调用cleanup_module或宏module_exit()指定的退出函数。它会反注册掉入口函数提供到内核的功能函数
Functions available to modules
模块不能调用标准I/O库,只能调用由内核提供的函数,内核的导出函数可以查看/proc/kallsyms
系统调用运行在内核空间,由内核提供。标准库函数运行在用户空间,提供比系统调用更为方便的接口, 当完成一项具体功能时,标准库中的函数会调用相对应的系统调用。 可以用strace来查看程序中调用了哪些系统调用。
User Space VS Kernel Space
在Windows中,称为User Mode和Kernel Mode, 意义相同。 CPU能够运行在不同的模式中,在Intel IA32体系结构中有4种模式,ring 0、1、2、3。Unix和Windows都使用了其中的两个ring,ring0(supervisor mode)和ring4(the lowest ring)
Name Space
当写内核代码时,即使最小的模块也会链接到整个内核。如果一个模块导出的变量与另一个模块导出的变量相同,就会引起命令冲突。这种问题的解决方法是将你的变量都定义成静态的并且所有的符号都使用良好定义的前缀。内核使用的前缀都是小写的。如果你不想将所有符号都定义为静态的,另一种选择是声明一个符号表,并在内核中注册它。
Code Space
应用程序使用虚拟地址空间,由内存管理器分配并管理,将虚拟地址映射到实际的物理地址。任何两个程序使用的地址空间是相互独立的。
内核也有自己的内存地址空间。因为模块可以动态的加载或卸载到内核中,它拥有与内核相同的地址空间,而不是有独享的空间。所以如果你的模块发生段错误,整个内核就会发生段错误,这是非常危险的。
Device Drivers
设备驱动程序是一种类型的模块,向内核提供访问硬件的功能。在Unix系统中,每个硬件设备由/dev中的一个文件表示。
每个硬件设备由一个主设备号和次设备号来表示。主设备号表示哪种驱动可以用于访问该硬件,每个Driver都被赋于一个唯一的主设备号。拥有相同主设备号的设备文件(设备)由相同的驱动程序来管理。 设备驱动程序通过次设备号来区分由它控制的不同的设备。 设备可以分成三类: 字符设备、块设备、网络设备 当系统安装时,设备文件由mknod命令来创建,创建新的字符设备文件
mknod /dev/coffee c 12 2
新的字符设备文件的主/次设备号为12, 2。
内核只关心设备的主设备号,它告诉内核要使用哪种设备驱动程序来操作该设备。内核不会使用次设备号。驱动程序中才会用到它,用于定位具体的设备。
The Linux Kernel Mode Programming --- Part2
Posted by Rick at 2013-10-27 with tags Linux
宏
在Linux2.4之后,引入了如下两个宏,在模块中就没有必要使用init_module()和cleanup_module(),而是可以自定义实现这两个功能的函数, part1中的模块可以改写为
#include <linux/module.h> //Needed for all modules
#include <linux/kernel.h> //Needed for KERN_INFO
#include <linux/init.h> //Needed for the macros
static int __init hello_2_init(void)
{
printk(KERN_INFO "Hello, world 2\n");
return 0;
}
static void __exit hello_2_exit(void)
{
printk(KERN_INFO "Goodbye, world 2\n");
}
module_init(hello_2_init);
module_exit(hello_2_exit);
- __init表示模块加载到内核时,首先执行hello_2_init()函数,执行完后就可以释放掉该函数所占的内存区,它在之后不会再用到。
- __exit表示模块加载到内核后,只有当该模块从内核中卸载掉时才执行函数hello_2_exit()
- module_init() 声明初始化函数init function
- module_exit() 声明清理函数cleanup function
- MODULE_LICENSE(“GPL”) 声明代码遵循的GPL协议
- MODULE_AUTHOR() 该模块是谁写的
- MODULE_DESCRIPTION() 该模块的简单功能描述
- more in linux/init.h
向模块传递参数
模块在加载的时候可以带有命令行参数,但和传统的程序中的argc/argv不同。
为了能向模块传递参数,模块要导出变量变量的名字以便加载模块的程序(insmod)可以看到, 要用到宏module_param()(defined in linux/moduleparam.h)
/*
* module_param(var, int, 0000)
* The first param is the parameters name
* The second param is it's data type
* The final argument is the permissions bits,
* for exposing parameters in sysfs (if non-zero) at a later stage.
*/
int myint = 3;
module_param(myint, int 0);
/*
* module_param_array(arr, type, num, perm);
* The first param is the parameter's (in this case the array's) name
* The second param is the data type of the elements of the array
* The third argument is a pointer to the variable that will store the number
* of elements of the array initialized by the user at module loading time
* The fourth argument is the permission bits
*/
int myinitarray[2];
module_param_array(myintarray, int , NULL, 0);
在加载模块时就可以使用如下命令
$ sudo insmod mymodule.ko myint=5
初识OpenGL
Posted by Rick at 2013-10-26 with tags OpenGL
什么是OpenGL?
OpenGL是图形硬件的一种软件接口,它的设计目标是作为一种流线型的、独立于硬件的接口,在许多不同的硬件平台上实现。 OpenGL并不是一种编程语言,而更像是一个C运行时函数库,提供一些预包装的功能,帮助开发人员编写功能强大的三维图形应用程度。
历史?
OpenGL的前身是SGI公司开发的IRIS GL图形函数库。SGI是一家久负盛名的公司,在计算机图形和动画领域处于领先地位。IRIS GL最初是一个2D图形函数库,后来逐渐演化为SGI的高端IRIS图形工作站所使用的3D编程API。后来,由于图形技术的发展,SGI对IRIS GL的移植性进行了改进和提高,使它逐步发展成如今的OpenGL。
术语
渲染(rendering): 计算机根据模型创建图像的过程 模型(model): 是根据几何图元创建的,也称为物体(Object) OpenGL渲染环境: 是OpenGL在其中存储状态信息的数据结构、渲染图像的时候要用到这些信息。 模式(profile): OpenGL 3.0引入该概念,是特定于应用程序领域的OpenGL功能集的子集
OpenGL函数
- 每个OpenGL函数的前缀为”gl”
- 组成函数的每个单词的首字母都用大写,如”glClearColor()”
- “glVertex3f()”, 3表示该函数接收三个参数,f表示参数类型为float。
OpenGL实用工具库(GLUT)
Install on Ubuntu12.04
$ sudo apt-get install build-essential
$ sudo apt-get install libgl1-mesa-dev
$ sudo apt-get intsall freeglut3-dev
The Linux Kernel Mode Programming---Part1
Posted by Rick at 2013-10-24 with tags Linux
什么是Kernel module?
内核模块是能够在需要的时候动态的加载或卸载的代码块,他们扩展了内核的功能而不需要重启系统。例如, 驱动程序就是一种类型的内核模块,允许内核访问连接到系统的硬件设备。如果没有内核,我们就需要重新编译内核并把新的功能加到内核映像中去,这会使得内核越来越大,且要求每次添加新功能时都要重新编译并重新启动系统。
模块是怎样加载到内核的?
查看已经加载内核模块
$ lsmod
例: 需要加载模块msdos.ko到内核,而msdos.ko信赖于fat.ko
手动加载内核模块, 使用命令insmod
$ sudo insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko
$ sudo insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko
智能方式加载内核模块, modprobe
$ modprobe msdos
可见,modprobe只需提供内核的名称就可以加载内核,而insmod要提供路径名及模块依赖关系。modprobe是如何工作的呢? * 首先, 如果内核需要有一个模块的功能但该模块还没有加载到内核中,则内核模块守护进程(服务)kmod调用modprobe来加载这个模块, 并提供该模块的名字msdos * 然后modprobe去查看/etc/modprobe.conf_看是否该模块是个别名。下一步,modprobe查看/lib/modules/version/modules.dep_, 查看模块的依赖,该文件是由depmod -a创建的包含了模块的依赖关系。 * 最后,modprobe使用insmod先加载fat.ko,并提供完整路径名,然后再加载dos.ko
The Simplest Module “Hello, World”
/*
* hello-1.c - The simplest kernel module
*/
#include <linux/module.h>
#include <linux/kernel.h>
int init_module(void)
{
printk(KERN_INFO "Hello world 1\n");
return 0; //non 0 means failed, module can't be loaded
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodby world 1.\n");
}
内核模块至少有两个函数 + start func: init_module(), 当模块被加载到内核时被调用 + end func: cleanup_module(), 当模块从内核中卸载时被调用 + 从内核2.3.13之后,这两个名字是可以改变的,可以指定任意的名字
每个模块都要包含linux/module.h, 当要用到printk()时要包含linux/kernel.h
printk
printk是内核的log机制,目的是记录信息或是给出一些警告。每个printk()都要指明优先级,共有8个优先级每个都有macro对应,默认的是DEFAULT_MESSAGE_LOGLEVEL。 当syslogd和klogd在运行时,log信息被保存在_/var/log/messages_中。
编译内核模块
Makefile
obj-m += hello-1.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uanme -r)/build M=$(PWD) clean
then run make in terminal
$ make
make -C /lib/modules/3.8.0-29-generic/build M=/home/persnail/workspace/git/repos/LKMPG/HelloWorld1 modules
make[1]: Entering directory `/usr/src/linux-headers-3.8.0-29-generic'
CC [M] /home/persnail/workspace/git/repos/LKMPG/HelloWorld1/hello-1.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/persnail/workspace/git/repos/LKMPG/HelloWorld1/hello-1.mod.o
LD [M] /home/persnail/workspace/git/repos/LKMPG/HelloWorld1/hello-1.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.8.0-29-generic'
hello-1.ko就是编译完成的内核模块,查看它的信息
$ modinfo hello-1.ko
filename: hello-1.ko
srcversion: 140276773A3090F6F33891F
depends:
vermagic: 3.8.0-29-generic SMP mod_unload modversions
加载内核模块
$ sudo insmod ./hello-1.ko
查看是否已经加载
$ cat /proc/modules | grep hello
hello_1 12426 0 - Live 0x0000000000000000 (POF)
卸载内核模块
$ sudo rmmod hello-1.ko
查看log信息
$ less /var/log/syslog
Oct 25 00:58:48 persnail-Vostro-2420 kernel: [ 7423.160253] Hello world 1.
Oct 25 01:00:41 persnail-Vostro-2420 kernel: [ 7536.613425] Goodbye world 1.