Memory Layout¶
Enumerate mapped regions¶
for region in lief.runtime.memory_layout():
print(f"{region.addr:#014x}-{region.end_addr:#014x} {region.name}")
for (const LIEF::runtime::MemoryLayout::Region& region :
LIEF::runtime::memory_layout())
{
std::cout << region.addr() << '-' << region.end_addr() << ' ' << region.name()
<< '\n';
}
for region in lief::runtime::memory_layout() {
println!(
"{:#014x}-{:#014x} {}",
region.addr(),
region.end_addr(),
region.name()
);
}
For illustration, a Linux process running /usr/bin/cat could have this layout:
0x563668f9d000-0x563668f9f000 /usr/bin/cat
0x563668f9f000-0x563668fa6000 /usr/bin/cat
0x563668fa6000-0x563668fa9000 /usr/bin/cat
0x563668fa9000-0x563668faa000 /usr/bin/cat
0x563668faa000-0x563668fab000 /usr/bin/cat
0x56367cb52000-0x56367cb73000 [heap]
0x7f2d28e00000-0x7f2d29196000 /usr/lib/locale/locale-archive
0x7f2d291be000-0x7f2d29200000
0x7f2d29200000-0x7f2d29224000 /usr/lib/libc.so.6
[...]
0x7f2d29478000-0x7f2d2947a000 [vdso]
0x7f2d2947a000-0x7f2d2947b000 /lib64/ld-linux-x86-64.so.2
[...]
0x7ffd162f1000-0x7ffd16312000 [stack]
0xffffffffff600000-0xffffffffff601000 [vsyscall]
the name or the path of the module mapped at this address (e.g.
/usr/lib/libc.so.6);the identifier of a region that is not backed by a file (e.g.
[stack],[heap],[vdso]);empty, for anonymous regions.
As shown in the output above, a module is not mapped as a single region: it usually gets one region per set of permissions.
Names and mappings depend on the operating system and can change as the process allocates memory or loads libraries. A region describes a mapping: it does not own the mapped memory.
Inspecting the layout¶
The following snippet iterates over the memory layout to:
Compute the total mapped size
Group that size by region name
Locate the region containing a given address
These totals measure virtual address space. They are not resident-memory (RSS) measurements, and grouping anonymous regions combines unrelated allocations.
address: int
count = 0
mapped = 0
footprint: defaultdict[str, int] = defaultdict(int)
enclosing: lief.runtime.MemoryLayout.Region | None = None
for region in lief.runtime.memory_layout():
count += 1
mapped += region.size
name = region.name if region.name else "<anonymous>"
footprint[name] += region.size
if region.contains(address):
enclosing = region
print(f"{count} regions, {mapped // 1024} KB mapped")
for name, size in footprint.items():
print(f"{size:#010x} {name}")
if enclosing is not None:
print(f"{address:#x}: {enclosing.name}+{address - enclosing.addr:#x}")
size_t count = 0;
uint64_t mapped = 0;
std::unordered_map<std::string, uint64_t> footprint;
LIEF::runtime::MemoryLayout::Region enclosing;
for (const LIEF::runtime::MemoryLayout::Region& region :
LIEF::runtime::memory_layout())
{
++count;
mapped += region.size();
std::string name =
region.name().empty() ? "<anonymous>" : std::string(region.name());
footprint[name] += region.size();
if (region.contains(address)) {
enclosing = region;
}
}
std::cout << count << " regions, " << (mapped / 1024) << " KB mapped\n";
for (const auto& [name, size] : footprint) {
std::cout << size << ' ' << name << '\n';
}
if (enclosing.size() > 0) {
std::cout << address << ": " << enclosing.name() << '+'
<< (address - enclosing.addr()) << '\n';
}
let mut count = 0usize;
let mut mapped = 0u64;
let mut footprint: BTreeMap<String, u64> = BTreeMap::new();
let mut enclosing: Option<lief::runtime::Region> = None;
for region in lief::runtime::memory_layout() {
count += 1;
mapped += region.size();
let mut name = region.name();
if name.is_empty() {
name = String::from("<anonymous>");
}
*footprint.entry(name).or_insert(0) += region.size();
if region.contains(address) {
enclosing = Some(region);
}
}
println!("{count} regions, {} KB mapped", mapped / 1024);
for (name, size) in &footprint {
println!("{size:#010x} {name}");
}
if let Some(region) = enclosing {
println!(
"{address:#x}: {}+{:#x}",
region.name(),
address - region.addr()
);
}
Linux / Android¶
On Linux and Android, named mappings such as [stack] and [heap] can be located when present. They do not account for every thread stack or allocator-managed allocation:
for region in lief.runtime.memory_layout():
# On Linux and Android, the kernel names the regions that back the
# stack and the heap of the process.
if region.name in ("[stack]", "[heap]"):
print(f"{region.name}: {region.addr:#014x}-{region.end_addr:#014x}")
for (const LIEF::runtime::MemoryLayout::Region& region :
LIEF::runtime::memory_layout())
{
// On Linux and Android, the kernel names the regions that back the stack
// and the heap of the process.
if (region.name() == "[stack]" || region.name() == "[heap]") {
std::cout << region.name() << ": " << region.addr() << '-'
<< region.end_addr() << '\n';
}
}
for region in lief::runtime::memory_layout() {
// On Linux and Android, the kernel names the regions that back the
// stack and the heap of the process.
let name = region.name();
if name == "[stack]" || name == "[heap]" {
println!(
"{name}: {:#014x}-{:#014x}",
region.addr(),
region.end_addr()
);
}
}
[heap]: 0x56367cb52000-0x56367cb73000
[stack]: 0x7ffd162f1000-0x7ffd16312000