我的简单代码是,
int main()
{
const char *str="jigneshparmar";
printf("address of str data:%p , address of str variable:%p\n",(void*)str,(void*)&str );
getchar();
return 0;
}
字符串
这里,字符串常量“jignesh”存储在只读数据段中。
通过使用size命令,这里size的输出是:
gcc datasec.c -o datasec
size -A datasec
datasec :
section size addr
.interp 28 792
.note.gnu.property 32 824
.note.gnu.build-id 36 856
.note.ABI-tag 32 892
.gnu.hash 36 928
.dynsym 168 968
.dynstr 133 1136
.gnu.version 14 1270
.gnu.version_r 32 1288
.rela.dyn 192 1320
.rela.plt 24 1512
.init 27 4096
.plt 32 4128
.plt.got 16 4160
.plt.sec 16 4176
.text 405 4192
.fini 13 4600
.rodata 18 8192
.eh_frame_hdr 68 8212
.eh_frame 264 8280
.init_array 8 15800
.fini_array 8 15808
.dynamic 496 15816
.got 72 16312
.data 16 16384
.bss 8 16400
.comment 42 0
Total 2236
型
当我增加字符串常量时,这个.rodata的大小也会增加。
我打印的字符串的地址属于代码段。
./datasec
address of str data:0x55f5301f3004 ,address of str variable:0x7ffd0a2b1940
型
地址0x55f5301f3004位于代码段中。
cat /proc/4018/maps
555da289d000-555da289e000 r--p 00000000 103:02 13109134 /root/Desktop/lsp-prac/datasec
555da289e000-555da289f000 r-xp 00001000 103:02 13109134 /root/Desktop/lsp-prac/datasec
555da289f000-555da28a0000 r--p 00002000 103:02 13109134 /root/Desktop/lsp-prac/datasec
555da28a0000-555da28a1000 r--p 00002000 103:02 13109134 /root/Desktop/lsp-prac/datasec
555da28a1000-555da28a2000 rw-p 00003000 103:02 13109134 /root/Desktop/lsp-prac/datasec
555da416c000-555da418d000 rw-p 00000000 00:00 0 [heap]
7f5485c38000-7f5485c5d000 r--p 00000000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485c5d000-7f5485dd5000 r-xp 00025000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485dd5000-7f5485e1f000 r--p 0019d000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485e1f000-7f5485e20000 ---p 001e7000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485e20000-7f5485e23000 r--p 001e7000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485e23000-7f5485e26000 rw-p 001ea000 103:02 9963657 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f5485e26000-7f5485e2c000 rw-p 00000000 00:00 0
7f5485e41000-7f5485e42000 r--p 00000000 103:02 9963653 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f5485e42000-7f5485e65000 r-xp 00001000 103:02 9963653 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f5485e65000-7f5485e6d000 r--p 00024000 103:02 9963653 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f5485e6e000-7f5485e6f000 r--p 0002c000 103:02 9963653 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f5485e6f000-7f5485e70000 rw-p 0002d000 103:02 9963653 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f5485e70000-7f5485e71000 rw-p 00000000 00:00 0
7ffda6e9c000-7ffda6ebd000 rw-p 00000000 00:00 0 [stack]
7ffda6ed9000-7ffda6edd000 r--p 00000000 00:00 0 [vvar]
7ffda6edd000-7ffda6edf000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
型
这怎么可能
先谢了。
3条答案
按热度按时间yb3bgrhw1#
Linux内核通过将ELF可执行文件Map到内存中来“加载”它们。这发生在页面粒度上,
/proc/PID/maps
中的 offset 字段(权限后面的字段)描述了每个Map区域的文件偏移量。字符串文本存储在
.rodata
部分的ELF文件中,可执行代码存储在.text
部分。Linux内核确实使用节头来确定要Map的内容。ELF文件格式有一组 * 程序头 *,您可以在例如
readelf -l binary
中看到。这里相关的是LOAD头;这些指定了Linux内核Map到内存中的内容。例如,下面是来自x86-64(
readelf -l /bin/cat
)上的GNU Coreutils 8.28cat
的两个LOAD程序头:字符串
当Linux内核执行ELF文件时,它会将LOAD程序头Map到内存中。看到0x 0 - 0x 79 d 0只有读和执行,0x 6a 60 - 0x 8260只有读写(内存地址0x 207 a70 - 0x 208260),而根本没有“只读”吗?
使用
objdump -d -s /bin/cat
,我们可以看到(仅限相关片段):型
和
型
和
型
看看.text和.rodata是如何属于同一个LOAD程序头的?这就是为什么它们以相同的方式Map。.data部分是一个单独的LOAD程序头,因此被单独Map,具有不同的权限。
您可以看到,在大多数Linux系统(包括Ubuntu 18.04.5,上面的代码来自于此)上的x86-64上使用的链接器文件将.text和.rodata部分合并到一个LOAD程序头中;由于这是控制Linux内核如何将ELF可执行文件加载(Map)到内存中的方法,因此它们被Map到相同的内存区域,具有相同的权限(
r-xp
)。考虑以下示例程序
maps.c
:型
使用
gcc -Wall -Wextra -O2 maps.c -o maps
编译它,然后运行./maps
。型
其示出
literal1
(const char *const literal1 = "...";
)如何属于被Map为r--p
的存储器区域,但是指向被Map为r-xp
的存储器。(“嘿,我记得你说过内核只MapLOAD程序头?" 是的;那个特殊的Map不是由内核创建的,而是由动态链接器创建的。我没有说 * 只 * 内核将ELF可执行文件Map到内存中;我解释了内核如何将必需的最小LOAD程序头Map到内存中,并将执行交给该代码。库,该代码Map程序的其余部分和任何先决条件的动态库。)
但是注意,
array1
不可变的字符数组完全在r-xp
Map的内存区域中,就像literal2
引用的字符串字面量一样。因为
array2
和literal2
是在main()
函数中声明的,所以它们位于[stack]
内存区域。8hhllhi22#
两件事正在发生:
1.您没有正确打印字符串字面量的地址;
1.您没有正确查看Map。
正如我在评论中提到的,
%p
期望其对应的参数具有void *
类型,而对printf
的调用是C中少数(也许是唯一)必须显式转换指向void *
的指针的地方之一,因此字符串字面量的地址可能没有正确格式化。否则,您将无法正确查看Map。
我用你的代码在我的系统上编译。当我运行它的时候我得到输出
字符串
您可以使用
objdump
实用程序查看可执行文件的各个部分-要查看.rodata
的内容,请执行以下操作:型
当我在我构建的代码上这样做时,我得到
型
与程序的输出相匹配
gz5pxeao3#
你可能有错误的地址不知何故。我尝试了你的代码,得到的结果是字符串常量地址str是在.rodata节,和地址的局部变量str是在堆栈上。
这是汇编代码所期望的。.rodata 部分中的字符串常量的地址被计算为相对地址(以启用位置无关代码),作为下一条指令的地址加上偏移量 * 0x 5ed *,并放入$rax寄存器。下一条指令将此地址 * 0x 55555556008 * 放入堆栈:
字符串
我们现在把str(.rodata中字符串的地址)放在堆栈上。用gdb检查 * 0x 55555556008 *,这是 str 的值放在堆栈上:
型
我们检查这确实是字符串的地址:
型
你可以在这个屏幕截图中看到str地址在堆栈上:
x1c 0d1x的数据
所以这是程序的真实的输出。我们希望地址不会与我们使用gdb运行程序时相同:
型
流程图为:
的
所以我们可以确认str指向.rodata,&str指向一个 stack