入门系列:gdb学习——函数调用时参数传递

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:513683159 【相互学习】
内容来源
  《Debug Hack 中文版》#10
  
实验环境
  ubuntu16.04,64位

内容

  接下来要进行函数参数传递的调试。
  参数传递方法根据架构、语言、编译器的不同而不同。
参数的存储位置
  整型和指针型的参数会从左至右依次保存到rdi、rsi、rdx、rcx、r8、r9中。
  浮点型参数会保存到xmm0、xmm1……中。
  多于这些寄存器的参数会被保存到栈上。
因此,利用GDB在希望确认的函数开头中断之后,查看寄存器或栈即可获得参数内容。
函数前加星号(*)与不加的区别?
  *:断点会设置到汇编语言层次的函数开头、
  不加*:断点会设置到地址偏后一点的源代码级别的开头。
由于参数也可能保存到栈上,若在break命令中不加*直接使用函数名,就无法用于参数确认。
  

实践

  ①创建源文件vim func_call.c内容如下;

#include
#include

int v1 = 1;
float v2 = 0.01;

void func(int a,long b,short c,char d,long long e,
          float f,double g,int *h,float *i,char *j)
{
        printf("a:%d,b:%ld,c:%d,d:%c,e:%lld\n"
               "f:%.3e,g:%.3e\n"
               "h:%p,i:%p,j:%p\n",a,b,c,d,e,f,g,h,i,j);
}

int main(void)
{
        func(100,35000L,5,'A',123456789LL,3.14,2.99792458e8,&v1,&v2,"string");
        return EXIT_SUCCESS;
}

  ②编译源程序gcc func_call.c -o func -g-g包含调试信息。
  ③执行可执行程序./func,内容如下:

a:100,b:35000,c:5,d:A,e:123456789
f:3.140e+00,g:2.998e+08
h:0x601038,i:0x60103c,j:0x4006b2

  ④启动gdb调试gdb func,如下表示成功:

...
Reading symbols from func...done.

  ⑤设置断点b func.(b = break)在该函数设置断点

Breakpoint 1 at 0x400550: file func_call.c, line 10.

  ⑥查看断点信息i b(i = info ,b = break)

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400550 in func at func_call.c:10
	breakpoint already hit 1 time

  ⑦运行程序r(r =run)

Starting program: /home/xsndz/Desktop/func 

Breakpoint 1, func (a=100, b=35000, c=5, d=65 'A', e=123456789, f=3.1400001, g=299792458, h=0x601038 <v1>, i=0x60103c <v2>, j=0x4006b2 "string") at func_call.c:10
10		printf("a:%d,b:%ld,c:%d,d:%c,e:%lld\n"

  ⑧继续执行程序c(c = continue)

Continuing.
a:100,b:35000,c:5,d:A,e:123456789
f:3.140e+00,g:2.998e+08
h:0x601038,i:0x60103c,j:0x4006b2
[Inferior 1 (process 2610) exited normally]

  ⑨删除所有断点:d break(d = delete),y

Delete all breakpoints? (y or n) y

  ①确认是否删除i b(i = info ,b = break)

No breakpoints or watchpoints.

  ②设置断点b func(b = break)在该函数设置断点

Breakpoint 2 at 0x400526: file func_call.c, line 9.

  ③查看断点i b(i = info ,b = break)

Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000400526 in func at func_call.c:9

  ④运行程序r(r =run)

Starting program: /home/xsndz/Desktop/func 
Breakpoint 2, func (a=0, b=140737488346510, c=0, d=0 '\000', e=1, f=5.87970963e-39, g=7.7939996923098536e-317, h=0x7ffff7ffe168, i=0x60103c <v2>, j=0x4006b2 "string") at func_call.c:9
9	{

  ⑤查看一下func的汇编程序disas func

Dump of assembler code for function func:
   0x0000000000400526 <+0>:	push   %rbp
   0x0000000000400527 <+1>:	mov    %rsp,%rbp
   0x000000000040052a <+4>:	sub    $0x30,%rsp
   ...

  该三句完成开辟栈空间设置为一个栈帧
  ⑥查看参数:a、b、c、d、e(保存在寄存器中),故查看一下所有寄存器:i r(i = info ,r = registers)

rax            0x400598	4195736
rbx            0x0	0
rcx            0x41	65										//参数d
rdx            0x5	5										//参数c
rsi            0x88b8	35000								//参数b
rdi            0x64	100										//参数a
rbp            0x7fffffffdd90	0x7fffffffdd90
rsp            0x7fffffffdd78	0x7fffffffdd78
r8             0x75bcd15	123456789						//参数e
r9             0x601038	6295608								//参数h
r10            0x846	2118
r11            0x7ffff7a2d750	140737348032336
r12            0x400430	4195376
r13            0x7fffffffde70	140737488346736
r14            0x0	0
r15            0x0	0
rip            0x400526	0x400526 <func>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

  从上可看出:参数:a、b、c、d、e分别保存在rdi 、rsi、rdx 、rcx 、 r8
  ⑦查看参数f、g(分别保存在xmm0、xmm1寄存器) 的数值:
  p $xmm0.v4_float[0]

$1 = 3.1400001

  p $xmm1.v2_double[0]

$2 = 299792458

  为什么xmm0/xmm1还需加上后缀?这是因为GDB将这些寄存器看作下面的联合:

union {
float		v4_float[4];
double		v2_double[2];
int8_t		v16_int8[16];
int16_t		v8_int16[8];
int32_t		v4_int32[8];
int64_t		v2_int64[8];
int128_t	unit128;
}xmm0;

  ⑧查看参数h、i、j【h处理方法与整形相同,保存到r9中[在上面就可看到参数h],由于i、j也为指针,参数少会存在寄存器中,但多了则不够用,这两个则存入栈中进行传递】
  查看内存栈空间中内容:x /3g $rsp

0x7fffffffdd78:	4195807	6295612
0x7fffffffdd88:	4196018

  g=giant word双字【整数和指针等带下均为g】,3代表要数量,即显示3个参数,但不是仅想要查看i、j,这是因为栈开头保存了函数的返回地址。简易图示如下:
入门系列:gdb学习——函数调用时参数传递_第1张图片
  若想确认它们指向的值或字符串可进行如下操作:
  1.printf"%.2f\n",*(float*)6295612printf"%.2f\n",*(float*)(*(unsigned long*)($rsp+0x8))

0.01

  2.p(char*)4196018p (char*)(*(unsigned long*)($rsp+0x10))

$3 = 0x4006b2 "string"

  ⑨继续执行程序c(c = continue)

Continuing.
a:100,b:35000,c:5,d:A,e:123456789
f:3.140e+00,g:2.998e+08
h:0x601038,i:0x60103c,j:0x4006b2
[Inferior 1 (process 2614) exited normally]

i386环境

  看书
  原则上参数全部放在栈中。
  i3876中寄存器调用,即:
    也可放在将部分参数放在寄存器中进行函数调用。这种方式称为:fastcall(快速调用)。
  gcc在函数声明中添加__attribute_((regparm(3))),即可进行这种调用。这样即可使用eax、edx、ecx传递开头的是哪个参数

C++篇

  看书

你可能感兴趣的:(基础知识,c语言,linux)