为什么我的 Rust 可执行文件映射到如此高的地址(靠近堆栈)而不是 0x400000?
Why is my Rust executable mapped to such high addresses (near the stack) instead of 0x400000?
我正在了解 Linux 用户 space 在 x86_64 系统上的内存布局,并希望从某些部分打印一些地址。我使用了这个 Rust 代码:
fn main() {
let x = 3; // should be stored on stack
let s = "hello"; // should be in the .data section
println!("stack ≈ {:p}", &x);
println!(".text ≈ {:p}", main as *const ());
println!(".data ≈ {:p}", s);
use std::io;
let mut f = std::fs::File::open("/proc/self/maps").unwrap();
let out = io::stdout();
io::copy(&mut f, &mut out.lock()).unwrap();
}
此代码还将文件 /proc/self/maps
打印到标准输出。我用 rustc mem.rs
简单地编译了这个文件 mem.rs
。它打印:
stack ≈ 0x7ffffbf82f2c
.text ≈ 0x7f45b7c0a2b0
.data ≈ 0x7f45b7c4d35b
7f45b6800000-7f45b6c00000 rw-- 00000000 00:00 0
7f45b6de0000-7f45b6f9a000 r-x- 00000000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
7f45b6f9a000-7f45b6fa2000 ---- 001ba000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
[ ... more .so files]
7f45b7a22000-7f45b7a23000 r--- 00022000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f45b7a23000-7f45b7a24000 rw-- 00023000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f45b7a24000-7f45b7a25000 rw-- 00000000 00:00 0
7f45b7aa0000-7f45b7aa2000 rw-- 00000000 00:00 0
7f45b7ab0000-7f45b7ab2000 rw-- 00000000 00:00 0
7f45b7ac0000-7f45b7ac1000 rw-- 00000000 00:00 0
7f45b7ad0000-7f45b7ad1000 rw-- 00000000 00:00 0
7f45b7ae0000-7f45b7ae2000 rw-- 00000000 00:00 0
7f45b7c00000-7f45b7c5f000 r-x- 00000000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e5e000-7f45b7e62000 r--- 0005e000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e62000-7f45b7e63000 rw-- 00062000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e63000-7f45b7e64000 rw-- 00000000 00:00 0
7ffffb784000-7ffffb785000 ---- 00000000 00:00 0 [stack]
7ffffb785000-7ffffbf84000 rw-- 00000000 00:00 0
7ffffc263000-7ffffc264000 r-x- 00000000 00:00 0 [vdso]
至少我自己打印的地址似乎与maps
所说的相符。但是当我在终端中执行 cat /proc/self/maps
时,我得到了这个输出:
00400000-0040b000 r-x- 00000000 00:00 107117 /bin/cat
0060a000-0060b000 r--- 0000a000 00:00 107117 /bin/cat
0060b000-0060c000 rw-- 0000b000 00:00 107117 /bin/cat
0071c000-0073d000 rw-- 00000000 00:00 0 [heap]
7f7deb933000-7f7debc30000 r--- 00000000 00:00 758714 /usr/lib/locale/locale-archive
7f7debc30000-7f7debdea000 r-x- 00000000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
7f7debdea000-7f7debdf2000 ---- 001ba000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
[ ... more .so files ...]
7f7dec222000-7f7dec223000 r--- 00022000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f7dec223000-7f7dec224000 rw-- 00023000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f7dec224000-7f7dec225000 rw-- 00000000 00:00 0
7f7dec250000-7f7dec252000 rw-- 00000000 00:00 0
7f7dec260000-7f7dec261000 rw-- 00000000 00:00 0
7f7dec270000-7f7dec272000 rw-- 00000000 00:00 0
7ffff09e8000-7ffff11e8000 rw-- 00000000 00:00 0 [stack]
7ffff1689000-7ffff168a000 r-x- 00000000 00:00 0 [vdso]
后一个结果与我阅读的有关该主题的所有内容相匹配:可执行文件的部分映射到虚拟地址的低端 space(从 0x400000 左右开始)。
我为 Windows(Ubuntu 14.04 基本上)执行并编译了 Linux 子系统中的所有内容。我知道,它是新东西,但我相当确定这不是子系统的问题(不过请告诉我是否是!)。 Rust 1.14 很重要(我对此表示怀疑),
我也用 C 程序做了同样的尝试(请原谅我的 C 可能不好):
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv) {
FILE *test_file;
char buf[4096];
if ((test_file = fopen ("/proc/self/maps", "r")) != NULL) {
while (!feof (test_file)) {
fgets (buf, sizeof (buf), test_file);
puts (buf);
}
}
return 0;
}
它输出类似于 cat
的内容:
00400000-00401000 r-x- 00000000 00:00 1325490 /home/lukas/tmp/a.out
00600000-00601000 r--- 00000000 00:00 1325490 /home/lukas/tmp/a.out
00601000-00602000 rw-- 00001000 00:00 1325490 /home/lukas/tmp/a.out
为什么 Rust 可执行文件映射到堆栈附近的大地址?
使用rustc -Z print-link-args addr.rs
,您可以看到 Rust 编译器将使用的链接器调用。由于当前链接器恰好是cc
,我们可以直接为C程序重用这些选项。忽略不重要的参数并一一删除其他参数,我只剩下这个编译器调用:
gcc -fPIC -pie addr.c -o addr-c
像这样编译 C 代码会产生与 Rust 编译的可执行文件相似的地址,表明这些选项中的一个或两个可能是罪魁祸首。这会将问题更改为 "why does -fPIC
and/or -pie
map to such high addresses?"
我发现了另一个问题 似乎可以说明这一点:
The PIE
binary is linked just as a shared library, and so its default load address (the .p_vaddr
of the first LOAD
segment) is zero. The expectation is that something will relocate this binary away from zero page, and load it at some random address.
在 Rust 可执行文件上使用 readelf -e
,我们可以看到第一个 LOAD
段的虚拟地址为零:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x000000000005e6b4 0x000000000005e6b4 R E 200000
LOAD 0x000000000005ead0 0x000000000025ead0 0x000000000025ead0
0x00000000000039d1 0x00000000000049e8 RW 200000
我想这会将问题更改为 "why are these random addresses chosen",但我不确定该答案。 ^_^ hunch 告诉我 ASLR 发挥了作用。 other answer 似乎证实了这一点:
PIE is to support ASLR in executable files.
ASLR 是一种安全技术,可帮助加强程序抵御某些类型的攻击,因此 Rust 以其安全意识的方法尝试默认启用此类功能是有道理的。实际上,每次调用时地址都会发生一点变化:
root@97bcff9a925c:/# ./addr | grep 'r-xp' | grep 'addr'
5587cea9d000-5587ceafc000 r-xp 00000000 00:21 206 /addr
561d8aae2000-561d8ab41000 r-xp 00000000 00:21 206 /addr
555c30ffd000-555c3105c000 r-xp 00000000 00:21 206 /addr
55db249d5000-55db24a34000 r-xp 00000000 00:21 206 /addr
55e988572000-55e9885d1000 r-xp 00000000 00:21 206 /addr
560400e1b000-560400e7a000 r-xp 00000000 00:21 206 /addr
我正在了解 Linux 用户 space 在 x86_64 系统上的内存布局,并希望从某些部分打印一些地址。我使用了这个 Rust 代码:
fn main() {
let x = 3; // should be stored on stack
let s = "hello"; // should be in the .data section
println!("stack ≈ {:p}", &x);
println!(".text ≈ {:p}", main as *const ());
println!(".data ≈ {:p}", s);
use std::io;
let mut f = std::fs::File::open("/proc/self/maps").unwrap();
let out = io::stdout();
io::copy(&mut f, &mut out.lock()).unwrap();
}
此代码还将文件 /proc/self/maps
打印到标准输出。我用 rustc mem.rs
简单地编译了这个文件 mem.rs
。它打印:
stack ≈ 0x7ffffbf82f2c
.text ≈ 0x7f45b7c0a2b0
.data ≈ 0x7f45b7c4d35b
7f45b6800000-7f45b6c00000 rw-- 00000000 00:00 0
7f45b6de0000-7f45b6f9a000 r-x- 00000000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
7f45b6f9a000-7f45b6fa2000 ---- 001ba000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
[ ... more .so files]
7f45b7a22000-7f45b7a23000 r--- 00022000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f45b7a23000-7f45b7a24000 rw-- 00023000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f45b7a24000-7f45b7a25000 rw-- 00000000 00:00 0
7f45b7aa0000-7f45b7aa2000 rw-- 00000000 00:00 0
7f45b7ab0000-7f45b7ab2000 rw-- 00000000 00:00 0
7f45b7ac0000-7f45b7ac1000 rw-- 00000000 00:00 0
7f45b7ad0000-7f45b7ad1000 rw-- 00000000 00:00 0
7f45b7ae0000-7f45b7ae2000 rw-- 00000000 00:00 0
7f45b7c00000-7f45b7c5f000 r-x- 00000000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e5e000-7f45b7e62000 r--- 0005e000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e62000-7f45b7e63000 rw-- 00062000 00:00 1134580 /home/lukas/tmp/mem
7f45b7e63000-7f45b7e64000 rw-- 00000000 00:00 0
7ffffb784000-7ffffb785000 ---- 00000000 00:00 0 [stack]
7ffffb785000-7ffffbf84000 rw-- 00000000 00:00 0
7ffffc263000-7ffffc264000 r-x- 00000000 00:00 0 [vdso]
至少我自己打印的地址似乎与maps
所说的相符。但是当我在终端中执行 cat /proc/self/maps
时,我得到了这个输出:
00400000-0040b000 r-x- 00000000 00:00 107117 /bin/cat
0060a000-0060b000 r--- 0000a000 00:00 107117 /bin/cat
0060b000-0060c000 rw-- 0000b000 00:00 107117 /bin/cat
0071c000-0073d000 rw-- 00000000 00:00 0 [heap]
7f7deb933000-7f7debc30000 r--- 00000000 00:00 758714 /usr/lib/locale/locale-archive
7f7debc30000-7f7debdea000 r-x- 00000000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
7f7debdea000-7f7debdf2000 ---- 001ba000 00:00 664435 /lib/x86_64-linux-gnu/libc-2.19.so
[ ... more .so files ...]
7f7dec222000-7f7dec223000 r--- 00022000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f7dec223000-7f7dec224000 rw-- 00023000 00:00 663920 /lib/x86_64-linux-gnu/ld-2.19.so
7f7dec224000-7f7dec225000 rw-- 00000000 00:00 0
7f7dec250000-7f7dec252000 rw-- 00000000 00:00 0
7f7dec260000-7f7dec261000 rw-- 00000000 00:00 0
7f7dec270000-7f7dec272000 rw-- 00000000 00:00 0
7ffff09e8000-7ffff11e8000 rw-- 00000000 00:00 0 [stack]
7ffff1689000-7ffff168a000 r-x- 00000000 00:00 0 [vdso]
后一个结果与我阅读的有关该主题的所有内容相匹配:可执行文件的部分映射到虚拟地址的低端 space(从 0x400000 左右开始)。
我为 Windows(Ubuntu 14.04 基本上)执行并编译了 Linux 子系统中的所有内容。我知道,它是新东西,但我相当确定这不是子系统的问题(不过请告诉我是否是!)。 Rust 1.14 很重要(我对此表示怀疑),
我也用 C 程序做了同样的尝试(请原谅我的 C 可能不好):
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv) {
FILE *test_file;
char buf[4096];
if ((test_file = fopen ("/proc/self/maps", "r")) != NULL) {
while (!feof (test_file)) {
fgets (buf, sizeof (buf), test_file);
puts (buf);
}
}
return 0;
}
它输出类似于 cat
的内容:
00400000-00401000 r-x- 00000000 00:00 1325490 /home/lukas/tmp/a.out
00600000-00601000 r--- 00000000 00:00 1325490 /home/lukas/tmp/a.out
00601000-00602000 rw-- 00001000 00:00 1325490 /home/lukas/tmp/a.out
为什么 Rust 可执行文件映射到堆栈附近的大地址?
使用rustc -Z print-link-args addr.rs
,您可以看到 Rust 编译器将使用的链接器调用。由于当前链接器恰好是cc
,我们可以直接为C程序重用这些选项。忽略不重要的参数并一一删除其他参数,我只剩下这个编译器调用:
gcc -fPIC -pie addr.c -o addr-c
像这样编译 C 代码会产生与 Rust 编译的可执行文件相似的地址,表明这些选项中的一个或两个可能是罪魁祸首。这会将问题更改为 "why does -fPIC
and/or -pie
map to such high addresses?"
我发现了另一个问题
The
PIE
binary is linked just as a shared library, and so its default load address (the.p_vaddr
of the firstLOAD
segment) is zero. The expectation is that something will relocate this binary away from zero page, and load it at some random address.
在 Rust 可执行文件上使用 readelf -e
,我们可以看到第一个 LOAD
段的虚拟地址为零:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x000000000005e6b4 0x000000000005e6b4 R E 200000
LOAD 0x000000000005ead0 0x000000000025ead0 0x000000000025ead0
0x00000000000039d1 0x00000000000049e8 RW 200000
我想这会将问题更改为 "why are these random addresses chosen",但我不确定该答案。 ^_^ hunch 告诉我 ASLR 发挥了作用。 other answer 似乎证实了这一点:
PIE is to support ASLR in executable files.
ASLR 是一种安全技术,可帮助加强程序抵御某些类型的攻击,因此 Rust 以其安全意识的方法尝试默认启用此类功能是有道理的。实际上,每次调用时地址都会发生一点变化:
root@97bcff9a925c:/# ./addr | grep 'r-xp' | grep 'addr'
5587cea9d000-5587ceafc000 r-xp 00000000 00:21 206 /addr
561d8aae2000-561d8ab41000 r-xp 00000000 00:21 206 /addr
555c30ffd000-555c3105c000 r-xp 00000000 00:21 206 /addr
55db249d5000-55db24a34000 r-xp 00000000 00:21 206 /addr
55e988572000-55e9885d1000 r-xp 00000000 00:21 206 /addr
560400e1b000-560400e7a000 r-xp 00000000 00:21 206 /addr