Linux Debugging(三): C++函数调用的参数传递方法总结(通过gdb+反汇编)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介:

         上一篇文章《Linux Debugging:使用反汇编理解C++程序函数调用栈》没想到能得到那么多人的喜爱,因为那篇文章是以32位的C++普通函数(非类成员函数)为例子写的,因此只是一个特殊的例子。本文将函数调用时的参数传递方法进行一下总结。总结将为C++普通函数、类成员函数;32位和64位进行总结。

        建议还是读一下Linux Debugging:使用反汇编理解C++程序函数调用栈,这样本文的结论将非常容易理解,将非常好的为CoreDump分析开一个好头。而且,它也是32位C++ 普通函数的调用的比较好的例子,毕竟从汇编的角度,将参数如何传递的进行了比较好的说明。

1. 32位程序普通函数

普通函数的意思是非class member function

void func2(int a, int b)
{
  a++;
  b+ = 2;
}

int main()
{
  func2( 1111, 2222);
  return 0;
}

main函数的汇编:

main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    $2222, 4(%esp)
        movl    $1111, (%esp)
        call    func2(int, int)
        movl    $0, %eax
        leave
        ret

1111是第一个参数,放到了esp指向的地址。2222是第二个参数,放到了高地址。因次我们可以知道,在函数func2中,通过ebp+8可以访问到第一个参数1111,通过ebp+12可以访问到第二个参数2222。

func2(int, int):
        pushl   %ebp
        movl    %esp, %ebp
        addl    $1, 8(%ebp)
        addl    $2, 12(%ebp)
        popl    %ebp
        ret

下面我们使用gdb通过ebp打印一下传入的参数:

anzhsoft@ubuntu:~/linuxDebugging/parameter$ gdb a.out 
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) b func2
Breakpoint 1 at 0x8048597: file m32noclass.cpp, line 6.
(gdb) r
Starting program: /home/anzhsoft/linuxDebugging/parameter/a.out 

Breakpoint 1, func2 (a=1111, b=2222) at m32noclass.cpp:6
warning: Source file is more recent than executable.
6	  a++;
(gdb) p *(int*)($ebp+8)
$1 = 1111
(gdb) p *(int*)($ebp+12)
$2 = 2222

总结:

1. 参数通过栈查传递,底地址传递从左边开始的第一个参数

2. 使用gdb可以很方便打印传入参数

(gdb) p *(int*)($ebp+8)
$1 = 1111
(gdb) p *(int*)($ebp+12)
$2 = 2222

其实32位的程序也可以使用寄存器传递参数。请看下一节。

2. 32位普通函数-通过寄存器传递参数

可以使用GCC的扩展功能__attribute__使得参数传递可以使用寄存器。

修改第一节的函数:

#define STACKCALL __attribute__((regparm(3))) 
void STACKCALL func4(int a, int b, int c, int d)
{
  a++;
  b += 2;
  c += 3;
  d += 4;
}

int main()
{
  func4(1111, 2222, 3333, 4444);
  return 0;
}

__attribute__((regparm(3)))意思是使用寄存器

anzhsoft@ubuntu:~/linuxDebugging/parameter$ gdb a.out 
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) b main
Breakpoint 1 at 0x80485bb: file m32noclass.cpp, line 14.
(gdb) r
Starting program: /home/anzhsoft/linuxDebugging/parameter/a.out 

Breakpoint 1, main () at m32noclass.cpp:14
14      func4(1111, 2222, 3333, 4444);
(gdb) s
func4 (a=1111, b=2222, c=3333, d=4444) at m32noclass.cpp:6
6      a++;
(gdb) i r
eax            0x457    1111
ecx            0xd05    3333
edx            0x8ae    2222
ebx            0xb3eff4    11792372
esp            0xbf8e9580    0xbf8e9580
ebp            0xbf8e958c    0xbf8e958c
esi            0x0    0
edi            0x0    0
eip            0x80485a3    0x80485a3 <func4(int, int, int, int)+15>
eflags         0x282    [ SF IF ]
cs             0x73    115
ss             0x7b    123
ds             0x7b    123
es             0x7b    123
fs             0x0    0
gs             0x33    51
(gdb) p *(int*)($ebp+8)
$1 = 4444
(gdb) 


