说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
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
,这是因为栈开头保存了函数的返回地址。简易图示如下:
若想确认它们指向的值或字符串可进行如下操作:
1.printf"%.2f\n",*(float*)6295612
或printf"%.2f\n",*(float*)(*(unsigned long*)($rsp+0x8))
0.01
2.p(char*)4196018
或p (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]
看书
原则上参数全部放在栈中。
i3876中寄存器调用,即:
也可放在将部分参数放在寄存器中进行函数调用。这种方式称为:fastcall(快速调用)。
gcc在函数声明中添加__attribute_((regparm(3)))
,即可进行这种调用。这样即可使用eax、edx、ecx
传递开头的是哪个参数
看书