我的程序我做主--不改代码让只读数据可写

1:00:00 PM 0 Comments

什么情况下,下面这个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把权限改了

Some say he’s half man half fish, others say he’s more of a seventy/thirty split. Either way he’s a fishy bastard.