可以看到,前三个参数分别通过eax/edx/ecx传递,第四个参数通过栈传递。这种传递方式被称为fastcall

3. 32位 class member function 参数传递方式

我们通过宏USINGSTACK强制使用栈来传递参数。

#define USINGSTACK __attribute__((regparm(0)))
class Test
{
public:
  Test():number(3333){}
  void USINGSTACK func2(int a, int b)
  {
    a++;
    b += 2;
  }
private:
  int number;
};

int main(int argc, char* argv[])
{
  Test tInst;
  tInst.func2(1111, 2222);
  return 0;
}

通过gdb来打印传入的参数:

anzhsoft@ubuntu:~/linuxDebugging/parameter$ gdb a.out 
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) b main
Breakpoint 1 at 0x804859d: file m32class.cpp, line 18.
(gdb) r
Starting program: /home/anzhsoft/linuxDebugging/parameter/a.out 

Breakpoint 1, main (argc=1, argv=0xbf98a454) at m32class.cpp:18
18	  Test tInst;
(gdb) n
19	  tInst.func2(1111, 2222);
(gdb) s
Test::func2 (this=0xbf98a39c, a=1111, b=2222) at m32class.cpp:9
9	    a++;
(gdb) p *(int*)($ebp+12)
$1 = 1111
(gdb) p *(int*)($ebp+16)
$2 = 2222
(gdb) p *this
$3 = {number = 3333}

可以看到,class成员函数的第一个参数是对象的指针。通过这种方式,成员函数可以和非成员函数以类似的方式进行调用。

4. x86-64 class member function 的参数传递

在x86-64中,整形和指针型参数的参数从左到右依次保存到rdi,rsi,rdx,rcx,r8,r9中。浮点型参数会保存到xmm0,xmm1……。多余的参数会保持到栈上。

下面这个例子将传递九个参数。可以通过它来验证一下各个寄存器的使用情况:

class Test
{
public:
  Test():number(5555){}
  void func9(int a, int b, int c, int d,char*str, long e, long f, float h, double i)
  {
    a++;
    b += 2;
    c += 3;
    d += 4;
  }
private:
  int number;
};

int main(int argc, char* argv[])
{
  Test tInst;
  tInst.func9(1111, 2222, 3333, 4444, "hello, world!", 6666,7777, 8.888, 9.999);
  return 0;
}

下面通过gdb验证各个寄存器的使用情况:

khawk-dev-zhanga12:~/study/c++callstack # gdb a.out
GNU gdb (GDB) SUSE (7.0-0.4.16)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-suse-linux".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/study/c++callstack/a.out...done.
(gdb) b main
Breakpoint 1 at 0x40071b: file m64class.cpp, line 19.
(gdb) r
Starting program: /root/study/c++callstack/a.out
Breakpoint 1, main (argc=1, argv=0x7fffffffe188) at m64class.cpp:19
19        Test tInst;
(gdb) n
20        tInst.func9(1111, 2222, 3333, 4444, "hello, world!", 6666,7777, 8.888, 9.999);
(gdb) s
Test::func9 (this=0x7fffffffe0a0, a=1111, b=2222, c=3333, d=4444, str=0x400918 "hello, world!", e=6666, f=7777, h=8.88799953, i=9.9990000000000006)
    at m64class.cpp:8
