C程序的内存布局
C memory layout of a program
我正在熟悉 C,我正在检查程序的内存布局。
我确实设法获得了一些关于变量在内存中的位置的信息,但我仍然有一些不清楚。
我 运行 在 lubuntu 18.04 运行 virtualbox 上 windows 10 作为主机
假设我有以下程序:
int foo(); // (??)
void point(void* p); // (??)
int data1; // bss segment
int data2 = 3; // data segment
int main(){
char str[3] = {'a','b','c'}; // text segment(??)
char *str1 = "word"; // str1 - stack, *str1 = 'w' - text segment (??)
point(&data1); // call stack
return 0;
}
void point(void* p){
long dist1 = (size_t)&data2 - (size_t)p; // call stack - inside point's AF: dist1, p
printf("%ld\n", dist1); /* is a long integer generally enough to hold addresses (or difference of
addresses). is it legal to calculate differences of addresses of different
segments? */
}
int foo(){
return 0;
}
对于我在评论中添加的问题,我需要一些帮助,并且可能需要一些对我已经做过的事情的确认。
提前致谢。
这完全是实现定义的。
关于地址,大多数系统提供intptr_t
和uintptr_t
,它们是适合存放地址的整数类型。你可以用它做任何事情,因为它只是一个整数,但如果你将它转换回指针,它的有效性取决于你(即转换为 intptr_t
并返回就可以了,就像执行等价的正确指针算术,但仅此而已)。
还有ptrdiff_t
,保存指针差;它通常与 intptr_t
相同,但始终可用。你仍然可以用它做任何事情,因为它是一个整数;操作意义由你决定。但是只有指针指向同一个数组的不同元素时才允许减去指针本身。 1
关于部分,函数体进入 .text 或等效文件;函数头本身不会去任何地方,但是如果函数被导出(Linux 上的默认值),链接器将必要的数据添加到程序文件中,动态 linker/loader 使用它来构建真正的跳转表。局部变量进入线程堆栈。常量可能位于 data
、rodata
甚至 text
中(尤其是在 x64 上)。 bss
是一个非存储部分,即在文件中,只有它的描述存在,但在程序启动时它被分配并且(通常)用零填充。
让我们用 gcc -O0 program.c -o program
编译你的程序,然后用 objdump -D program
反汇编它。为方便起见,我继续这样做 (AT&T syntax, Intel syntax)。您可以看到 foo
在 .text
部分中,它已被替换为一个存根,该存根实际上什么也不做,只是 returns 在函数之外。由于 point
是在声明之后定义的,它相当于在声明中定义,并且也在 .text
部分中具有实际实现。可以看到你是对的,data1
在.bss
,data2
在.data
。至于你在main中的{'a', 'b', 'c'}
数组,你可以看到它有点奇怪。
6c1: c6 45 f5 61 mov BYTE PTR [rbp-0xb],0x61
6c5: c6 45 f6 62 mov BYTE PTR [rbp-0xa],0x62
6c9: c6 45 f7 63 mov BYTE PTR [rbp-0x9],0x63
这些值实际上被一个一个地加载到数组中,所以我猜你可以说它被存储在 .text
部分。您可能会注意到 "word"
字符串实际上并不在反汇编中。但是,如果您执行 readelf -x .rodata program
,您会发现它位于 .rodata
部分。
Hex dump of section '.rodata':
0x000007d0 01000200 776f7264 00256c64 0a00 ....word.%ld..
您还可以看到,虽然变量未按名称引用,但它们位于函数的 堆栈帧 中,由基指针的偏移量给出 rbp
.对于 64 位二进制文件,一个地址是 8 个字节,对于 32 位二进制文件,一个地址是 4 个字节。
我正在熟悉 C,我正在检查程序的内存布局。 我确实设法获得了一些关于变量在内存中的位置的信息,但我仍然有一些不清楚。
我 运行 在 lubuntu 18.04 运行 virtualbox 上 windows 10 作为主机
假设我有以下程序:
int foo(); // (??)
void point(void* p); // (??)
int data1; // bss segment
int data2 = 3; // data segment
int main(){
char str[3] = {'a','b','c'}; // text segment(??)
char *str1 = "word"; // str1 - stack, *str1 = 'w' - text segment (??)
point(&data1); // call stack
return 0;
}
void point(void* p){
long dist1 = (size_t)&data2 - (size_t)p; // call stack - inside point's AF: dist1, p
printf("%ld\n", dist1); /* is a long integer generally enough to hold addresses (or difference of
addresses). is it legal to calculate differences of addresses of different
segments? */
}
int foo(){
return 0;
}
对于我在评论中添加的问题,我需要一些帮助,并且可能需要一些对我已经做过的事情的确认。
提前致谢。
这完全是实现定义的。
关于地址,大多数系统提供intptr_t
和uintptr_t
,它们是适合存放地址的整数类型。你可以用它做任何事情,因为它只是一个整数,但如果你将它转换回指针,它的有效性取决于你(即转换为 intptr_t
并返回就可以了,就像执行等价的正确指针算术,但仅此而已)。
还有ptrdiff_t
,保存指针差;它通常与 intptr_t
相同,但始终可用。你仍然可以用它做任何事情,因为它是一个整数;操作意义由你决定。但是只有指针指向同一个数组的不同元素时才允许减去指针本身。 1
关于部分,函数体进入 .text 或等效文件;函数头本身不会去任何地方,但是如果函数被导出(Linux 上的默认值),链接器将必要的数据添加到程序文件中,动态 linker/loader 使用它来构建真正的跳转表。局部变量进入线程堆栈。常量可能位于 data
、rodata
甚至 text
中(尤其是在 x64 上)。 bss
是一个非存储部分,即在文件中,只有它的描述存在,但在程序启动时它被分配并且(通常)用零填充。
让我们用 gcc -O0 program.c -o program
编译你的程序,然后用 objdump -D program
反汇编它。为方便起见,我继续这样做 (AT&T syntax, Intel syntax)。您可以看到 foo
在 .text
部分中,它已被替换为一个存根,该存根实际上什么也不做,只是 returns 在函数之外。由于 point
是在声明之后定义的,它相当于在声明中定义,并且也在 .text
部分中具有实际实现。可以看到你是对的,data1
在.bss
,data2
在.data
。至于你在main中的{'a', 'b', 'c'}
数组,你可以看到它有点奇怪。
6c1: c6 45 f5 61 mov BYTE PTR [rbp-0xb],0x61
6c5: c6 45 f6 62 mov BYTE PTR [rbp-0xa],0x62
6c9: c6 45 f7 63 mov BYTE PTR [rbp-0x9],0x63
这些值实际上被一个一个地加载到数组中,所以我猜你可以说它被存储在 .text
部分。您可能会注意到 "word"
字符串实际上并不在反汇编中。但是,如果您执行 readelf -x .rodata program
,您会发现它位于 .rodata
部分。
Hex dump of section '.rodata':
0x000007d0 01000200 776f7264 00256c64 0a00 ....word.%ld..
您还可以看到,虽然变量未按名称引用,但它们位于函数的 堆栈帧 中,由基指针的偏移量给出 rbp
.对于 64 位二进制文件,一个地址是 8 个字节,对于 32 位二进制文件,一个地址是 4 个字节。