先提问题,在类似如下的函数中:
char *GetMemory(void)
{
char p[] = "hello world";
printf("%p\n",p);
return p;
}
问题1:
printf("%p\n",p); // 对p的有什么影响?请对比GetMemory0和GetMemory1
问题2:
p[1] = 'a'; //这个的先后顺序不同,为啥结果不一样?请对比GetMemory2和GetMemory3
已知见解:
char* p = "hello world";
是一个指向常量区地址的指针。
char p[] = "hello world";
是局部字符串数组,会把"hello world"拷贝到函数栈中.
printf("%p",p)会修改栈的中数据的释放原则?
代码示例:
char *GetMemory0(void)
{
char p[] = "hello world";
return p;
}
char *GetMemory1(void)
{
char p[] = "hello world";
printf("%p\n",p);
return p;
}
char *GetMemory2(void)
{
char p[] = "hello world";
printf("%p\n",p);
p[1] = 'a';
return p;
}
char *GetMemory3(void)
{
char p[] = "hello world";
p[1] = 'a';
printf("%p\n",p);
return p;
}
char *GetMemory4(void)
{
char *p = "hello world";
return p;
}
char *GetMemory5(void)
{
char *p = "hello world";
printf("%p\n",p);
return p;
}
int main()
{
char *str = NULL;
str = GetMemory0();
printf(str);
printf("\n---0---\n");
str = GetMemory1();
printf(str);
printf("\n---1---\n");
str = GetMemory2();
printf(str);
printf("\n---2---\n");
str = GetMemory3();
printf(str);
printf("\n---3---\n");
str = GetMemory4();
printf(str);
printf("\n---4---\n");
str = GetMemory5();
printf(str);
printf("\n---5---\n");
}
运行结果为:
0(M�
---0---
0x7fff184d2720
hello world
---1---
0x7fff184d2720
hello world
---2---
0x7fff184d2720
hallo world
---3---
hello world
---4---
0x400988
hello world
---5---
编译过程:
g++ --version
g++ (GCC) 4.8.2 20140206 (prerelease)
g++ -Wall -O -g -c test.cpp -o test.o
test.cpp: 在函数‘char* GetMemory0()’中:
test.cpp:4:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]
char p[] = "hello world";
^
test.cpp: 在函数‘char* GetMemory1()’中:
test.cpp:9:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]
char p[] = "hello world";
^
test.cpp: 在函数‘char* GetMemory2()’中:
test.cpp:15:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]
char p[] = "hello world";
^
test.cpp: 在函数‘char* GetMemory3()’中:
test.cpp:22:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]
char p[] = "hello world";
^
test.cpp: 在函数‘char* GetMemory4()’中:
test.cpp:29:15: 警告:不建议使用从字符串常量到‘char*’的转换 [-Wwrite-strings]
char *p = "hello world";
^
test.cpp: 在函数‘char* GetMemory5()’中:
test.cpp:34:15: 警告:不建议使用从字符串常量到‘char*’的转换 [-Wwrite-strings]
char *p = "hello world";
^
g++ -Wall -O -g test.o -o ./test -lpthread -lstdc++
chmod a+x ./test
先说一下,和@依云 说的问题应该无关……
另外说一点,调换func0和func1的顺序会出现奇怪的现象,我暂时无法解释。
update1
使用下面的printf,就正常了……
现在的问题是我自己编译glibc就没问题,用pacman的就有问题,pacman的又不能调试,gdb又不支持汇编单步……
我来大胆猜测一下应该是因为不定传参奇特的压栈方式导致的吧……总之你换上
puts
的结果在我看来才是比较符合逻辑的。。我有一个怀疑,可能是因为某个不知名的原因在第一次调用printf的时候,printf会去调用一个类似puts这样的函数,导致参数被破坏?但是为什么同样的glibc的代码,自己写一遍就没有问题了……
对了,问题2和问题3我这里没办法复现
update2
上了一个调试器,printf第一次调用的时候代码路径确实不一样。
关键是这句
其中第一次会jmp到00000000004005b6,也就是下一句,然后经过一些跳转之后会到
在这里会压一堆的栈进去(56那么大),然后
call 0x00007f39a3fc8ce0
这个函数会返回
r11 = 00007f39a3448810
,也就是真实的printf
地址,之后再调用printf的时候就直接call这个了。第二次会jmp到00007f550b166810,那个地方的代码才是printf的真正代码。
那么为什么这个call并不会导致
hello world
被破坏呢?因为这时候
hello world
在00007fff:3cd28d28|00007fff3cd28d00|...<....|ASCII "hello world"
这个位置,栈顶指针在00007fff:3cd28c40|0000000000000000|........|
这个位置,之间间隔了232
。但是,这里程序想要压一堆的参数,所以
sub rsp 0xd8
,把栈顶移动了216
,但是实际上程序只往rsp+40
,rsp+48
,rsp+56
,rsp+64
,rsp+72
写入了东西,然后就直接jz 0x00007f39a344886b
,计算一些参数之后就call 0x00007f39a343e500
去调用vfprintf
了,当然不会影响到这时候相对rsp+232
开始的hello world
。之后因为栈顶已经被拉的超过
hello world
了,之后再怎么调用也就没什么关系了。为了验证这一点,你可以把
GetMonery1
修改成这样:你会发现,输出的东西并不完整,反而被破坏了。
-至于glibc为啥这么做……谁知道……-
按照@依云 说的,应该是Linux下用于动态链接库的PLT。