8           a++;
(gdb) info reg
rax            0x400918 4196632
rbx            0x400830 4196400
rcx            0xd05    3333
rdx            0x8ae    2222
rsi            0x457    1111
rdi            0x7fffffffe0a0   140737488347296
rbp            0x7fffffffe070   0x7fffffffe070
rsp            0x7fffffffe070   0x7fffffffe070
r8             0x115c   4444
r9             0x400918 4196632
r10            0xffffffffffffffff       -1
r11            0x7ffff733d890   140737340758160
r12            0x400620 4195872
r13            0x7fffffffe180   140737488347520
r14            0x0      0
r15            0x0      0
rip            0x4007ff 0x4007ff <Test::func9(int, int, int, int, char*, long, long, float, double)+35>
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fctrl          0x37f    895
fstat          0x0      0
ftag           0xffff   65535
fiseg          0x0      0
fioff          0x0      0
foseg          0x0      0
fooff          0x0      0
fop            0x0      0
mxcsr          0x1f80   [ IM DM ZM OM UM PM ]
(gdb) p *this
$1 = {number = 5555}
(gdb) p *(char*)$r9@13
$2 = "hello, world!"
(gdb) p *(int*)($rbp+16)
$4 = 6666
(gdb) p *(int*)($rbp+24)
$5 = 7777
(gdb) p *(int*)$rdi
$6 = 5555

r9存储的是指针型char *str的字符串。因为寄存器只能存储6个整形、指针型参数,注意,Test 对象的指针占用了一个。因此
参数e和f只能通过栈传递。注意每个地址空间占8个字节。因此rbp+2*8存储的是e,rbp+3*8存储的是f。

float h是通过xmm0,double i是通过xmm1传递的。这类寄存器的size大小是128bits,当然128个bits可以不填满。GDB将这些寄存器看成下面这些数据的联合:

union{
  float   v4_float[4];
  double  v2_double[2];
  int8_t  v16_int8[16];
  int16_t v8_int16[8];
  int32_t v4_int32[4];
  int64_t v2_int64[2];
  int128_t unit128;
}xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7;

打印方式如下:

(gdb) p $xmm0.v4_float[0]
$7 = 8.88799953
(gdb) p $xmm1.v2_double[0]
$8 = 9.9990000000000006

总结:

在x86-64中,整形和指针型参数的参数从左到右依次保存到rdi,rsi,rdx,rcx,r8,r9中。浮点型参数会保存到xmm0,xmm1……。多余的参数会保持到栈上。

5. 总结

32位:

  1)默认的传递方法不使用寄存器,使用ebp+8可以访问第一个参数,ebp+16可以访问第二个参数。。。

  2) 可以使用GCC扩展__attribute__将参数放到寄存器上,依次使用的寄存器是eax/edx/ecx

  3)对于class 的member function,调用时第一个参数为this(对象指针)地址。因此函数的第一个参数使用ebp+16才可以访问到。

  4)ebp存储的是上层的bp的地址。ebp+4存储的是函数返回时的地址指令。

64位:

  1)默认的传递方法是使用寄存器。当然也可以强制使用栈,方式:函数声明时使用

__attribute__((regparm(0)))
  2)整形和指针型参数的参数从左到右依次保存到rdi,rsi,rdx,rcx,r8,r9中。浮点型参数会保存到xmm0,xmm1……。多余的参数会保持到栈上。

  3)xmm0……是比较特殊的寄存器,访问内容时需要注意。


尊重原创,转载请注明出处: anzhsoft http://blog.csdn.net/anzhsoft/article/details/18739193

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
29天前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
86 0
|
29天前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
66 1
Linux C/C++之IO多路复用(aio)
|
26天前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
207 3
|
29天前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
23 0
Linux C/C++之线程基础
|
29天前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
22 0
Linux C/C++之IO多路复用(poll,epoll)
|
29天前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
35 0
Linux C/C++之TCP / UDP通信
|
29天前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
26 0
Linux c/c++之IPC进程间通信
|
29天前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
20 0
Linux c/c++进程间通信(1)
|
29天前
|
Linux C++
Linux c/c++之进程的创建
这篇文章介绍了在Linux环境下使用C/C++创建进程的三种方式:system函数、fork函数以及exec族函数,并展示了它们的代码示例和运行结果。
30 0
Linux c/c++之进程的创建
|
29天前
|
Linux C++
Linux c/c++进程之僵尸进程和守护进程
这篇文章介绍了Linux系统中僵尸进程和守护进程的概念、产生原因、解决方法以及如何创建守护进程。
17 0