周末无事,闲逛bbs,看到这样一个问题:
什么情况下,下面这个foo() 函数会返回一个 "Aello world" 的指针, 而不会运行报错
- char *foo()
-
{
-
char* p = (char*)"Hello world";
-
-
p[0] = 'A';
-
-
return p;
-
}
(运行环境: linux )
很多公司的笔试题里,都认为这段代码是肯定执行出错的。不过某些时候,还是可以继
续运行的。
========================================================
基于我对ELF的理解,稍稍研究了一下,结果如下:
这个返回出错是因为"Hello world"会被放到.rodata里面。
kinwin@ustc-king:/tmp$ cat tt.c
char * foo(){
char *a = (char *)"Hello world\n";
a[0] = 'A';
return a;
}
kinwin@ustc-king:/tmp$ cc -c tt.c
kinwin@ustc-king:/tmp$ objdump -d tt.o
tt.o: file format elf32-i386
Disassembly of section .text:
00000000 :
0: 55 push p
1: 89 e5 mov %esp,p
3: 83 ec 10 sub $0x10,%esp
6: c7 45 fc 00 00 00 00 movl $0x0,-0x4(p)
d: 8b 45 fc mov -0x4(p),x
10: c6 00 41 movb $0x41,(x)
13: 8b 45 fc mov -0x4(p),x
16: c9 leave
17: c3 ret
这里的"Hello World"会生成一个重定位表项
kinwin@ustc-king:/tmp$ readelf -r tt.o
Relocation section '.rel.text' at offset 0x31c contains 1 entries:
Offset Info Type Sym.Value Sym. Name
00000009 00000501 R_386_32 00000000 .rodata
链接的时候把这个.rodata中的字符串地址填入那堆0的地方。
产生segment fault的原因就是指针指的是.rodata中的内容,而.rodata的段属性是
ALLOC,不带WRITE和EXECUTE,这样在load的时候生成的vm_area_struct也是只读属性的
,写其中的内容自然会在vm_area_struct那里被捕获,然后被sigsegv了。
要让它可写也是Ok的,最根本的就是让它写的时候对应地址的vm_area_struct属性是可
写的,pte层面应该都是可写的。就有几种办法了
1. loader载入elf文件是根据elf的program header来生成对应的vm_area_struct的,其
中默认是两个,一个可读可执行,一个可读可写,.rodata落在了第一块。
因此,法一, program header table的映射关系让.rodata这个section映射到
program header的可读可写块即可
或者,法二:干脆在链接的时候让各目标文件的.rodata连接到.data这个section来(任
一program header中映射的可读可写区域),而.data load的时候是可读可写的,如:
链接脚本如此写:
.data :
*(.rodata)
*(.data .data.* .gnu.linkonce.d.*)
kinwin@ustc-king:/tmp$ gcc test.c -Wl,-T,ld.ver -o test.out
kinwin@ustc-king:/tmp$ ./test.out
Aello world
法三: 提示gcc在生成目标文件的时候不放入.rodata,而是放入.data,不知道gcc有无
这样的选项
法四:以上都为修改elf文件输出,也可以在运行时写只读区域之前,把对应的区域用
mprotect把权限改了
阅读(1711) | 评论(0) | 转发(1) |