Chinaunix首页 | 论坛 | 博客
  • 博客访问: 466978
  • 博文数量: 62
  • 博客积分: 1742
  • 博客等级: 中尉
  • 技术积分: 859
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-06 00:13
个人简介

这是一句很长很长而且又很啰嗦并且很无聊的废话...

文章分类

全部博文(62)

文章存档

2013年(1)

2012年(13)

2011年(48)

分类: C/C++

2011-05-31 21:05:47

     照例,先贴代码,再说明,再总结!随便起个例子的.
  1. #include <stdio.h>
  2. int & kiss(int&,int);
  3. double kiss2(char *o,int *t,int &s);
  4. int main(){
  5.     printf("dddd\n");
  6.     int k=10;
  7.     int wy = kiss(k,20);
  8.     printf("%d\n",wy);
  9.     kiss2(0,0,wy);
  10.     return 0;
  11. }
  12. int& kiss(int& i,int j){
  13.     i++;
  14.     return i;
  15. }
  16. double kiss2(char *o,int *t,int &s){
  17.     double res = 12.332;
  18.     return res;
  19. }
test.cpp
----------------------------------------------------------------------
附上 objdump 出来的汇编
两头省掉一万行代码...直接进主题
----------------------------------------------------------------------
080484f4
:
/
 80484f4: 55                    push   %ebp
 80484f5: 89 e5                 mov    %esp,%ebp
\_上面两句分别为保存调用者的栈帧指针和激活本函数的栈帧指针.
/
 80484f7: 83 e4 f0              and    $0xfffffff0,%esp
\_栈帧对齐.
/
 80484fa: 83 ec 20              sub    $0x20,%esp
\_申请本函数的局部变量栈空间,包括(局部变量int k,int wy和本函
数调用的子函数中参数个数或长度最大的一个所需的参数压栈空间),
在本例为kiss2函数.这个跟调用约定也有关系,C/C++默认均为_cdecl,
规则为调用子程序的参数栈由调用者自行平衡,子程序只需平衡子程序
内用到的寄存器和局部变量,调用子子程序的最长参数压栈空间(以此
规则递归...),其他的例如参数压栈顺序从右到左的就不多说了.
/
 80484fd: c7 04 24 50 86 04 08  movl   $0x8048650,(%esp)
\_printf("dddd\n");这句话的第一个参数,在C++里变成了puts,
$0x8048650为只读字符串首地址.在这里顺便说下,如上所说,本函数已
经申请了函数内调用到的参数长度最长的子函数参数栈空间,所以可以用
自esp打上的栈空间作为参数调用函数内的任何子函数.
/
 8048504: e8 17 ff ff ff        call   8048420 <>
\_调用之...
/
 8048509: c7 44 24 1c 0a 00 00  movl   $0xa,0x1c(%esp)
 8048510: 00
\_0x1c(%esp)为局部变量int k=10;,并进行初始化.
/
 8048511: c7 44 24 04 14 00 00  movl   $0x14,0x4(%esp)
 8048518: 00
\_kiss(k,20);函数调用从右至左的第一个参数.
/
 8048519: 8d 44 24 1c           lea    0x1c(%esp),%eax
 804851d: 89 04 24              mov    %eax,(%esp)
\__kiss(k,20);函数调用从右至左的第二个参数,因为是引用传递,所以
实质上还是传递局部变量int k的地址.
/
 8048520: e8 3f 00 00 00        call   8048564 <_Z4kissRii>
\_调用之...
/
 8048525: 8b 00                 mov    (%eax),%eax ;这里留个疑问,首先结果是正确的,lea 0x1c(%esp),%eax这句拿的是SS段的偏移,mov (%eax),%eax这句操作的是ds段,用同一个偏移...望高手指点.
 8048527: 89 44 24 18           mov    %eax,0x18(%esp)
\_因为kiss返回一个引用值,所以实质还是返回一个地址,并访问这个
地址,取值赋给局部变量int wy(0x18(%esp)),随便说下,函数一般式
用eax作为返回值的.
/
 804852b: 8b 44 24 18           mov    0x18(%esp),%eax
 804852f: 89 44 24 04           mov    %eax,0x4(%esp)
 8048533: c7 04 24 55 86 04 08  movl   $0x8048655,(%esp)
\_分别初始化printf("%d\n",wy);函数调用的第一个和第二个形参,
把int wy赋给第二个参数,把字符串首地址$0x8048655赋给第一个参数.
/
 804853a: e8 d1 fe ff ff        call   8048410 <>
\_调用之...
/
 804853f: 8d 44 24 18           lea    0x18(%esp),%eax
 8048543: 89 44 24 08           mov    %eax,0x8(%esp)
 8048547: c7 44 24 04 00 00 00  movl   $0x0,0x4(%esp)
 804854e: 00
 804854f: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
\_真是太忙了,printf刚调用完,马上又为调用kiss2作准备...继续说明之,
kiss2右边第一个参数为引用形参,地址传之...右起第一个和第二个都为0,
直接赋值之!
/
 8048556: e8 1e 00 00 00        call   8048579 <_Z5kiss2PcPiRi>
\_调用之...
/
 804855b: dd d8                 fstp   %st(0)
\_浮点型的返回值,把结果放到浮点寄存器中...
/
 804855d: b8 00 00 00 00        mov    $0x0,%eax
\_上面说过了,一般情况下eax是作为函数返回值.
/
 8048562: c9                    leave 
 8048563: c3                    ret
\_这两个...厄...先贴下原意,leave 和 ret ->  mov esp,ebp
                                            pop ebp
                                            sub esp, -4
                                            jmp dword [esp-4]
