C语言基础知识汇编分析
来源:互联网 发布:虫哥耳机 知乎 编辑:程序博客网 时间:2024/06/05 07:20
1.静态、动态存储变量的区别
C语言源码:
fun()
{
int i = 0x100;
static int c = 0x200;
i = c;
}
汇编代码:
push bp
mov bp, sp
push si
mov si, 100h ;定义动态变量 i = 0x100
mov si, ds:[0000] ;对静态变量赋值i = c;ds:[0000]内存储的是静态变量C
pop si
pop bp
retn
总结:其实静态变量类似于全局变量,实现方式类似。C语言源码:
void change(int[]);
fun()
{
int a[] = {1,2};
add(a);
}
void change(int a[])
{
int tmp;
tmp=a[0];
a[0]=a[1];
a[1]=tmp;
}
汇编代码:
push bp
mov bp, sp
sub sp, 4 ;开辟堆栈空间,保存数组数据
push ss
lea ax, [bp-4]
push ax ;将es:di压栈
mov ax,0
push ax
push ds ;将ds:si压栈
mov cx,4 ;设置数组数据所占字节数,即需复制字节数
call sub_100C7
lea ax, [bp-4] ;此命令就是将数组的首地址传给调用的函数
push ax ;将数组的首地址压栈传递给子程序change
call sub_100C6
pop cx ;平衡堆栈
mov sp, bp
pop bp
retn
sub_100C6: ;change函数
push bp
mov bp, sp
push si
push di ;tmp局部变量
mov si, [bp+4] ;将数组首地址读入si寄存器
mov di, [si] ;si保存的是一个地址,这个地址中存储就是a[0],即指针 tmp = a[0]
mov ax, [si+2] ;ax = a[1]
mov [si], ax ;a[0]=ax a[0]=a[1] 通过ax寄存器中转
mov [si+2],di ;a[1]=tmp
pop di
pop si
pop bp
retn
sub_100C7: ;复制数据段数组数据至开辟的堆栈空间中
push bp
mov bp, sp
push si
push di
push ds
lds si, [bp+6]
les di, [bp+0Ah]
cld
shr cx, 1
rep movsw
adc cx, cx
rep movsb
pop ds
pop di
pop si
pop bp
retf 8
总结:传递数组其实就是传递数组的首地址,可以理解为传递的是数组指针,对数组的操作,将会影响数组中的数据。
这个的内容比较多,有点重量级。呵呵。。
C语言源码:
int add(int,int);
fun()
{
int a = 0x10;
int b = 0x100;
int c = add(a,b);
}
int add(int a,int b)
{
int c;
c = a + b;
return c;
}
汇编程序代码:
push bp
mov bp, sp 压栈bp,bp寄存器将在后续部分作为基址寄存器使用
sub sp, 2 在堆栈中分配空间给变量C
push si
push di
mov si, 10h 变量a=0x10
mov di, 100h 变量b=0x100
push di
push si 压栈b,a做为参数传递给add函数
call sub_100B0
pop cx
pop cx 平衡堆栈,pop cx机器指令较短,执行效率高,相当于add sp,4
mov [bp-2], ax 将返回值付给变量C,C语言中通过ax寄存器返回函数值
pop di
pop si
mov sp, bp
pop bp
retn 至此函数fun返回
sub_100B0:
push bp
mov bp, sp
push si
mov si, [bp+4] c = a
add si, [bp+6] c = a + b
mov ax, si 将结果传给ax寄存器
pop si
pop bp
retn add子程序返回
总结:函数进入时基本上都有push bp,mov bp,sp语句,此两条语句将bp寄存器压栈,过后做为基址寄存器使用。
调用函数时,先将参数从右到左依次压栈,执行call指令时,将ip压栈,接着将bp压栈,所以在16位的CPU中,取参数时,bp+4取得第一个参数,bp+6取得第二个参数,依次类推。局部变量使用使用sub sp,2指令在堆栈中开辟空间存储,所以取局部变量时,通过bp-2取得第一个局部变量,bp-4取得第二个局部变量,函数若有返回值一般通过ax寄存器返回。最后要执行mov sp,bp,pop bp指令平衡局部变量占用的堆栈空间,实际的参数并未清空。Ret后弹出ip值,返回调用程序执行的地址。
一般情况下由调用函数的程序平衡堆栈,所以上例pop cx执行两次,相当于add sp,4平衡堆栈。但数组中传送字符串的函数使用了ret n,由子程序本身进行了平衡堆栈的工作。
从上可以看到,在调用函数时,调用参数并未在子函数中更改,即在子函数中使用了参数值,但并未真正更改参数的值。使用指针做为参数时的情况,后续再研究。4.数组的定义使用
C语言程序:
int x[] = {1,2};
int i = x[0]+x[1];
汇编程序代码:
Push bp
mov bp, sp
sub sp, 6 用到三个局部变量,其中数组2个,所以分配6个字节的堆栈空间
push ss
lea ax, [bp-6]
push ax
push ds
mov ax, 194h
push ax 以上压栈操作给子程序传递参数
mov cx, 4 数组占用字节长度
call SCOPY@ 注意:此处为远调用,压入CS、IP寄存器
mov ax, [bp-6] ax寄存器内容x[1]
add ax, [bp-4] x[0]+x[1]
mov [bp-2], ax 将结果付给[bp-2],即变量i
mov sp, bp
pop bp
retn 以上语句为返回子程序
子程序SCOPY@
push bp
mov bp, sp
push si
push di
push ds 保存子程序中用到的寄存器
lds si, [bp+6] ds:si为调用程序中的DS,Ax的值,初始化字符串复制源地址
les di, [bp+0Ah]es:di为堆栈空间的地址
cld
shr cx, 1
rep movsw 将数据段中定义的数组内容复制到堆栈中分配的空间
adc cx, cx
rep movsb 此处有疑问,不太清楚。
pop ds
pop di
pop si
pop bp
retf 8 堆栈平衡,清空子程序调用前压入的参数。
总结:C语言处理数组是通过在数据段定义数组内容,并且一般以16字节为单位,若不够则用“0”补齐,然后将DS及数组偏移地址,SS及堆栈开始地址压入堆栈供子程序调用,子程序负责将数据段中定义的数组内容复制到堆栈中。
一定要注意复制数组内容的子程序为远调用,曾经在此处分析时,出现问题。
多维数组其实就是按行存放,先放第一行的元素,再存放第二行的元素。
例:int i[2][2]内存中为:i[0][0],i[0][1],i[1][0] ,i[1][1]。
5.C语言的各种循环语句
1、While循环
C语言程序:
int x = 1;
while(x<10)
{
x++;
}
对应的汇编语句:
push bp
mov bp, sp
push si
mov si, 1
jmp short loc_1009C
loc_1009B: inc si
loc_1009C:
cmp si,0Ah 判断x是否大于10
jl short loc_1009B 小于则跳至inc si,x++
pop si
pop bp
retn
理解:先判断while语句后的条件,若是真,则执行后续语句,若为假则退出。
2、for循环
C语言程序:
int x = 0;
int i;
for(i=0;i<10;i++)
{
x++;
}
对应的汇编语句:
push bp
mov bp, sp
push si
push di
xor di,di 设置x=0
xor si,si 设置i=0
jmp short loc_1009F
loc_1009D:
inc di
inc si
loc_1009F:
cmp si,0Ah 比较i是否大于10
jl short loc_1009D
pop di
pop si
pop bp
retn
理解:先判断条件,若条件为真,则执行i++,执行下列语句;若条件为假,则退出循环。
3、do while循环
C语言程序:
int x = 1;
do
{
x++;
}
while (x<10);
对应的汇编语句:
push bp
mov bp, sp
push si
mov si,1
loc_10099:
inc si
cmp si,0Ah
jl short loc_10099
pop si
pop bp
retn
理解:先执行后续语句,后判断条件。换句话说,后续语句最少可以执行一次。
4、break,continue语句
理解:break语句用于switch或循环中,退出循环;continue语句用于结束此次循环Switch语句
C语言程序:
int x = 1;
int y = 10;
switch(x)
{
case 1: y = 0x100;
case 2: y = 0x101;
case 3: y = 0x102;
default: y = 0x110;
}
汇编程序:
push bp
mov bp, sp
push si
push di
mov di, 1
mov si, 0Ah
mov ax,1 将di(即变量x)赋给ax
cmp ax,1 case 1,x与1比较
jz short loc_100B0 y=0x100
cmp ax,2 case 2,x与2比较
jz short loc_100B3 y=0x101
cmp ax,3 case 3,x与3比较
jz short loc_100B6 y=0x102
jmp short loc_100B9 y=0x110
loc_100B0: mov si,100h
loc_100B3: mov si,101h
loc_100B6: mov si,102h
loc_100B9:
mov si,110h
pop di
pop si
pop bp
retn
总结:switch语句将会转变为类似多个if语句的结构。以下为上面的switch语句对应的C语言if语句描述,若不加break语句,则每条if语句顺序执行。
If(x==1) y=0x100;
If(x==2) y=0x101;
If(x==3) y=0x102;
Y=0x103
If语句
C语言程序:
int x = 1;
int y = 10;
if( x > y)
x=0x10;
else
{
if(x==y)
y = 0x100;
else
y =0x102;
}
汇编程序:
push bp
mov bp, sp
push si
push di
mov di, 1
mov si, 0Ah
cmp di, si
jle short loc_100A6 若x<=y则跳转至loc_100A6进入else部分
mov di, 10h x=0x10
jmp short loc_100B2
loc_100A6:
cmp di, si
jnz short loc_100AF
mov si, 100h
jmp short loc_100B2
loc_100AF:
mov si, 102h
loc_100B2:
pop di
pop si
pop bp
retn
一、逻辑运算符:
1、与运算符的使用
C语言程序:
int x = 1;
int y = 10;
if( x && y)
x = 0x10;
else
y = 0x100;
对应的汇编语句:
push bp
mov bp, sp
push si
push di
mov si, 1
mov di, 0Ah
or si, si 求或运算,若为零则跳转至y=0x100
jz short loc_100AA
or di, di 求或运算,若为零则跳转至y=0x100
jz short loc_100AA
mov si, 10h
jmp short loc_100AD
loc_100AA:
mov di, 100h
loc_100AD:
pop di
pop si
pop bp
retn
注意:与运算时,只要有一个为0则结果就为0,所以判断x是否为0,为0则跳转至y=0x100;若不为零,再判断y是否为0,若为零则亦跳转至y=0x100。
2、或运算符的使用
C语言程序:
int x = 1;
int y = 10;
if( x || y)
x = 0x10;
else
y = 0x100;
对应的汇编语句:
push bp
mov bp, sp
push si
push di
mov si, 1
mov di, 0Ah
or si, si
jnz short loc_100A5 求或运算,若不为零则跳转至x=0x10
or di, di
jz short loc_100AA 求或运算,若为零则跳转至y=0x100
mov si, 10h
jmp short loc_100AD
loc_100A5:
mov si, 10h
jmp short loc_100AD
loc_100AA:
mov di, 100h
loc_100AD:
pop di
pop si
pop bp
retn
注意:求或运算时,先判断x是否为0,不为0则直接x=0x100;若为0,则再判断y是否为0,为0则直接y=0x100。
3、非运算符的使用
C语言程序:
int x = 1;
int y = 10;
if(!y)
x = 0x10;
else
y = 0x100;
对应的汇编语句:
push bp
mov bp, sp
push si
push di
mov si, 1
mov di, 0Ah
or di, di
jnz short loc_100A6 若y不为零则执行y=0x100
mov di, 10h 若为零则执行x=0x10
jmp short loc_100A9
loc_100A6:
mov si, 100h
loc_100A9:
pop di
pop si
pop bp
retn
注意:求非运算时,先判断y是否为0,若不为0非y后,结果为假,则直接y=0x100,不为0则直接x=0x100。
综上所述:可以看到TC编译器,处理逻辑运算符时,先判断左边第一个参数,后判断接下来的参数,所以在使用时应该将最有可能出现的情况放在左边做判断,效率会更高一些。1、大于号的使用
C语言程序:
int x = 1;
int y = 10;
if( x > y)
x = 0x10;
else
y = 0x100;
对应的汇编语句:
push bp
mov bp, sp
push si
push di
mov si, 1
mov di, 0Ah x=1;y=10对x,y赋值
cmp si, di 比较x,y
jle short loc_100A6 小于或等于转移至y=0x100
mov si,10h
jmp short loc_100A9 跳至返回部分代码
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
2、大于等于号的使用
C语言程序:
int x = 1;
int y = 10;
if( x >= y)
x = 0x10;
else
y = 0x100;
汇编关键部分:
cmp si, di
jl short loc_100A6 小于转移至y=0x100
mov si, 10h
jmp short loc_100A9
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
3、小于号的使用
C语言部分
if( x < y)
x = 0x10;
else
y = 0x100;
汇编关键部分:
cmp si, di
jge short loc_100A6 大于或等于转移至y=0x100
mov si, 10h
jmp short loc_100A9
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
4、小于等于的使用
C语言部分
if( x <= y)
x = 0x10;
else
y = 0x100;
汇编关键部分:
cmp si, di
jg short loc_100A6 大于转移至y=0x100
mov si, 10h
jmp short loc_100A9
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
5、 等于的使用
C语言部分
if( x == y)
x = 0x10;
else
y = 0x100;
汇编关键部分:
cmp si, di
jnz short loc_100A6 不等于则转移至y=0x100
mov si, 10h
jmp short loc_100A9
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
6、不等于的使用
C语言部分
if( x != y)
x = 0x10;
else
y = 0x100;
汇编关键部分:
cmp si, di
jz short loc_100A6 等于则转移至y=0x100
mov si, 10h
jmp short loc_100A9
loc_100A6:
mov di, 100h
loc_100A9:
pop di
pop si
pop bp
retn
小结:综上所述,TC编译器将关系运算符转变为相反的关系运算,保持了源程序的顺序,即相反的比较跳转至else部分,而正常的跳转至if后语句。
C语言程序:
int x = 1;
int y = 10;
int i = 0x99;
x++;
x = x + 1;
y--;
y = y - 1;
i += 1;
i += 2;
对应的汇编语句:
push bp
mov bp, sp
sub sp, 2
push si
push di
mov si, 1 变量x
mov di, 0Ah 变量y
mov [bp-2], 99h 变量i
inc si x++
mov ax, si
inc ax
mov si, ax x = x + 1
dec di y--
mov ax, di
dec ax
mov di, ax y = y - 1
inc [bp-2] i += 1
add [bp+var_2], 2 i +=2
pop di
pop si
mov sp, bp
pop bp
retn
后续还有更深入一些的研究。
C语言程序:
int a = 0x100;
int b = 0x200;
int c = 0x300;
int d = a * b + c + 'a';
对应的汇编语句:
push bp
mov bp, sp
sub sp, 4
push si
push di
mov si, 100h
mov di, 200h
mov [bp-4], 300h 变量C
mov ax, si
mul di a * b
add ax, [bp-4] a * b + c
add ax, 61h ; 'a' a * b + c + ‘a’
mov [bp-2], ax 将计算结果赋给d变量
pop di
pop si
mov sp, bp
pop bp
retn
写的都比较简单,但是原理都是一样的。
C语言程序:
int a = 0x100;
int b = 0x200;
int c,d,e,f,g;
c = a + b;
d = b - a;
e = a * b;
f = b / a;
g = b % a;
对应的汇编语句:
push bp
mov bp, sp
sub sp, 0Ah 分配堆栈
push si 保存函数中要用到的寄存器,C语言偏向使用si,di寄存器
push di
mov si, 100h
mov di, 200h
mov ax, si
add ax, di
mov [bp-8], ax c = a + b
mov ax, di
sub ax, si
mov [bp-6], ax d = b - a
mov ax, si
mul di
mov [bp-4], ax e = a * b
mov ax, di
cwd
idiv si
mov [bp-2], ax f = b / a
mov ax, di
cwd
idiv si
mov [bp-0Ah], dx g = b % a
pop di
pop si 恢复函数中使用的寄存器
mov sp, bp 恢复堆栈指针
pop bp
retn 返回程序pop ip
呵呵。。。写的比较简单,但是基本上反应了C语言编译器的意图。确实很佩服写TC2.0编译器的那些前辈,反汇编的代码精炼的很。C语言的执行效率就是通过简练的汇编代码保证的。
前言:最近学习了汇编语言,想通过反汇编TC2.0的程序,了解C语言的各项元素到底是如何实现的。先做TC2.0的研究,DOS下的C语言研究透彻了。打好基础,学完Windows编程后,再对VC6的东西进行反汇编。
变量:C语言的局部变量一般定义在堆栈中,不同的类型占用不同的字节数。
以下定义了一个int整型变量:
push bp 将bp寄存器压栈,在后续程序中做为基址寄存器使用。
mov bp, sp
sub sp, 2 在栈中分配空间存储局部变量
mov [bp-2], 100h 为局部变量赋值
mov sp, bp 恢复sp寄存器,将局部变量占用堆栈恢复,之前不能改变bp寄存器内容,否则程序无法正常返回
pop bp
retn 弹出ip寄存器内容,函数返回
a) 整型变量:long 之外其他的整型,均分配16位,占用两个字节;long整型占用32位,占用四个字节,一般会放在ax,dx寄存器中。
b) 浮点型变量:比较复杂,暂未分析。
c) 字符变量:使用8位(即一个字节)存储字符,但C语言编译器在栈中分配2个字节的存储空间(或许是因为分配两个字节效率高)。
指针的定义及存储方式
C语言源码:
f()
{
int i = 0x100;
int *p;
p= &i;
}
汇编代码:
enter 4, 0 ;分配四个字节空间存储局部变量
mov [bp-4], 100h ;int i = 0x100
lea ax, [bp-4] ;将变量i的地址赋给ax
mov [bp-2], ax ;int *p=&i
leave
retn
总结:指针即地址,指针变量存储的是i的地址。
C语言源码:
f()
{
int a = 0x100;
int b = 0x200;
int *p,*p1,*p2;
p1 = &a;
p2 = &b;
p = p1;
p1 = p2;
p2 = p;
}
汇编代码:
enter 6, 0
push si
push di
mov [bp-6], 100h ;变量a int a = 0x100;
mov [bp-4], 200h ;变量b int b = 0x200;
lea si, [bp-6] ;变量p1 int *p1 = &a,现si中存储的是变量a的地址
lea di, [bp-4] ;变量p2 int *p2 = &b,现di中存储的是变量b的地址
mov [bp-2], si ;p = p1
mov si, di ;p1 = p2
mov di, [bp-2] ;p2 = p1
pop di
pop si
leave
retn
总结:lea命令是取地址的操作命令。13.C语言的数组和指针的关系及++操作符的使用
C语言代码:
fun()
{
int i[] = {'a','b'};
int *p = i;
int i1= *++p;
}
汇编代码:
enter 6,0 ;开辟空间存储数组元素
push si
push ss
lea ax,[bp-6]
push ax
push ds
mov ax,0
push ax
mov cx,4
call 000A:0029 ;以上为数组元素复制函数准备参数
lea si,[bp-6] ;将数组首地址赋给si。即int p* = i
inc si
inc si ;以上两句完成++p
mov ax,[si]
mov [bp-2],ax ;以上两句完成int i1= *++p;
pop si
leave
ret
C语言代码:
fun()
{
char c[] = {'a','b'};
char *p = c;
char c1= *p++;
}
汇编代码:
enter 4,0 ;开辟空间存储数组元素
push si
push ss
lea ax,[bp-4]
push ax
push ds
mov ax,0
push ax
mov cx,2
call 000A:0029 ;以上为数组元素复制函数准备参数
lea si,[bp-4] ;将数组首地址赋给si。即char p* = c
mov al,[si] ;[bp-3]中存放字符串'b',[bp-2]未使用
mov [bp-1],al ;以上两句完成char c1= *p
inc si ;p++
pop si
leave
ret
总结:以上均省略了数组元素复制的代码。通过以上代码可以发现C语言编译器可以根据定义的指针的类型,对++操作进行指定的字节后移,char类型占用一个字节,则p++对应为inc si;int类型占用两个字节,则p++对应为inc si两次。
指针多使用lea命令将地址赋值给相应的指针变量。另外可以看到p++,++p的差别,p++是先操作后加,++p为先加后操作
14.C语言的函数指针和指向函数的指针变量
C语言代码:
main()
{
int max();
int (*p)();
int a,b,c;
a = 0x10;
b = 0x20;
p = max;
c = (*p)(a,b);
}
max(int a,int b)
{
int z;
if(a > b) z = a;
else
z = b;
return b;
}
汇编代码:
push bp
mov bp, sp
sub sp, 4 ;分配变量c和b。其中c-[bp-2],b-[bp-4]
push si
push di
mov di, 10h ;变量a = 0x10
mov [bp-4], 20h ;变量b = 0x20
mov si, 21Eh ;p = max即函数的地址,此处21Eh为函数的地址
push [bp-4]
push di
call si ; sub_1021E ;压栈b,a跳转至si指向的函数的地址
pop cx
pop cx ;堆栈平衡
mov [bp-2], ax ;c = (*p)(a,b),一般函数通过ax返回值。
pop di
pop si
mov sp, bp
pop bp
retn
sub_1021E: ;max函数开始,地址为21Eh
push bp
mov bp, sp
push si
push di
mov si, [bp+6] ;变量b
mov ax, [bp+4] ;变量a
cmp ax, si ;if语句开始
jle short loc_10232 ;a <= b则 z = b,否则 z = a
mov di, [bp+4] ;z = a
jmp short loc_10234
loc_10232:
mov di, si ;z = b
loc_10234:
mov ax, si ;将返回值赋给ax寄存器
pop di
pop si
pop bp
retn
总结:函数的指针其实就是函数的开始地址。注意函数的参数必须一致,否则在调用实际的函数时,就会出现问题。
另外:C语言编译器发现局部变量较少时,使用di,si寄存器存储局部变量,这样可以提高程序的运行速度。
- C语言基础知识汇编分析
- 汇编基础知识 - [C/C++]
- c语言元素实现汇编分析
- C 语言函数返回结构体汇编分析
- c 语言调用汇编堆栈的详细分析
- 简单C语言程序的汇编分析1
- C语言程序的反汇编分析2
- 从汇编简要分析c语言函数调用栈
- C语言返回值为结构体的汇编分析
- C语言流程控制语句的反汇编分析
- c语言内部(汇编代码分析)函数调用过程探究
- 从汇编角度分析C语言的本质(一)
- C语言反汇编代码分析,理解计算机工作原理
- C语言三种循环反汇编分析
- c语言汇编
- C语言调用汇编
- c语言汇编
- 汇编和C语言
- 动态链接库和静态链接库的区别
- C数组和指针的区别
- C语言声明左右法则
- Shellcode的基本介绍
- Windows程序员需掌握的技术
- C语言基础知识汇编分析
- c语言元素实现汇编分析
- 指针的用法自解
- 理解java的内省与反射
- 让你不再害怕指针(1)
- 让你不再害怕指针(2)
- C/C++中的指针的应用及注意问题
- C语言指针问题快速解惑
- 深入认识 Turbo C 编译器