Linux Debugging(三): C++函数调用的参数传递方法总结(通过gdb+反汇编)
上一篇文章《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
Linux Debugging(三): C++函数调用的参数传递方法总结(通过gdb+反汇编)的更多相关文章
- Ubuntu Linux系统三种方法添加本地软件库
闲着没事教教大家以Ubuntu Linux系统三种方法添加本地软件库,ubuntu Linux使用本地软件包作为安装源——转2007-04-26 19:47新手重新系统的概率很高,每次重装系统后都要经 ...
- 在linux代码中打印函数调用的堆栈的方法
之前一直有这样的需求,当时问到,也没搜到方法,现在竟然既问到了,也搜到了,哎,世事真是不能强求啊! 在Linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入: ...
- Linux启动新进程的几种方法汇总
有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些 ...
- Linux启动新进程的几种方法及比较
有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些 ...
- Linux启动新进程的几种方法及比较[转]
有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些 ...
- linux下查找某个文件位置的方法
一.通过文件名查找法: 举例说明,假设你忘记了httpd.conf这个文件在系统的哪个目录 下,甚至在系统的某个地方也不知道,则这是可以使用如下命令: find / -name httpd.conf ...
- linux多种安装包格式的安装方法
linux多种安装包格式的安装方法 一.rpm包安装方式步骤:1.找到相应的软件包,比如soft.version.rpm,下载到本机某个目录: 2.打开一个终端,su -成root用户: 3.cd s ...
- Linux系统产生随机数的3种方法
Linux系统产生随机数的3种方法 方法一:生成8位随机数 [root@localhost ~]# echo "$RANDOM$(date +%N%t)" | md5sum | c ...
- Linux中监控命令top命令使用方法详解
收集了两篇关于介绍Linux中监控命令top命令的详细使用方法的文章.总的来说,top命令主要用来查看Linux系统的各个进程和系统资源占用情况,在监控Linux系统性能方面top显得非常有用,下面就 ...
随机推荐
- .net通用CMS快速开发框架——问题1:Dapper通用的多表联合分页查询怎么破?
最近在弄一个东东,类似那种CMS的后台管理系统,方便作为其它项目的初始化框架用的. 现在遇到个问题,如标题所示:Dapper通用的多表联合分页查询怎么破? 难道只能通过拼接sql或者使用存储过程吗?我 ...
- blog写作心得体会
虽然写blog也挺久了,写出来的东西自己回顾的时候也会怀疑读者是否能看的明白,还是有种流水账的感觉,以后希望多从读者的角度出发.下面记录一些以后写博客的注意点. 具体关于某种技术点的小知识还有碰到的各 ...
- 加解密、PKI与CA基础
介绍 这门知识如果以前尝过的各位想必都知道:枯燥无比!因此在文中我会尽量讲的生动些,举一些例子,并试图以一个完整的例子来贯穿整个讲述过程.今年又恰逢莎翁逝世400周年,一方面也为了纪念这位伟大的作家. ...
- Dynamics CRM2016 Web API之获取查找字段的text及选项集的text
本篇再来介绍个web api的功能,关于lookup的text这里只是略带,因为有expand,现有的web api就能实现,主要提的是选项集的text,我们通过基本的查询api查出来的字段值只带有v ...
- 【SSH系列】深入浅出spring IOC中三种依赖注入方式
spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入什么?控 ...
- How to code like a hacker
We are coding. Are we engineers? Are we programmers? Are we coder? No, I want to be a hacker! Many g ...
- ubuntu日志文件管理
众所周知,ubuntu的日志文件会越来越大,需要定期管理 logrotate是个十分有用的工具,它可以自动对日志进行截断(或轮循).压缩以及删除旧的日志文件.例如,你可以设置logrotate,让/v ...
- Linux 高性能服务器编程——IP协议详解
1 IP服务特点 IP协议是TCP/IP协议族的动力,它为上层协议提供无状态.无连接.不可靠的服务. 无状态:IP通信双方不同步传输数据的状态信息,因此IP数据包的发送.传输和接收都是无序的. ...
- Dynamics CRM2016 Web API之更新记录的单个属性字段值
在web api中提供了对单个属性的更新接口,这和查询中查询单个属性类似,对这个接口我个人也是比较喜欢的. var id = "{D1E50347-86EB-E511-9414-ADA183 ...
- 适配器模式(adapter)
适配器模式的定义: 将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能在一起的那些类可以一起工作. 主要分为三类:类的适配器模式.对象的适配器模式.接口的适配器模式. ...