至此,又一胎...
下面的两个子函数就不再继续说明了,也是按照如上规则递归之...
总结下:cdecl的风格是自己搞大别人肚子的,就要自己负责,如果不
是你的(传进来的参数),打死也不要揽这个担子上身!stdcall则
比较多情,不管是自己的还是别人的,照揽(一般形式为在最后的ret
后面加上本函数参数所需的栈空间大小,例如 ret 8.),对了顺便
提下,C++里的非静态成员函数调用规则大致如上,唯独this指针是通
过ecx传进函数的.
08048564 <_Z4kissRii>:
 8048564: 55                    push   %ebp
 8048565: 89 e5                 mov    %esp,%ebp
 8048567: 8b 45 08              mov    0x8(%ebp),%eax
 804856a: 8b 00                 mov    (%eax),%eax
 804856c: 8d 50 01              lea    0x1(%eax),%edx
 804856f: 8b 45 08              mov    0x8(%ebp),%eax
 8048572: 89 10                 mov    %edx,(%eax)
 8048574: 8b 45 08              mov    0x8(%ebp),%eax
 8048577: 5d                    pop    %ebp
 8048578: c3                    ret   
08048579 <_Z5kiss2PcPiRi>:
 8048579: 55                    push   %ebp
 804857a: 89 e5                 mov    %esp,%ebp
 804857c: 83 ec 10              sub    $0x10,%esp
 804857f: dd 05 60 86 04 08     fldl   0x8048660
 8048585: dd 5d f8              fstpl  -0x8(%ebp)
 8048588: dd 45 f8              fldl   -0x8(%ebp)
 804858b: c9                    leave  
----------------------------------------------------------------------

另外,下面是在网上找到的一点资料,让自己长点记性.
Enter -> push ebp
         mov ebp,esp
    
Leave -> mov esp,ebp
         pop ebp
 
ret指令宏
macro ret AddEspNum
{
 if ~ AddEspNum eq 0 ; 如果无参数,则恢复EIP所占栈,然后跳.否则先恢复传进来大小+EIP占大小,再跳.有参的时候貌似还判断了CPU是多少位的.
  if (AddEspNum mod 4)<>0 ; 32-bit cpu
   .err
  end if
  lea esp, [esp+(AddEspNum+4)]
  jmp dword [esp-(AddEspNum+4)] ; skip AddEspNum bits stack value, and jump to next instruction
 else
  sub esp, -4
  jmp dword [esp-4] ; no return value, jump to next instruction
 end if
}
 
     针对上面的一个疑问,经高人指点,疑惑解开.顺便贴下过程:
  1. #include <stdio.h>

  2. int main(char argc,char **argv){
  3.     int cs=0,ds=0,ss=0;
  4.     __asm__ __volatile__("movl $0,%%eax;\
  5.                           movw %%cs,%%ax;\
  6.                           movl %%eax,%0;\
  7.                           movl $0,%%eax;\
  8.                           movw %%ds,%%ax;\
  9.                           movl %%eax,%1;\
  10.                           movl $0,%%eax;\
  11.                           movw %%ss,%%ax;\
  12.                           movl %%eax,%2;"
  13.             :"=r"(cs),"=r"(ds),"=r"(ss)
  14.             :);
  15.     printf("CS:%d DS:%d SS:%d\n",cs,ds,ss);
  16.     return 0;
  17. }

打印结果为: CS:115 DS:123 SS:123  这就能解释上面用同一偏移地址,在不同段选择子的同一结果.

阅读(4939) | 评论(4) | 转发(0) |
给主人留下些什么吧!~~

czysocket_dara2011-06-02 13:08:51

GFree_Wind: x86的平台,有段寄存器,但是几乎等于没用。因为所有的段寄存器的起始地址都是0,limit都是4G。可以看一下linux内存管理.....
是的,多谢大侠指点,我昨晚也回去翻了下内核书,确实也提到了,x86遗留下来的段,为了兼容所以...
      于是,早上也做了个小实验,把代码贴下:

#include <stdio.h>
int main(char argc,char **argv){
    int cs=0,ds=0,ss=0;
    __asm__ __volatile__("movl $0,%%eax;\
               &nbs

GFree_Wind2011-06-01 23:09:53

czysocket_dara: 首先谢谢你的回复,这是X86哦,不是ARM...你的意思是CS DS SS哪个没用上,还是都没用上,有有关这些资料说明的连接吗?
     俺菜鸟求大侠指点~~~.....
x86的平台,有段寄存器,但是几乎等于没用。因为所有的段寄存器的起始地址都是0,limit都是4G。可以看一下linux内存管理

czysocket_dara2011-06-01 21:32:24

GFree_Wind: 8048525: 8b 00                 mov    (%eax),%eax ;这里留个疑问,首先结果是正确的,lea 0x1c(%esp),%eax这句拿的是SS段的偏移,mov (%eax),%eax这句操作的是d.....
首先谢谢你的回复,这是X86哦,不是ARM...你的意思是CS DS SS哪个没用上,还是都没用上,有有关这些资料说明的连接吗?
     俺菜鸟求大侠指点~~~

GFree_Wind2011-06-01 12:20:39

8048525: 8b 00                 mov    (%eax),%eax ;这里留个疑问,首先结果是正确的,lea 0x1c(%esp),%eax这句拿的是SS段的偏移,mov (%eax),%eax这句操作的是ds段,用同一个偏移...望高手指点.
--------------------------
这里跟段没有什么关系,linux下段根本没有起作用。其实mov (%eax), %eax这句的意思,你下面自己基本上也说清楚了。%eax为函数的返回值,kiss的返回类型,实际上就是返回地址,那么%eax存的是i的地址。为了将i赋给wy,那么首先通过mov (%eax), %eax,将i的值取出存放到%eax中,然后下一句汇编,将%eax的值赋给xy。