老码识途第2章笔记
庖丁解码:底层的力量与乐趣
1.全局变量或字符串常量等数据存放在程序的数据段
2.jmp的机器码是eb,jz的机器码是74
3.下面的代码会死循环?
void main(){
int i;
int a[10];
for (i=0;i<=10;++i){
a[i]=0;
printf("%d\n",i);
}
}
在debug版本的可执行文件中,i在ebp-4中,而a[10]也在ebp-4中,因为a只定义了长度为10,a[10]为第11个元素,将存放在i相同的位 置发生:a[10]=0导致i=0,i=0导致无限循环.
在release版本的可执行文件中,不会发生无限循环,因为在release版本中,i没有存到ebp-4中,对release版本来说,没有意义的代 码不要,于是没有必要给i分配内存,它就是个计数器,放esi中即可,也没必要给数组a分配内存,这个局部变量没有传递给其他函数 或做他用,a[i]=0根本没有意义,所以既没有为数组a分配内存,也没有生成a[i]=0的代码,因此不会产生死循环.
调试版不会随意改掉,kill掉你的代码.忠实原著才好调试.这也是为什么有时候调试版对但发行版错的原因,对于错的代码,发行版 和调试版不等价.如果用vs2008编译,则死循环在调试版下也不会发生.通过反汇编跟踪,a[10]地址比i地址低.a[10]的越界访问根 本覆盖不到i,而且a与i之间有许多似乎无用的填充字节
4.下面的代码应该怎么改才对?
void allocateInt(int *i){
int *pi=(int *)malloc(sizeof(int));
*pi=3;
*i=pi;
}
在函数内部中看到pi是int *
类型的指针(*pi=3
),而最后的*i=pi
意味着i是int **
类型的指针,也即指向int *
类型指针
的指针,但是函数的参数声明中声明的是int *i
,即声明i是int *
类型的指针,与函数内部的i的类型不同,于是这个函数有误,
可以通过修改参数的声明为int ** i
修正.
5.跨语种(如c与delphi之间)代码交互必须解决以下几点
a)如何将参数传递给函数?通过栈还是寄存器,如果是栈,那么从左压栈参数还是从右? b)如何平衡栈?调用方还是被调用方清理栈? c)返回值怎样返回?
6.对于代码来说,代码段和常量段的属性是只读的,如果代码尝试写代码段或常量段将发生错误
7.有时候源码虽然简单,但高级语言会暗地里加入许多指令.如果我们没有理解这背后的规则,那错误离我们就不远了.当你面对一 段源码百思不得其解时,用反汇编打开黑盒是前进的不二法门.
8.钩子有多种实现方法,如:修改函数入口指令,改为jmp到用户代码(也有不同实现风格);修改导入表函数入口地址等.
9.当发生异常时,操作系统将捕获异常,从fs:[0]指向的内存获取第一个异常处理块首地址,然后调用块第2字段指向的异常处理函 数,根据函数返回值决定其下一步动作,如是否继续调用下一个异常处理块处理异常(如当前块不处理则下一块将处理).每条try语 句会生产一个异常处理块,注册到操作系统(插入到异常处理链首).当整个try-catch语句结束时,语句负责从异常处理链中注销刚 才注册的块