Cover-Art für: Schlüsseltechnologie

Schlüsseltechnologie #45 vom 19. Oktober 2023

Speicherstruktur aus Programmsicht

In dieser Ausgabe greifen wir ein altes Thema erneut auf, bei dem ttimeless trotzdem stellenweise nur schwer folgen kann. Denn durch Wiederholung lernt man. Denn durch Wiederholung lernt man.

Länge: 70:32 Minuten

 
00:00 Intro
 
01:42 Rückblick: Speicherverwaltung
 
07:49 Was stellt uns das Betriebssystem bereit?
 
24:28 Vorbemerkungen zu klassischen Schutzmaßnahmen
 
40:39 Stack und Heap
 
42:23 Der Stapelspeicher
 
56:40 Der Haufenspeicher
 
58:53 Xyrills minimales sleep
 
62:47 Was sind vvar, vdso und vsyscall?

Download

Shownotes


$ sleep 30 &
[1] 4970
$ cat /proc/$(pidof sleep)/maps
# address                perms offset   dev   inode       pathname
556ac29f4000-556ac29f6000 r--p 00000000 fe:00 10234368    /usr/bin/sleep
556ac29f6000-556ac29f9000 r-xp 00002000 fe:00 10234368    /usr/bin/sleep
556ac29f9000-556ac29fa000 r--p 00005000 fe:00 10234368    /usr/bin/sleep
556ac29fa000-556ac29fb000 r--p 00006000 fe:00 10234368    /usr/bin/sleep
556ac29fb000-556ac29fc000 rw-p 00007000 fe:00 10234368    /usr/bin/sleep
556ac2f81000-556ac2fa2000 rw-p 00000000 00:00 0           [heap]
7fe4dea00000-7fe4ded42000 r--p 00000000 fe:00 10321904    /usr/lib/locale/locale-archive
7fe4deefc000-7fe4deeff000 rw-p 00000000 00:00 0
7fe4deeff000-7fe4def21000 r--p 00000000 fe:00 10226583    /usr/lib/libc.so.6
7fe4def21000-7fe4df07e000 r-xp 00022000 fe:00 10226583    /usr/lib/libc.so.6
7fe4df07e000-7fe4df0d6000 r--p 0017f000 fe:00 10226583    /usr/lib/libc.so.6
7fe4df0d6000-7fe4df0da000 r--p 001d6000 fe:00 10226583    /usr/lib/libc.so.6
7fe4df0da000-7fe4df0dc000 rw-p 001da000 fe:00 10226583    /usr/lib/libc.so.6
7fe4df0dc000-7fe4df0eb000 rw-p 00000000 00:00 0
7fe4df11e000-7fe4df11f000 r--p 00000000 fe:00 10226556    /usr/lib/ld-linux-x86-64.so.2
7fe4df11f000-7fe4df145000 r-xp 00001000 fe:00 10226556    /usr/lib/ld-linux-x86-64.so.2
7fe4df145000-7fe4df14f000 r--p 00027000 fe:00 10226556    /usr/lib/ld-linux-x86-64.so.2
7fe4df14f000-7fe4df151000 r--p 00031000 fe:00 10226556    /usr/lib/ld-linux-x86-64.so.2
7fe4df151000-7fe4df153000 rw-p 00033000 fe:00 10226556    /usr/lib/ld-linux-x86-64.so.2
7ffe3da15000-7ffe3da36000 rw-p 00000000 00:00 0           [stack]
7ffe3da73000-7ffe3da77000 r--p 00000000 00:00 0           [vvar]
7ffe3da77000-7ffe3da79000 r-xp 00000000 00:00 0           [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0   [vsyscall]

Randbemerkungen: Sofern ich die Ausgabe von strace sleep 30 richtig lese, wurde [heap] hier mit brk verwaltet. Die beiden anonymen Segmente gehen höchstwahrscheinlich auf das Konto des Laufzeit-Linkers (hier ld-linux-x86-64.so.2), der die Programmbibliotheken (hier nur libc.so.6) in den Prozess lädt.

Um das zu bestätigen, habe ich dann mal schnell ein minimales Programm gebaut, dass ausschließlich sleep gefolgt von exit macht, und definitiv gar nichts anderes. Ich war in der Tat erfolgreich:

$ strace ./target/release/minimal-memory-maps
execve("./target/release/minimal-memory-maps", ["./target/release/minimal-memory-"...], 0x7ffd0c104b00 /* 67 vars */) = 0
nanosleep({tv_sec=10, tv_nsec=0}, 0x7ffdaa084800) = 0
exit(0)                                 = ?
+++ exited with 0 +++

Und dieses Programm hat nicht mal einen Heap, sondern nur den Stack und die Kernel-Schnittstellen:

$ cat /proc/$(pidof minimal-memory-maps)/maps
# address                perms offset   dev   inode       pathname
00400000-00401000         rwxp 00000000 fe:00 9309336     .../target/release/minimal-memory-maps
7ffdaa066000-7ffdaa087000 rw-p 00000000 00:00 0           [stack]
7ffdaa0ea000-7ffdaa0ee000 r--p 00000000 00:00 0           [vvar]
7ffdaa0ee000-7ffdaa0f0000 r-xp 00000000 00:00 0           [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0   [vsyscall]

Hier sieht man übrigens schön, dass ich mein Programm nicht mit PIC kompiliert habe, sodass für die Programmdatei selber keine ASLR vorgenommen werden konnte.

Der Vollständigkeit halber dokumentiere ich hier noch den Code, um mein Testprogramm zu reproduzieren. Der Code funktioniert nur auf Linux und x86-64. Und selbst da habe ich irgendwas nicht ganz richtig gemacht: Das Programm funktioniert nur, wenn man dem Compiler Optimierungen verbietet (?!?). Trotzdem ein Shoutout an diesen Blogartikel und diesen anderen Blogartikel, mit deren Hilfe ich das Kompilieren ohne libc und main zumindest hinreichend weit ans Laufen bekommen habe.

$ cat Makefile
build:
    RUSTFLAGS="-Copt-level=0 -Ctarget-cpu=native -Clink-args=-nostartfiles -Clink-args=-Wl,-n,-N,--no-dynamic-linker" cargo build --release

$ cat Cargo.toml
[package]
name = "minimal-memory-maps"
version = "0.1.0"
edition = "2021"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"

$ cat src/main.rs
#![no_std]
#![no_main]
use core::arch::asm;

#[repr(C)]
struct Timespec {
    pub tv_sec: i64,
    pub tv_nsec: i64,
}

const NR_NANOSLEEP: usize = 35;
const NR_EXIT: usize = 60;

#[no_mangle]
pub extern "C" fn _start() -> ! {
    let req = Timespec { tv_sec: 10, tv_nsec: 0 };
    let mut resp = Timespec { tv_sec: 0, tv_nsec: 0 };
    unsafe {
        // nanosleep(&req, &mut resp)
        let req_ptr = (&req as *const Timespec) as usize;
        let resp_ptr = (&mut resp as *mut Timespec) as usize;
        let ret: usize;
        asm!(
            "syscall",
            inout("rax") NR_NANOSLEEP => ret,
            in("rdi") req_ptr,
            in("rsi") resp_ptr,
            out("rcx") _,
            out("r11") _,
        );
        let exit_code = if ret == 0 { 0 } else { 1 };
        // exit(0)
        asm!(
            "syscall",
            in("rax") NR_EXIT,
            in("rdi") exit_code,
            options(noreturn)
        )
    }
}

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Audioquellen in Abspielreihenfolge (soweit nicht gemeinfrei)