计算机之旅(just for fun)(未完)
2017-01-03 14:59
309 查看
未经允许,拒绝转载
这篇文章综合了目前我在编译原理,linux内核,以及其它涉及计算机方面的认识。推荐书籍:UTL(深入理解linux内核),龙书,linkers and loaders,以及一个国产的编译原理透视,csapp也行不过感觉在内核方面不怎么深入。我将尽可能不过多深入细节,因为一旦深入细节估计我几十篇博客都说不完,我只对整个框架作探讨。
在你编程的时候你有考虑过你写的东西在计算机上是怎么布置和运行的吗?我可以用一组形象的比喻来帮助你了解整个程序运行的计算机各个方面的参与,看完过后希望你可以像我一样对整个计算机软件架构组成感到不可思议以及那深深地敬仰。
从你编写的程序(你所输入的是ASII码)到计算机能识别的目标文件是编译器的事,而后链接器把多个目标文件及.o文件以及一些动态库给链接成一个能在内核里面执行的可执行文件。形象的比喻是你就像是一个铁路铁轨的规划师,编译器负责按照你的规划造铁轨,造完后链接器把它装上去,内核的控制流才能控制CPU及火车在你的铁路上跑(我这控制流的比喻是有深意的,稍后看完就知道),对于c语言这种结构化的语言(在我看来是面向内存的语言
)来说,结构是核心,及“{}”这个是核心,在这用代码举个例子:
你猜猜打印是啥,如果你能第一时间答对,你对C编译器的语法分析的一个大特性算是了解了。(编译器 mingw32-gcc)
让我来解释解释,所有数据都用一个标识符表示,在词法分析时会生成一个token也及一个词法单元每个词法单元都会有一些可选的属性值,词法分析是怎么分析的呢,用的是有穷自动机算法来做状态机(每种类型比如说 空白键,无符号数字都是一个状态转移图,有穷自动机就是匹配这些状态的一种算法),有穷自动机算法会匹配各个词素而词素(也就是有意义的字符串)就是词法单元的一个实例,比如说
不过我为啥知道用L在字符串之前也是看gcc源代码知道的)于是这个词素的类型就是CST__INT(反正是系统预定义的),而后遇到i,这个不是特殊保留字于是把它当作标识符并生成类似于<id,1>这样的词法单元,id为该标识符的抽象符号(相当于一个指向该标识符的指针,1为指向符号表的指针),标识符类型在语法分析的时候确定就是int,于是标识符i有了一些属性,前面说到的符号表的使用其实是在语义分析中由语法制导翻译(syntax-directed
translation)阶段才用到,在遇到“{”时相当于进入了个新作用域,此时编译器会更新当前作用域已经相应的符号表。是不是看到现在有些昏。。因为实际上更复杂(具体感兴趣可以看龙书),总之作用域是每个语言非常关键的地方,C语言的作用域是人为规划的,所以很多人不了解原理会乱用,作用域的环境在C中是一棵树形结构,采用后续遍历,和最近嵌套规则(most-closely),这就是为啥最后打印结果为2的原因:
int i;
int main()
{
i =9;
__int16 * str2=L"1223";
char * str3=L"1223";
printf("do you know?\n%s %d %d\n",str2,*(str2+1),*(str3+1));
{i=2;}{int i=3;}{int i=4;}
printf("%d",i);
return 0;
}
int i中的i是这个程序的顶层作用域,main函数为其子作用域中间且其也有3个子作用域,像树一样被连在一起,好了printf要打印的i了,那i的定义在哪呢?首先在当前作用域里面找,没有,于是后序遍历往上在顶层作用域找到了,那这个i此时的值是啥呢?首先因为编译器是从上往下分析,这个i先是在main的作用域被赋值为9,在其子作用域有3个关于i的操作,第一个的i在当前作用域找不到定义,于是后续遍历找到顶层作用域那有个定义,于是这个i实际上与i=9那个i指向的是同一个内存地址(在.bss区,这个内存分布参考我第一篇博客),而剩下两个对i的定义抽像到汇编层次可能就是放在main函数堆栈的两个值,所以最后打印出来是2。(一个作用域一个符号表,编译器很有趣,后面也比较难,什么RTL之类的,好在有了lex等辅助生产工具,不过就算这样也很难,我认为和内核难度是一个层次的东西,我在这里面就简单写了一点无关紧要的东西,之后可能会有单独写编译器的文章
)
好了铁轨铺好了,从编译器那产生个目标文件(gcc不是编译器,它是一个编译套件GNU Compiler Collection里面包含有编译器,汇编器,链接器),你可能要问为啥要有链接器和所谓目标文件,直接一步到位不是好,too naive
,那些大型软件以及内核岂是区区一个或者几个文件能产生的,.o文件是一个c程序产生的目标文件,它是不能直接运行的,因为就像是铁轨要很多条联通才能运行一样,编译器只负责一个文件一个文件这样检查你的语法语义错误产生代码,你有没有相过万一我两个文件都在顶层作用域定义了个i那么可能只有鬼知道你指向的是哪个i的内存地址,于是搭铁轨的链接器诞生了,为了保持其目标文件的纯洁性独一性,往往目标代码是位置无关代码(很多编译器都有编译选项
-fplc就是确保如此,而且许多攻击setuid文件的漏洞都大致利用了此特性
https://www.exploit-db.com/exploits/15274/),而且起始地址是0,于是重定位成了链接器的主要工作,就是把程序给映射到内存当中去,至于怎么个重定向法看之下的例子(我之前项目的一个小模块测试程序):
AT&T的汇编格式,至于你想改成其它啥格式逆向你可以参考我的第一篇博客,再看看反汇编可执行程序的样子:(我已把一些表示版本号和其它的辅助性区给人为去掉或者你用objdump -S命令):
linkage table)
找到相应函数地址给替换之前的标识符,是函数就在section .plt中找,每个外部定义的符号都在GOT中有对应条目,是函数则在PLT(哈哈,这就是好多ctf pwn题的一些基本
,什么ROP攻击,ret2lib之类的都或多或少利用了可执行文件的这些接口泄露的真实lib函数地址用来跳到内核控制流来达到攻击目的)。用nm命令可以查看文件一些顶层作用域或者外部符号表:
0000000000600bb8 B __bss_start
0000000000600bb8 b completed.6661
0000000000600ba8 D __data_start
0000000000600ba8 W data_start
00000000004005a0 t deregister_tm_clones
0000000000400620 t __do_global_dtors_aux
0000000000600968 t __do_global_dtors_aux_fini_array_entry
0000000000600bb0 D __dso_handle
0000000000600978 d _DYNAMIC
0000000000600bb8 D _edata
0000000000600bc0 B _end
U fclose@@GLIBC_2.2.5
00000000004007d4 T _fini
U fopen@@GLIBC_2.2.5
0000000000400640 t frame_dummy
0000000000600960 t __frame_dummy_init_array_entry
0000000000400958 r __FRAME_END__
0000000000400666 T fun
0000000000600bbc B fun1
U getw@@GLIBC_2.2.5
0000000000600b50 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000004004c0 T _init
0000000000600968 t __init_array_end
0000000000600960 t __init_array_start
00000000004007e0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600970 d __JCR_END__
0000000000600970 d __JCR_LIST__
w _Jv_RegisterClasses
00000000004007d0 T __libc_csu_fini
0000000000400760 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
00000000004006bd T main
U mkdir@@GLIBC_2.2.5
U printf@@GLIBC_2.2.5
U putw@@GLIBC_2.2.5
00000000004005e0 t register_tm_clones
0000000000400570 T _start
0000000000600bb8 D __TMC_END__
T代表在text区及代码区,B则是bss全局未初始化变量..etc。看红字部分代表这个符号表来自外部的库,连库版本都有,是不是很强大,这个只是一个目标文件链接成一个可执行代码的简单例子,多个目标文件链接时,链接器会将它们的.text,.data,.bss等等区合并,这时若你的目标文件里面有多个重复的标识符那么会链接失败,C语言在这点的就不如c++之类好,c语言的函数标识符(这个标识符是针对链接器来说,而不是在编译阶段的标识符)比较死,两个函数函数名相同但是参数不同在c里面算一个标识符,而c++的标识符会区分函数参数甚至连空间都有,于是有了
extern “c”这种修饰符用来统一编译和链接规则。搭铁轨说那么多就行了,更细节的在于汇编指令以及动态库之类的了,这也是和内核及操作系统有关的。下面是重中之重,就是内核控制流部分了。
估计要写到第二篇。
众所周知,CPU通过读取rip(eip)寄存器的值给总线来控制底层硬件管脚的高低电平从而产生如此神奇的东西——计算机。所以你写的程序肯定一部分是给cpu控制流读取的(.text)而且必须还有一部分是用来存储上下文(也就是数据.bss.rodata.data..etc)的,这一部分映射到内存后(映射过程是由虚拟地址到线性地址最后才是物理地址,由于linux不采用分段机制,故线性地址和虚拟地址一样,一个32位地址大致分成这样:cr3(占10位,存页基址 )+页目录地址(10位)+页表地址(12位,这个是4kb页)64位可能就有多个页目录),给这部分内存起一个名就叫进程,且用一个叫task_struct的数据结构描述它,而其中常见信息存在thread_info这个字段里面(简称tts),且进程用fs_struct维护它打开的文件指针fd,这个参考我的第二篇博客,且用signal_struct这个维护接受信号(处理异常所必须),其实进程只是铺好的铁轨,至于上面有没有车在跑(cpu执行流),这个就和内核有关了,进程大致有7个状态,每个状态都对应一个双向链表(想深入了解看UTL),每一个进程都想抢cpu时间片也就是想schedule一下,不同版本的内核调度器不同,有的是O(1)调度,有的是CFS,有的是BNF,据我收集的资料CFS比较常见,因为其在上千个虚拟核并行的时候效果比较好,这个是O(1)调度的我认为算是形象的例子:http://www.manio.org/cn/scheduling-of-linux-view-of-society/,他描述的是大致没涉及到多核,内核还用了4个hash表标识进程,好让内核快速查找,比如说我们熟知的pid就是一个。进程之间的关系特别复杂,特别是在多核cpu的preempt竞争机制下,这里篇幅有限就不多说,我们假设每个cpu是一个司机,那么一个进程的结束就是在多个司机(也有可能是一个)的作用下cpu流把这个进程的.text走完(这个是正常结束,也有可能出现异常,比如说你ctrl+c时会向当前进程发一个SIGINT信号,若进程没有相应的信号处理函数则会被中断,可以参考这个http://blog.csdn.net/yikai2009/article/details/8643818),那么所谓多线程是啥呢?其实就是一个进程内有多个流,这个在用户态用pthread_create人为定义,它会把某个在当前进程空间的函数当做一个小车让cpu司机去驾驶,而且小车的轨道是铺好的,之下的就是多线程例子(取自我为上个项目编写的调试代码):
中间的那个互斥锁和条件信号限于篇幅我不讲了(可能下一篇会说),
,你会发现所谓多线程就是如此,而内核态多线程复杂得多,因为内核时刻都在运行,内核态的进程必须自己释放CPU,而有个特殊的内核执行流要深入研究,那就是中断,一个进程遇到中断时CPU会直接跳到内核态去运行相应的中断服务程序,这中间不像进程切换要调用switch_to保存诸如页基址,堆栈之类的,只用保存少量上下文(eip,cs之类的计数器),而在内核模块定义中断是这样定义的,中断的输入是硬件管脚,这是无规律且很必然事件,所以很多时候CPU必须优先处理(这个是我上一个驱动项目代码的一部分):
控制它走向它想走的地方,好了限于时间,这篇到此结束,下一篇我有空我将继续升入CPU司机部分,调度,内核竞争之类的。
这篇文章综合了目前我在编译原理,linux内核,以及其它涉及计算机方面的认识。推荐书籍:UTL(深入理解linux内核),龙书,linkers and loaders,以及一个国产的编译原理透视,csapp也行不过感觉在内核方面不怎么深入。我将尽可能不过多深入细节,因为一旦深入细节估计我几十篇博客都说不完,我只对整个框架作探讨。
在你编程的时候你有考虑过你写的东西在计算机上是怎么布置和运行的吗?我可以用一组形象的比喻来帮助你了解整个程序运行的计算机各个方面的参与,看完过后希望你可以像我一样对整个计算机软件架构组成感到不可思议以及那深深地敬仰。
从你编写的程序(你所输入的是ASII码)到计算机能识别的目标文件是编译器的事,而后链接器把多个目标文件及.o文件以及一些动态库给链接成一个能在内核里面执行的可执行文件。形象的比喻是你就像是一个铁路铁轨的规划师,编译器负责按照你的规划造铁轨,造完后链接器把它装上去,内核的控制流才能控制CPU及火车在你的铁路上跑(我这控制流的比喻是有深意的,稍后看完就知道),对于c语言这种结构化的语言(在我看来是面向内存的语言
)来说,结构是核心,及“{}”这个是核心,在这用代码举个例子:
#include <stdio.h> #include <stdlib.h> int sum(int num,...){return num;} int i; int main() { i =9; __int16 * str2=L"1223"; char * str3=L"1223"; printf("do you know?\n%s %d %d\n",str2,*(str2+1),*(str3+1)); {i=2;}{int i=3;}{int i=4;} printf("%d",i); return 0; }
你猜猜打印是啥,如果你能第一时间答对,你对C编译器的语法分析的一个大特性算是了解了。(编译器 mingw32-gcc)
让我来解释解释,所有数据都用一个标识符表示,在词法分析时会生成一个token也及一个词法单元每个词法单元都会有一些可选的属性值,词法分析是怎么分析的呢,用的是有穷自动机算法来做状态机(每种类型比如说 空白键,无符号数字都是一个状态转移图,有穷自动机就是匹配这些状态的一种算法),有穷自动机算法会匹配各个词素而词素(也就是有意义的字符串)就是词法单元的一个实例,比如说
int i词法分析编译器扫到i这个字母时会往下继续一直遇到分隔符(空格换行符这些),得到int这个字符串(其中还有一些加快分析的方法比如说用缓冲区对),恰好是个保留字(keyword,系统预定义在GCC一个函数里面有,因为函数名比较长我忘记了
不过我为啥知道用L在字符串之前也是看gcc源代码知道的)于是这个词素的类型就是CST__INT(反正是系统预定义的),而后遇到i,这个不是特殊保留字于是把它当作标识符并生成类似于<id,1>这样的词法单元,id为该标识符的抽象符号(相当于一个指向该标识符的指针,1为指向符号表的指针),标识符类型在语法分析的时候确定就是int,于是标识符i有了一些属性,前面说到的符号表的使用其实是在语义分析中由语法制导翻译(syntax-directed
translation)阶段才用到,在遇到“{”时相当于进入了个新作用域,此时编译器会更新当前作用域已经相应的符号表。是不是看到现在有些昏。。因为实际上更复杂(具体感兴趣可以看龙书),总之作用域是每个语言非常关键的地方,C语言的作用域是人为规划的,所以很多人不了解原理会乱用,作用域的环境在C中是一棵树形结构,采用后续遍历,和最近嵌套规则(most-closely),这就是为啥最后打印结果为2的原因:
int i;
int main()
{
i =9;
__int16 * str2=L"1223";
char * str3=L"1223";
printf("do you know?\n%s %d %d\n",str2,*(str2+1),*(str3+1));
{i=2;}{int i=3;}{int i=4;}
printf("%d",i);
return 0;
}
int i中的i是这个程序的顶层作用域,main函数为其子作用域中间且其也有3个子作用域,像树一样被连在一起,好了printf要打印的i了,那i的定义在哪呢?首先在当前作用域里面找,没有,于是后序遍历往上在顶层作用域找到了,那这个i此时的值是啥呢?首先因为编译器是从上往下分析,这个i先是在main的作用域被赋值为9,在其子作用域有3个关于i的操作,第一个的i在当前作用域找不到定义,于是后续遍历找到顶层作用域那有个定义,于是这个i实际上与i=9那个i指向的是同一个内存地址(在.bss区,这个内存分布参考我第一篇博客),而剩下两个对i的定义抽像到汇编层次可能就是放在main函数堆栈的两个值,所以最后打印出来是2。(一个作用域一个符号表,编译器很有趣,后面也比较难,什么RTL之类的,好在有了lex等辅助生产工具,不过就算这样也很难,我认为和内核难度是一个层次的东西,我在这里面就简单写了一点无关紧要的东西,之后可能会有单独写编译器的文章
)
好了铁轨铺好了,从编译器那产生个目标文件(gcc不是编译器,它是一个编译套件GNU Compiler Collection里面包含有编译器,汇编器,链接器),你可能要问为啥要有链接器和所谓目标文件,直接一步到位不是好,too naive
,那些大型软件以及内核岂是区区一个或者几个文件能产生的,.o文件是一个c程序产生的目标文件,它是不能直接运行的,因为就像是铁轨要很多条联通才能运行一样,编译器只负责一个文件一个文件这样检查你的语法语义错误产生代码,你有没有相过万一我两个文件都在顶层作用域定义了个i那么可能只有鬼知道你指向的是哪个i的内存地址,于是搭铁轨的链接器诞生了,为了保持其目标文件的纯洁性独一性,往往目标代码是位置无关代码(很多编译器都有编译选项
-fplc就是确保如此,而且许多攻击setuid文件的漏洞都大致利用了此特性
https://www.exploit-db.com/exploits/15274/),而且起始地址是0,于是重定位成了链接器的主要工作,就是把程序给映射到内存当中去,至于怎么个重定向法看之下的例子(我之前项目的一个小模块测试程序):
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <errno.h> int fun(int a,...) {} int fun1; int main() { int i= 0 ; FILE *fp2,*fp1; mkdir("/data", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); //fp1= fopen("/data/sequence.txt","wb"); fp2 = fopen("/data/sequence.txt","rb"); if (fp2 < 0) perror("open"); //putw(i,fp2); //fread(&i,sizeof(int),1,fp2); i=getw(fp2); printf("%d\n", i); fclose(fp2); fp1= fopen("/data/sequence.txt","wb"); putw(i,fp1); fclose(fp1); return 0; }这个程序的.o文件逆向出来是这样:
test4.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <fun>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 48 sub $0x48,%rsp 8: 48 89 b5 58 ff ff ff mov %rsi,-0xa8(%rbp) f: 48 89 95 60 ff ff ff mov %rdx,-0xa0(%rbp) 16: 48 89 8d 68 ff ff ff mov %rcx,-0x98(%rbp) 1d: 4c 89 85 70 ff ff ff mov %r8,-0x90(%rbp) 24: 4c 89 8d 78 ff ff ff mov %r9,-0x88(%rbp) 2b: 84 c0 test %al,%al 2d: 74 20 je 4f <fun+0x4f> 2f: 0f 29 45 80 movaps %xmm0,-0x80(%rbp) 33: 0f 29 4d 90 movaps %xmm1,-0x70(%rbp) 37: 0f 29 55 a0 movaps %xmm2,-0x60(%rbp) 3b: 0f 29 5d b0 movaps %xmm3,-0x50(%rbp) 3f: 0f 29 65 c0 movaps %xmm4,-0x40(%rbp) 43: 0f 29 6d d0 movaps %xmm5,-0x30(%rbp) 47: 0f 29 75 e0 movaps %xmm6,-0x20(%rbp) 4b: 0f 29 7d f0 movaps %xmm7,-0x10(%rbp) 4f: 89 bd 4c ff ff ff mov %edi,-0xb4(%rbp) 55: c9 leaveq 56: c3 retq 0000000000000057 <main>: 57: 55 push %rbp 58: 48 89 e5 mov %rsp,%rbp 5b: 48 83 ec 20 sub $0x20,%rsp 5f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 66: be fd 01 00 00 mov $0x1fd,%esi 6b: bf 00 00 00 00 mov $0x0,%edi 70: e8 00 00 00 00 callq 75 <main+0x1e> 75: be 00 00 00 00 mov $0x0,%esi 7a: bf 00 00 00 00 mov $0x0,%edi 7f: e8 00 00 00 00 callq 84 <main+0x2d> 84: 48 89 45 f0 mov %rax,-0x10(%rbp) 88: 48 8b 45 f0 mov -0x10(%rbp),%rax 8c: 48 89 c7 mov %rax,%rdi 8f: e8 00 00 00 00 callq 94 <main+0x3d> 94: 89 45 fc mov %eax,-0x4(%rbp) 97: 8b 45 fc mov -0x4(%rbp),%eax 9a: 89 c6 mov %eax,%esi 9c: bf 00 00 00 00 mov $0x0,%edi a1: b8 00 00 00 00 mov $0x0,%eax a6: e8 00 00 00 00 callq ab <main+0x54> ab: 48 8b 45 f0 mov -0x10(%rbp),%rax af: 48 89 c7 mov %rax,%rdi b2: e8 00 00 00 00 callq b7 <main+0x60> b7: be 00 00 00 00 mov $0x0,%esi bc: bf 00 00 00 00 mov $0x0,%edi c1: e8 00 00 00 00 callq c6 <main+0x6f> c6: 48 89 45 e8 mov %rax,-0x18(%rbp) ca: 48 8b 55 e8 mov -0x18(%rbp),%rdx ce: 8b 45 fc mov -0x4(%rbp),%eax d1: 48 89 d6 mov %rdx,%rsi d4: 89 c7 mov %eax,%edi d6: e8 00 00 00 00 callq db <main+0x84> db: 48 8b 45 e8 mov -0x18(%rbp),%rax df: 48 89 c7 mov %rax,%rdi e2: e8 00 00 00 00 callq e7 <main+0x90> e7: b8 00 00 00 00 mov $0x0,%eax ec: c9 leaveq ed: c3 retq Disassembly of section .rodata: 0000000000000000 <.rodata>: 0: 2f (bad) 1: 64 61 fs (bad) 3: 74 61 je 66 <main+0xf> 5: 00 72 62 add %dh,0x62(%rdx) 8: 00 2f add %ch,(%rdi) a: 64 61 fs (bad) c: 74 61 je 6f <main+0x18> e: 2f (bad) f: 73 65 jae 76 <main+0x1f> 11: 71 75 jno 88 <main+0x31> 13: 65 6e outsb %gs:(%rsi),(%dx) 15: 63 65 2e movslq 0x2e(%rbp),%esp 18: 74 78 je 92 <main+0x3b> 1a: 74 00 je 1c <.rodata+0x1c> 1c: 25 64 0a 00 77 and $0x77000a64,%eax 21: 62 .byte 0x62 ... Disassembly of section .comment: 0000000000000000 <.comment>: 0: 00 47 43 add %al,0x43(%rdi) 3: 43 3a 20 rex.XB cmp (%r8),%spl 6: 28 44 65 62 sub %al,0x62(%rbp,%riz,2) a: 69 61 6e 20 34 2e 39 imul $0x392e3420,0x6e(%rcx),%esp 11: 2e 32 2d 31 30 29 20 xor %cs:0x20293031(%rip),%ch # 20293049 <main+0x20292ff2> 18: 34 2e xor $0x2e,%al 1a: 39 2e cmp %ebp,(%rsi) 1c: 32 00 xor (%rax),%al Disassembly of section .eh_frame: 0000000000000000 <.eh_frame>: 0: 14 00 adc $0x0,%al 2: 00 00 add %al,(%rax) 4: 00 00 add %al,(%rax) 6: 00 00 add %al,(%rax) 8: 01 7a 52 add %edi,0x52(%rdx) b: 00 01 add %al,(%rcx) d: 78 10 js 1f <.eh_frame+0x1f> f: 01 1b add %ebx,(%rbx) 11: 0c 07 or $0x7,%al 13: 08 90 01 00 00 1c or %dl,0x1c000001(%rax) 19: 00 00 add %al,(%rax) 1b: 00 1c 00 add %bl,(%rax,%rax,1) 1e: 00 00 add %al,(%rax) 20: 00 00 add %al,(%rax) 22: 00 00 add %al,(%rax) 24: 57 push %rdi 25: 00 00 add %al,(%rax) 27: 00 00 add %al,(%rax) 29: 41 0e rex.B (bad) 2b: 10 86 02 43 0d 06 adc %al,0x60d4302(%rsi) 31: 02 52 0c add 0xc(%rdx),%dl 34: 07 (bad) 35: 08 00 or %al,(%rax) 37: 00 1c 00 add %bl,(%rax,%rax,1) 3a: 00 00 add %al,(%rax) 3c: 3c 00 cmp $0x0,%al 3e: 00 00 add %al,(%rax) 40: 00 00 add %al,(%rax) 42: 00 00 add %al,(%rax) 44: 97 xchg %eax,%edi 45: 00 00 add %al,(%rax) 47: 00 00 add %al,(%rax) 49: 41 0e rex.B (bad) 4b: 10 86 02 43 0d 06 adc %al,0x60d4302(%rsi) 51: 02 92 0c 07 08 00 add 0x8070c(%rdx),%dl ...
AT&T的汇编格式,至于你想改成其它啥格式逆向你可以参考我的第一篇博客,再看看反汇编可执行程序的样子:(我已把一些表示版本号和其它的辅助性区给人为去掉或者你用objdump -S命令):
test4: file format elf64-x86-64 Disassembly of section .init: 00000000004004c0 <_init>: 4004c0: 48 83 ec 08 sub $0x8,%rsp 4004c4: 48 8b 05 7d 06 20 00 mov 0x20067d(%rip),%rax # 600b48 <_DYNAMIC+0x1d0> 4004cb: 48 85 c0 test %rax,%rax 4004ce: 74 05 je 4004d5 <_init+0x15> 4004d0: e8 6b 00 00 00 callq 400540 <__gmon_start__@plt> 4004d5: 48 83 c4 08 add $0x8,%rsp 4004d9: c3 retq Disassembly of section .plt: 00000000004004e0 <mkdir@plt-0x10>: 4004e0: ff 35 72 06 20 00 pushq 0x200672(%rip) # 600b58 <_GLOBAL_OFFSET_TABLE_+0x8> 4004e6: ff 25 74 06 20 00 jmpq *0x200674(%rip) # 600b60 <_GLOBAL_OFFSET_TABLE_+0x10> 4004ec: 0f 1f 40 00 nopl 0x0(%rax) 00000000004004f0 <mkdir@plt>: 4004f0: ff 25 72 06 20 00 jmpq *0x200672(%rip) # 600b68 <_GLOBAL_OFFSET_TABLE_+0x18> 4004f6: 68 00 00 00 00 pushq $0x0 4004fb: e9 e0 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400500 <fclose@plt>: 400500: ff 25 6a 06 20 00 jmpq *0x20066a(%rip) # 600b70 <_GLOBAL_OFFSET_TABLE_+0x20> 400506: 68 01 00 00 00 pushq $0x1 40050b: e9 d0 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400510 <printf@plt>: 400510: ff 25 62 06 20 00 jmpq *0x200662(%rip) # 600b78 <_GLOBAL_OFFSET_TABLE_+0x28> 400516: 68 02 00 00 00 pushq $0x2 40051b: e9 c0 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400520 <putw@plt>: 400520: ff 25 5a 06 20 00 jmpq *0x20065a(%rip) # 600b80 <_GLOBAL_OFFSET_TABLE_+0x30> 400526: 68 03 00 00 00 pushq $0x3 40052b: e9 b0 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400530 <__libc_start_main@plt>: 400530: ff 25 52 06 20 00 jmpq *0x200652(%rip) # 600b88 <_GLOBAL_OFFSET_TABLE_+0x38> 400536: 68 04 00 00 00 pushq $0x4 40053b: e9 a0 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400540 <__gmon_start__@plt>: 400540: ff 25 4a 06 20 00 jmpq *0x20064a(%rip) # 600b90 <_GLOBAL_OFFSET_TABLE_+0x40> 400546: 68 05 00 00 00 pushq $0x5 40054b: e9 90 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400550 <getw@plt>: 400550: ff 25 42 06 20 00 jmpq *0x200642(%rip) # 600b98 <_GLOBAL_OFFSET_TABLE_+0x48> 400556: 68 06 00 00 00 pushq $0x6 40055b: e9 80 ff ff ff jmpq 4004e0 <_init+0x20> 0000000000400560 <fopen@plt>: 400560: ff 25 3a 06 20 00 jmpq *0x20063a(%rip) # 600ba0 <_GLOBAL_OFFSET_TABLE_+0x50> 400566: 68 07 00 00 00 pushq $0x7 40056b: e9 70 ff ff ff jmpq 4004e0 <_init+0x20> Disassembly of section .text: 0000000000400570 <_start>: 400570: 31 ed xor %ebp,%ebp 400572: 49 89 d1 mov %rdx,%r9 400575: 5e pop %rsi 400576: 48 89 e2 mov %rsp,%rdx 400579: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40057d: 50 push %rax 40057e: 54 push %rsp 40057f: 49 c7 c0 d0 07 40 00 mov $0x4007d0,%r8 400586: 48 c7 c1 60 07 40 00 mov $0x400760,%rcx 40058d: 48 c7 c7 bd 06 40 00 mov $0x4006bd,%rdi 400594: e8 97 ff ff ff callq 400530 <__libc_start_main@plt> 400599: f4 hlt 40059a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 00000000004005a0 <deregister_tm_clones>: 4005a0: b8 bf 0b 60 00 mov $0x600bbf,%eax 4005a5: 55 push %rbp 4005a6: 48 2d b8 0b 60 00 sub $0x600bb8,%rax 4005ac: 48 83 f8 0e cmp $0xe,%rax 4005b0: 48 89 e5 mov %rsp,%rbp 4005b3: 76 1b jbe 4005d0 <deregister_tm_clones+0x30> 4005b5: b8 00 00 00 00 mov $0x0,%eax 4005ba: 48 85 c0 test %rax,%rax 4005bd: 74 11 je 4005d0 <deregister_tm_clones+0x30> 4005bf: 5d pop %rbp 4005c0: bf b8 0b 60 00 mov $0x600bb8,%edi 4005c5: ff e0 jmpq *%rax 4005c7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 4005ce: 00 00 4005d0: 5d pop %rbp 4005d1: c3 retq 4005d2: 66 66 66 66 66 2e 0f data16 data16 data16 data16 nopw %cs:0x0(%rax,%rax,1) 4005d9: 1f 84 00 00 00 00 00 00000000004005e0 <register_tm_clones>: 4005e0: be b8 0b 60 00 mov $0x600bb8,%esi 4005e5: 55 push %rbp 4005e6: 48 81 ee b8 0b 60 00 sub $0x600bb8,%rsi 4005ed: 48 c1 fe 03 sar $0x3,%rsi 4005f1: 48 89 e5 mov %rsp,%rbp 4005f4: 48 89 f0 mov %rsi,%rax 4005f7: 48 c1 e8 3f shr $0x3f,%rax 4005fb: 48 01 c6 add %rax,%rsi 4005fe: 48 d1 fe sar %rsi 400601: 74 15 je 400618 <register_tm_clones+0x38> 400603: b8 00 00 00 00 mov $0x0,%eax 400608: 48 85 c0 test %rax,%rax 40060b: 74 0b je 400618 <register_tm_clones+0x38> 40060d: 5d pop %rbp 40060e: bf b8 0b 60 00 mov $0x600bb8,%edi 400613: ff e0 jmpq *%rax 400615: 0f 1f 00 nopl (%rax) 400618: 5d pop %rbp 400619: c3 retq 40061a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 0000000000400620 <__do_global_dtors_aux>: 400620: 80 3d 91 05 20 00 00 cmpb $0x0,0x200591(%rip) # 600bb8 <__TMC_END__> 400627: 75 11 jne 40063a <__do_global_dtors_aux+0x1a> 400629: 55 push %rbp 40062a: 48 89 e5 mov %rsp,%rbp 40062d: e8 6e ff ff ff callq 4005a0 <deregister_tm_clones> 400632: 5d pop %rbp 400633: c6 05 7e 05 20 00 01 movb $0x1,0x20057e(%rip) # 600bb8 <__TMC_END__> 40063a: f3 c3 repz retq 40063c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400640 <frame_dummy>: 400640: bf 70 09 60 00 mov $0x600970,%edi 400645: 48 83 3f 00 cmpq $0x0,(%rdi) 400649: 75 05 jne 400650 <frame_dummy+0x10> 40064b: eb 93 jmp 4005e0 <register_tm_clones> 40064d: 0f 1f 00 nopl (%rax) 400650: b8 00 00 00 00 mov $0x0,%eax 400655: 48 85 c0 test %rax,%rax 400658: 74 f1 je 40064b <frame_dummy+0xb> 40065a: 55 push %rbp 40065b: 48 89 e5 mov %rsp,%rbp 40065e: ff d0 callq *%rax 400660: 5d pop %rbp 400661: e9 7a ff ff ff jmpq 4005e0 <register_tm_clones> 0000000000400666 <fun>: 400666: 55 push %rbp 400667: 48 89 e5 mov %rsp,%rbp 40066a: 48 83 ec 48 sub $0x48,%rsp 40066e: 48 89 b5 58 ff ff ff mov %rsi,-0xa8(%rbp) 400675: 48 89 95 60 ff ff ff mov %rdx,-0xa0(%rbp) 40067c: 48 89 8d 68 ff ff ff mov %rcx,-0x98(%rbp) 400683: 4c 89 85 70 ff ff ff mov %r8,-0x90(%rbp) 40068a: 4c 89 8d 78 ff ff ff mov %r9,-0x88(%rbp) 400691: 84 c0 test %al,%al 400693: 74 20 je 4006b5 <fun+0x4f> 400695: 0f 29 45 80 movaps %xmm0,-0x80(%rbp) 400699: 0f 29 4d 90 movaps %xmm1,-0x70(%rbp) 40069d: 0f 29 55 a0 movaps %xmm2,-0x60(%rbp) 4006a1: 0f 29 5d b0 movaps %xmm3,-0x50(%rbp) 4006a5: 0f 29 65 c0 movaps %xmm4,-0x40(%rbp) 4006a9: 0f 29 6d d0 movaps %xmm5,-0x30(%rbp) 4006ad: 0f 29 75 e0 movaps %xmm6,-0x20(%rbp) 4006b1: 0f 29 7d f0 movaps %xmm7,-0x10(%rbp) 4006b5: 89 bd 4c ff ff ff mov %edi,-0xb4(%rbp) 4006bb: c9 leaveq 4006bc: c3 retq 00000000004006bd <main>: 4006bd: 55 push %rbp 4006be: 48 89 e5 mov %rsp,%rbp 4006c1: 48 83 ec 20 sub $0x20,%rsp 4006c5: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 4006cc: be fd 01 00 00 mov $0x1fd,%esi 4006d1: bf e4 07 40 00 mov $0x4007e4,%edi 4006d6: e8 15 fe ff ff callq 4004f0 <mkdir@plt> 4006db: be ea 07 40 00 mov $0x4007ea,%esi 4006e0: bf ed 07 40 00 mov $0x4007ed,%edi 4006e5: e8 76 fe ff ff callq 400560 <fopen@plt> 4006ea: 48 89 45 f0 mov %rax,-0x10(%rbp) 4006ee: 48 8b 45 f0 mov -0x10(%rbp),%rax 4006f2: 48 89 c7 mov %rax,%rdi 4006f5: e8 56 fe ff ff callq 400550 <getw@plt> 4006fa: 89 45 fc mov %eax,-0x4(%rbp) 4006fd: 8b 45 fc mov -0x4(%rbp),%eax 400700: 89 c6 mov %eax,%esi 400702: bf 00 08 40 00 mov $0x400800,%edi 400707: b8 00 00 00 00 mov $0x0,%eax 40070c: e8 ff fd ff ff callq 400510 <printf@plt> 400711: 48 8b 45 f0 mov -0x10(%rbp),%rax 400715: 48 89 c7 mov %rax,%rdi 400718: e8 e3 fd ff ff callq 400500 <fclose@plt> 40071d: be 04 08 40 00 mov $0x400804,%esi 400722: bf ed 07 40 00 mov $0x4007ed,%edi 400727: e8 34 fe ff ff callq 400560 <fopen@plt> 40072c: 48 89 45 e8 mov %rax,-0x18(%rbp) 400730: 48 8b 55 e8 mov -0x18(%rbp),%rdx 400734: 8b 45 fc mov -0x4(%rbp),%eax 400737: 48 89 d6 mov %rdx,%rsi 40073a: 89 c7 mov %eax,%edi 40073c: e8 df fd ff ff callq 400520 <putw@plt> 400741: 48 8b 45 e8 mov -0x18(%rbp),%rax 400745: 48 89 c7 mov %rax,%rdi 400748: e8 b3 fd ff ff callq 400500 <fclose@plt> 40074d: b8 00 00 00 00 mov $0x0,%eax 400752: c9 leaveq 400753: c3 retq 400754: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40075b: 00 00 00 40075e: 66 90 xchg %ax,%ax 0000000000400760 <__libc_csu_init>: 400760: 41 57 push %r15 400762: 41 89 ff mov %edi,%r15d 400765: 41 56 push %r14 400767: 49 89 f6 mov %rsi,%r14 40076a: 41 55 push %r13 40076c: 49 89 d5 mov %rdx,%r13 40076f: 41 54 push %r12 400771: 4c 8d 25 e8 01 20 00 lea 0x2001e8(%rip),%r12 # 600960 <__frame_dummy_init_array_entry> 400778: 55 push %rbp 400779: 48 8d 2d e8 01 20 00 lea 0x2001e8(%rip),%rbp # 600968 <__init_array_end> 400780: 53 push %rbx 400781: 4c 29 e5 sub %r12,%rbp 400784: 31 db xor %ebx,%ebx 400786: 48 c1 fd 03 sar $0x3,%rbp 40078a: 48 83 ec 08 sub $0x8,%rsp 40078e: e8 2d fd ff ff callq 4004c0 <_init> 400793: 48 85 ed test %rbp,%rbp 400796: 74 1e je 4007b6 <__libc_csu_init+0x56> 400798: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40079f: 00 4007a0: 4c 89 ea mov %r13,%rdx 4007a3: 4c 89 f6 mov %r14,%rsi 4007a6: 44 89 ff mov %r15d,%edi 4007a9: 41 ff 14 dc callq *(%r12,%rbx,8) 4007ad: 48 83 c3 01 add $0x1,%rbx 4007b1: 48 39 eb cmp %rbp,%rbx 4007b4: 75 ea jne 4007a0 <__libc_csu_init+0x40> 4007b6: 48 83 c4 08 add $0x8,%rsp 4007ba: 5b pop %rbx 4007bb: 5d pop %rbp 4007bc: 41 5c pop %r12 4007be: 41 5d pop %r13 4007c0: 41 5e pop %r14 4007c2: 41 5f pop %r15 4007c4: c3 retq 4007c5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1) 4007cc: 00 00 00 00 00000000004007d0 <__libc_csu_fini>: 4007d0: f3 c3 repz retq Disassembly of section .fini: 00000000004007d4 <_fini>: 4007d4: 48 83 ec 08 sub $0x8,%rsp 4007d8: 48 83 c4 08 add $0x8,%rsp 4007dc: c3 retq嘻嘻,你注意我打红色的那两句汇编语句,这就是重定向的一种方式,就是在.o文件中用相对地址,链接器会根据你标识符找到函数接口,在PLT(procedure
linkage table)
找到相应函数地址给替换之前的标识符,是函数就在section .plt中找,每个外部定义的符号都在GOT中有对应条目,是函数则在PLT(哈哈,这就是好多ctf pwn题的一些基本
,什么ROP攻击,ret2lib之类的都或多或少利用了可执行文件的这些接口泄露的真实lib函数地址用来跳到内核控制流来达到攻击目的)。用nm命令可以查看文件一些顶层作用域或者外部符号表:
0000000000600bb8 B __bss_start
0000000000600bb8 b completed.6661
0000000000600ba8 D __data_start
0000000000600ba8 W data_start
00000000004005a0 t deregister_tm_clones
0000000000400620 t __do_global_dtors_aux
0000000000600968 t __do_global_dtors_aux_fini_array_entry
0000000000600bb0 D __dso_handle
0000000000600978 d _DYNAMIC
0000000000600bb8 D _edata
0000000000600bc0 B _end
U fclose@@GLIBC_2.2.5
00000000004007d4 T _fini
U fopen@@GLIBC_2.2.5
0000000000400640 t frame_dummy
0000000000600960 t __frame_dummy_init_array_entry
0000000000400958 r __FRAME_END__
0000000000400666 T fun
0000000000600bbc B fun1
U getw@@GLIBC_2.2.5
0000000000600b50 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000004004c0 T _init
0000000000600968 t __init_array_end
0000000000600960 t __init_array_start
00000000004007e0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600970 d __JCR_END__
0000000000600970 d __JCR_LIST__
w _Jv_RegisterClasses
00000000004007d0 T __libc_csu_fini
0000000000400760 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
00000000004006bd T main
U mkdir@@GLIBC_2.2.5
U printf@@GLIBC_2.2.5
U putw@@GLIBC_2.2.5
00000000004005e0 t register_tm_clones
0000000000400570 T _start
0000000000600bb8 D __TMC_END__
T代表在text区及代码区,B则是bss全局未初始化变量..etc。看红字部分代表这个符号表来自外部的库,连库版本都有,是不是很强大,这个只是一个目标文件链接成一个可执行代码的简单例子,多个目标文件链接时,链接器会将它们的.text,.data,.bss等等区合并,这时若你的目标文件里面有多个重复的标识符那么会链接失败,C语言在这点的就不如c++之类好,c语言的函数标识符(这个标识符是针对链接器来说,而不是在编译阶段的标识符)比较死,两个函数函数名相同但是参数不同在c里面算一个标识符,而c++的标识符会区分函数参数甚至连空间都有,于是有了
extern “c”这种修饰符用来统一编译和链接规则。搭铁轨说那么多就行了,更细节的在于汇编指令以及动态库之类的了,这也是和内核及操作系统有关的。下面是重中之重,就是内核控制流部分了。
估计要写到第二篇。
众所周知,CPU通过读取rip(eip)寄存器的值给总线来控制底层硬件管脚的高低电平从而产生如此神奇的东西——计算机。所以你写的程序肯定一部分是给cpu控制流读取的(.text)而且必须还有一部分是用来存储上下文(也就是数据.bss.rodata.data..etc)的,这一部分映射到内存后(映射过程是由虚拟地址到线性地址最后才是物理地址,由于linux不采用分段机制,故线性地址和虚拟地址一样,一个32位地址大致分成这样:cr3(占10位,存页基址 )+页目录地址(10位)+页表地址(12位,这个是4kb页)64位可能就有多个页目录),给这部分内存起一个名就叫进程,且用一个叫task_struct的数据结构描述它,而其中常见信息存在thread_info这个字段里面(简称tts),且进程用fs_struct维护它打开的文件指针fd,这个参考我的第二篇博客,且用signal_struct这个维护接受信号(处理异常所必须),其实进程只是铺好的铁轨,至于上面有没有车在跑(cpu执行流),这个就和内核有关了,进程大致有7个状态,每个状态都对应一个双向链表(想深入了解看UTL),每一个进程都想抢cpu时间片也就是想schedule一下,不同版本的内核调度器不同,有的是O(1)调度,有的是CFS,有的是BNF,据我收集的资料CFS比较常见,因为其在上千个虚拟核并行的时候效果比较好,这个是O(1)调度的我认为算是形象的例子:http://www.manio.org/cn/scheduling-of-linux-view-of-society/,他描述的是大致没涉及到多核,内核还用了4个hash表标识进程,好让内核快速查找,比如说我们熟知的pid就是一个。进程之间的关系特别复杂,特别是在多核cpu的preempt竞争机制下,这里篇幅有限就不多说,我们假设每个cpu是一个司机,那么一个进程的结束就是在多个司机(也有可能是一个)的作用下cpu流把这个进程的.text走完(这个是正常结束,也有可能出现异常,比如说你ctrl+c时会向当前进程发一个SIGINT信号,若进程没有相应的信号处理函数则会被中断,可以参考这个http://blog.csdn.net/yikai2009/article/details/8643818),那么所谓多线程是啥呢?其实就是一个进程内有多个流,这个在用户态用pthread_create人为定义,它会把某个在当前进程空间的函数当做一个小车让cpu司机去驾驶,而且小车的轨道是铺好的,之下的就是多线程例子(取自我为上个项目编写的调试代码):
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <pthread.h> static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; static void pthread_func_1 (void); static void pthread_func_2 (void); int main (int argc, char** argv) { pthread_t pt_1 = 0; pthread_t pt_2 = 0; pthread_attr_t attr = {0}; int ret = 0; pthread_attr_init (&attr); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); ret = pthread_create (&pt_1, NULL, (void *)pthread_func_1, NULL); if (ret != 0) { perror ("pthread_1_create"); } ret = pthread_create (&pt_2, NULL,(void *) pthread_func_2, NULL); if (ret != 0) { perror ("pthread_2_create"); } pthread_join (pt_1, NULL); pthread_join (pt_2, NULL); return 0; } static void pthread_func_1 (void) { while (1) { int i = 0; for (; i < 6; i++) { printf ("This is pthread_1.\n"); if (i==2) { pthread_cond_signal(&cond); return 0; } } } } static void pthread_func_2 (void) { while(1) { int rc; pthread_mutex_lock(&mtx); rc = pthread_cond_wait(&cond, &mtx); if(rc ==0) { int i = 0; for (; i < 3 ; i ++) { printf ("This is pthread_2.\n"); } pthread_mutex_unlock(&mtx); } } }
中间的那个互斥锁和条件信号限于篇幅我不讲了(可能下一篇会说),
,你会发现所谓多线程就是如此,而内核态多线程复杂得多,因为内核时刻都在运行,内核态的进程必须自己释放CPU,而有个特殊的内核执行流要深入研究,那就是中断,一个进程遇到中断时CPU会直接跳到内核态去运行相应的中断服务程序,这中间不像进程切换要调用switch_to保存诸如页基址,堆栈之类的,只用保存少量上下文(eip,cs之类的计数器),而在内核模块定义中断是这样定义的,中断的输入是硬件管脚,这是无规律且很必然事件,所以很多时候CPU必须优先处理(这个是我上一个驱动项目代码的一部分):
if((ret[0] = request_irq(61, axi_irq_handle1, IRQF_TRIGGER_RISING ,"axi_dev",NULL)) < 0) { printk(KERN_INFO "Request IRQ Failed with %d\n",ret[0]); }其中的参数61是管脚号,第二个参数是中断服务程序函数入口,第三个指定触发沿方式,第四个是驱动类名,最后一个一般为空。学过微机原理的同学们应该知道,很多芯片采用中断标志位用来判断中断类型,多核CPU用APIC来确定每一个中断信号能传到每个CPU,而intel采用了中断向量号来一一对应中断,(细节同见UTL)我在这举例就是想告诉你什么叫做内核流,内核流的轨道是像镀金一样高级轨道,当一个CPU司机开的进程车跑到这个地方就要小心了,很多恶意程序就是专门找你铺的轨道中这种地方利用各种手段达到劫车的地步,就是把你的内核CPU老司机劫持了,
控制它走向它想走的地方,好了限于时间,这篇到此结束,下一篇我有空我将继续升入CPU司机部分,调度,内核竞争之类的。
相关文章推荐
- 窥探 kernel,just for fun --- 分析sys_reboot
- Linus Torvalds——Just for Fun
- [Eclipse笔记]Just for fun – 在Eclipse下编译和运行C#的代码
- 窥探 kernel,just for fun --- 动手添加系统调用(ARM)
- Start game physics reading, just for fun
- just for fun
- 推荐阅读:Just for Fun --- Linus Torvalds 自传
- 尺度不变特征变换匹配算法详解Scale Invariant Feature Transform(SIFT) Just For Fun
- just for fun
- Just for fun----zjfc 并查集操作
- "SlideWindow", just for fun
- 世界上至少有十种东西是你不知道的!(Just For Fun)
- 使用内核模块hook内核系统调用(just for fun)
- 小小钢琴 囧。。。just for fun
- integer addition in terms of bit operators, just for fun
- linus 的十大名言(real or not , i dont know, just for fun ... )
- 玲珑杯 round#5 J just for Fun
- 一个简单的ShellCode执行器&代码(just for fun)
- just for fun
- 窥探 kernel,just for fun --- task_struct