runtime: add MemoryLayout API

This commit is contained in:
lief-integration-bot
2026-08-30 14:30:16 +02:00
parent 8254a43863
commit 9a78a15cee
29 changed files with 905 additions and 6 deletions
+20
View File
@@ -286,6 +286,26 @@ class Memory:
@staticmethod
def read_i64(addr: int) -> int: ...
class MemoryLayout:
class Region:
@property
def name(self) -> str: ...
@property
def addr(self) -> int: ...
@property
def size(self) -> int: ...
@property
def end_addr(self) -> int: ...
def contains(self, addr: int) -> bool: ...
def __str__(self) -> str: ...
def memory_layout() -> Iterator[Optional[MemoryLayout.Region]]: ...
def disassemble(addr: int) -> Iterator[Optional[lief.assembly.Instruction]]: ...
def assemble(address: int, assembly: str, config: lief.assembly.AssemblerConfig = ...) -> bytes: ...
+2
View File
@@ -27,6 +27,7 @@ namespace LIEF::runtime {
class Process;
class Module;
class Memory;
class MemoryLayout;
class Host;
}
@@ -61,6 +62,7 @@ void init(nb::module_& m) {
create<LIEF::runtime::Process>(runtime_mod);
create<LIEF::runtime::Module>(runtime_mod);
create<LIEF::runtime::Memory>(runtime_mod);
create<LIEF::runtime::MemoryLayout>(runtime_mod);
init_disassembler(runtime_mod);
init_assembler(runtime_mod);
@@ -3,6 +3,7 @@ target_sources(pyLIEF PRIVATE
pyModule.cpp
pyHost.cpp
pyMemory.cpp
pyMemoryLayout.cpp
pyDisassembler.cpp
pyAssembler.cpp
)
@@ -0,0 +1,61 @@
#include <sstream>
#include "LIEF/runtime/MemoryLayout.hpp"
#include "runtime/pyRuntime.hpp"
#include <nanobind/make_iterator.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/string_view.h>
#include <nanobind/stl/unique_ptr.h>
#include "pyOwningIterator.hpp"
namespace LIEF::runtime::py {
template<>
void create<MemoryLayout>(nb::module_& m) {
nb::class_<MemoryLayout> obj(m, "MemoryLayout",
R"doc(
This class exposes the memory layout of the current process.
)doc"_doc
);
using Region = MemoryLayout::Region;
nb::class_<Region>(obj, "Region",
R"doc(
A contiguous range of memory mapped in the current process.
)doc"_doc
)
.def_prop_ro("name", &Region::name,
R"doc(
Name associated with the region: the name or the path of the module
mapped at this address (e.g. ``libc.so.6``) or the identifier of a
region that is not backed by a file (e.g. ``[stack]``, ``[heap]``).
It can be empty for anonymous regions.
)doc"_doc
)
.def_prop_ro("addr", &Region::addr,
"Address at which the region starts"_doc
)
.def_prop_ro("size", &Region::size,
"Size of the region"_doc
)
.def_prop_ro("end_addr", &Region::end_addr,
"Address at which the region ends"_doc
)
.def("contains", &Region::contains, "addr"_a,
"Whether the given address is within this region"_doc
)
LIEF_DEFAULT_STR(Region)
;
m.def("memory_layout", [] () {
auto layout = LIEF::py::owning_range(runtime::memory_layout());
return nb::make_iterator<nb::rv_policy::take_ownership>(
nb::type<Region>(), "memory_layout_iterator", layout);
}, R"doc(
Return an iterator over the memory layout of the current process
)doc"_doc);
}
}
+3
View File
@@ -588,6 +588,9 @@ pub use crate::runtime::linux::module::ffi::runtime_linux_dlopen;
pub use crate::runtime::linux::process::ffi::runtime_linux_Process;
pub use crate::runtime::memory::ffi::runtime_Memory;
pub use crate::runtime::memory::ffi::runtime_Memory_Chunk;
pub use crate::runtime::memory_layout::ffi::runtime_MemoryLayout_Region;
pub use crate::runtime::memory_layout::ffi::runtime_it_memory_layout;
pub use crate::runtime::memory_layout::ffi::runtime_memory_layout;
pub use crate::runtime::module::ffi::runtime_Module;
pub use crate::runtime::module::ffi::runtime_it_modules;
pub use crate::runtime::module::ffi::runtime_module_from_addr;
+1
View File
@@ -4,6 +4,7 @@ pub mod disassembler;
pub mod host;
pub mod linux;
pub mod memory;
pub mod memory_layout;
pub mod module;
pub mod osx;
pub mod process;
@@ -0,0 +1,23 @@
#[cxx::bridge]
pub mod ffi {
unsafe extern "C++" {
include!("LIEF/rust/runtime/MemoryLayout.hpp");
type runtime_MemoryLayout_Region;
type runtime_it_memory_layout;
fn name(self: &runtime_MemoryLayout_Region) -> UniquePtr<CxxString>;
fn addr(self: &runtime_MemoryLayout_Region) -> u64;
fn size(self: &runtime_MemoryLayout_Region) -> u64;
fn end_addr(self: &runtime_MemoryLayout_Region) -> u64;
fn contains(self: &runtime_MemoryLayout_Region, addr: u64) -> bool;
fn to_string(self: &runtime_MemoryLayout_Region) -> UniquePtr<CxxString>;
fn next(self: Pin<&mut runtime_it_memory_layout>)
-> UniquePtr<runtime_MemoryLayout_Region>;
fn runtime_memory_layout() -> UniquePtr<runtime_it_memory_layout>;
}
impl UniquePtr<runtime_MemoryLayout_Region> {}
impl UniquePtr<runtime_it_memory_layout> {}
}
+4
View File
@@ -6,6 +6,7 @@ pub mod disassembler;
pub mod host;
pub mod linux;
pub mod memory;
pub mod memory_layout;
pub mod module;
pub mod osx;
pub mod process;
@@ -29,6 +30,9 @@ pub use module::{Module, Modules, module_from_addr, module_from_name, module_fro
#[doc(inline)]
pub use memory::Memory;
#[doc(inline)]
pub use memory_layout::{Region, RegionsIt, memory_layout};
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Arch {
@@ -0,0 +1,75 @@
use lief_ffi as ffi;
use crate::common::FromFFI;
use crate::declare_standalone_fwd_iterator;
/// A contiguous range of memory mapped in the current process.
pub struct Region {
ptr: cxx::UniquePtr<ffi::runtime_MemoryLayout_Region>,
}
impl FromFFI<ffi::runtime_MemoryLayout_Region> for Region {
fn from_ffi(ptr: cxx::UniquePtr<ffi::runtime_MemoryLayout_Region>) -> Self {
Self { ptr }
}
}
impl Region {
/// Name associated with the region: name/path of the module mapped at this
/// address (e.g. `libc.so.6`) or the identifier of a region that is not
/// backed by a file (e.g. `[stack]`, `[heap]`).
///
/// It can be empty for anonymous regions.
pub fn name(&self) -> String {
self.ptr.name().to_string()
}
/// Address at which the region starts
pub fn addr(&self) -> u64 {
self.ptr.addr()
}
/// Size of the region
pub fn size(&self) -> u64 {
self.ptr.size()
}
/// Address at which the region ends
pub fn end_addr(&self) -> u64 {
self.ptr.end_addr()
}
/// Whether the given address is within this region
pub fn contains(&self, addr: u64) -> bool {
self.ptr.contains(addr)
}
}
impl std::fmt::Display for Region {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.ptr.to_string())
}
}
impl std::fmt::Debug for Region {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Region")
.field("name", &self.name())
.field("addr", &self.addr())
.field("size", &self.size())
.field("end_addr", &self.end_addr())
.finish()
}
}
/// Return an iterator over the memory layout of the current process
pub fn memory_layout() -> RegionsIt {
RegionsIt::new(ffi::runtime_memory_layout())
}
declare_standalone_fwd_iterator!(
RegionsIt,
Region,
ffi::runtime_MemoryLayout_Region,
ffi::runtime_it_memory_layout
);
@@ -199,6 +199,30 @@ fn test_android() {
}
}
fn test_memory_layout() {
let regions: Vec<_> = lief::runtime::memory_layout().collect();
for region in &regions {
println!(
"{:#x}-{:#x} ({:#x}) {}",
region.addr(),
region.end_addr(),
region.size(),
region.name()
);
assert_eq!(region.end_addr(), region.addr() + region.size());
}
if lief::is_extended() {
assert!(!regions.is_empty());
assert!(regions.iter().all(|r| !format!("{r}").is_empty()));
// The regions backing this test binary must be part of the layout.
let self_addr = test_memory_layout as usize as u64;
assert!(regions.iter().any(|r| r.contains(self_addr)));
}
}
fn test_memory() {
let mut chunk = Memory::mmap(
0x4000,
@@ -297,6 +321,7 @@ fn test_runtime() {
test_modules();
test_memory();
test_memory_hint();
test_memory_layout();
test_android();
if cfg!(target_os = "macos") {
+1
View File
@@ -19,6 +19,7 @@
#include "LIEF/rust/runtime/Disassembler.hpp"
#include "LIEF/rust/runtime/Host.hpp"
#include "LIEF/rust/runtime/Memory.hpp"
#include "LIEF/rust/runtime/MemoryLayout.hpp"
#include "LIEF/rust/runtime/Module.hpp"
#include "LIEF/rust/runtime/Process.hpp"
#include "LIEF/rust/runtime/android/Host.hpp"
@@ -0,0 +1,67 @@
/* Copyright 2022 - 2026 R. Thomas
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "LIEF/runtime/MemoryLayout.hpp"
#include "LIEF/rust/Iterator.hpp"
#include "LIEF/rust/Mirror.hpp"
#include "LIEF/rust/helpers.hpp"
class runtime_MemoryLayout_Region
: public Mirror<LIEF::runtime::MemoryLayout::Region> {
public:
using Mirror::Mirror;
using lief_t = LIEF::runtime::MemoryLayout::Region;
auto name() const {
return to_unique_string(get().name());
}
uint64_t addr() const {
return get().addr();
}
uint64_t size() const {
return get().size();
}
uint64_t end_addr() const {
return get().end_addr();
}
auto contains(uint64_t addr) const {
return get().contains(addr);
}
auto to_string() const {
return to_unique_string(get().to_string());
}
};
class runtime_it_memory_layout
: public ForwardIterator<runtime_MemoryLayout_Region,
LIEF::runtime::MemoryLayout::Iterator> {
public:
runtime_it_memory_layout() :
ForwardIterator(LIEF::runtime::memory_layout()) {}
auto next() {
return ForwardIterator::next();
}
};
inline auto runtime_memory_layout() {
return std::make_unique<runtime_it_memory_layout>();
}
+10 -1
View File
@@ -35,6 +35,7 @@ option(LIEF_DEBUG_INFO "Build LIEF with DWARF/PDB support" O
option(LIEF_OBJC "Build LIEF with ObjC metadata support" OFF)
option(LIEF_DYLD_SHARED_CACHE "Build LIEF with Dyld shared cache support" OFF)
option(LIEF_ASM "Build LIEF with assembler/disassembler support" OFF)
option(LIEF_RUNTIME_EXTENDED "Build LIEF with assembler/disassembler support" OFF)
if (LIEF_COFF AND NOT LIEF_PE)
message(FATAL_ERROR "COFF module requires LIEF_PE enabled")
@@ -259,7 +260,15 @@ if (LIEF_ASM)
set(LIEF_ASM_SUPPORT 1)
endif()
if (LIEF_DEBUG_INFO OR LIEF_OBJC OR LIEF_DYLD_SHARED_CACHE OR LIEF_ASM) # or any other extended feature
if (LIEF_RUNTIME_EXTENDED)
set(LIEF_RUNTIME_EXTENDED_SUPPORT 1)
endif()
if (LIEF_DEBUG_INFO OR
LIEF_OBJC OR
LIEF_DYLD_SHARED_CACHE OR
LIEF_ASM OR
LIEF_RUNTIME_EXTENDED) # or any other extended feature
set(LIEF_EXTENDED 1)
endif()
+69
View File
@@ -0,0 +1,69 @@
#include <unordered_map>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <string>
#include <LIEF/runtime.hpp>
void iterate() {
// lief-doc: iterate-start
for (const LIEF::runtime::MemoryLayout::Region& region :
LIEF::runtime::memory_layout())
{
std::cout << region.addr() << '-' << region.end_addr() << ' ' << region.name()
<< '\n';
}
// lief-doc: iterate-end
}
void inspect(uint64_t address) {
// lief-doc: inspect-start
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';
}
// lief-doc: inspect-end
}
void stack_and_heap() {
// lief-doc: stack-heap-start
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';
}
}
// lief-doc: stack-heap-end
}
+49
View File
@@ -0,0 +1,49 @@
from collections import defaultdict
import lief
def iterate() -> None:
# lief-doc: iterate-start
for region in lief.runtime.memory_layout():
print(f"{region.addr:#014x}-{region.end_addr:#014x} {region.name}")
# lief-doc: iterate-end
def inspect(address: int) -> None:
# lief-doc: inspect-start
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}")
# lief-doc: inspect-end
def stack_and_heap() -> None:
# lief-doc: stack-heap-start
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}")
# lief-doc: stack-heap-end
+1
View File
@@ -9,6 +9,7 @@ pub mod elf;
pub mod intro;
pub mod logging;
pub mod macho;
pub mod memory_layout;
pub mod objc;
pub mod pdb;
pub mod pe;
+70
View File
@@ -0,0 +1,70 @@
use std::collections::BTreeMap;
pub fn iterate() {
// lief-doc: iterate-start
for region in lief::runtime::memory_layout() {
println!(
"{:#014x}-{:#014x} {}",
region.addr(),
region.end_addr(),
region.name()
);
}
// lief-doc: iterate-end
}
pub fn inspect(address: u64) {
// lief-doc: inspect-start
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()
);
}
// lief-doc: inspect-end
}
pub fn stack_and_heap() {
// lief-doc: stack-heap-start
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()
);
}
}
// lief-doc: stack-heap-end
}
+17
View File
@@ -1530,6 +1530,23 @@
:py:meth:`lief.runtime.Memory.mmap_hint`
:cpp:func:`LIEF::runtime::Memory::mmap_hint`
.. |lief-runtime-memorylayout| lief-api:: lief.runtime.MemoryLayout
:py:class:`lief.runtime.MemoryLayout`
:cpp:class:`LIEF::runtime::MemoryLayout`
.. |lief-runtime-memorylayout-region| lief-api:: lief.runtime.MemoryLayout.Region
:rust:struct:`lief::runtime::Region`
:py:class:`lief.runtime.MemoryLayout.Region`
:cpp:class:`LIEF::runtime::MemoryLayout::Region`
.. |lief-runtime-memory_layout| lief-api:: lief.runtime.memory_layout()
:rust:func:`lief::runtime::memory_layout`
:py:func:`lief.runtime.memory_layout`
:cpp:func:`LIEF::runtime::memory_layout`
.. |lief-runtime-module| lief-api:: lief.runtime.Module
:rust:trait:`lief::runtime::module::Module`
+10
View File
@@ -65,6 +65,16 @@
* Add |lief-runtime-memory-mmap_hint| to allocate memory close to a given
address.
* Add |lief-runtime-memorylayout| to inspect the memory layout of the current
process. |lief-runtime-memory_layout| iterates over the mapped
|lief-runtime-memorylayout-region| (:ref:`documentation <runtime_memory_layout>`):
.. code-block:: python
import lief
for region in lief.runtime.memory_layout():
print(f"{region.addr:#014x}-{region.end_addr:#014x} {region.name}")
:C++:
@@ -0,0 +1,97 @@
(runtime_memory_layout)=
# {fa}`solid fa-map` Memory Layout
The {sub-ref}`lief-runtime-memorylayout` interface exposes the memory layout of
the **current** process: the regions that are mapped in its address space.
{sub-ref}`lief-runtime-memory_layout` returns an iterator over these
regions, ordered by address:
::::{tabs}
:::{tab} {fa}`brands fa-python` Python
{{ literalinclude("../../../code/python/memory_layout.py", "iterate") }}
:::
:::{tab} {fa}`regular fa-file-code` C++
{{ literalinclude("../../../code/cpp/memory_layout.cpp", "iterate") }}
:::
:::{tab} {fa}`brands fa-rust` Rust
{{ literalinclude("../../../code/rust/src/memory_layout.rs", "iterate") }}
:::
::::
For a process running `/usr/bin/cat`, this prints:
```{code-block} text
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]
```
A {sub-ref}`lief-runtime-memorylayout-region` is a contiguous range of memory
described by its address range and the name can be either:
- 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.
## {fa}`solid fa-magnifying-glass` Inspecting the layout
The following snippet iterates over the memory layout to
- compute how much memory is mapped
- the footprint of each module
- the region that backs a given address
::::{tabs}
:::{tab} {fa}`brands fa-python` Python
{{ literalinclude("../../../code/python/memory_layout.py", "inspect") }}
:::
:::{tab} {fa}`regular fa-file-code` C++
{{ literalinclude("../../../code/cpp/memory_layout.cpp", "inspect") }}
:::
:::{tab} {fa}`brands fa-rust` Rust
{{ literalinclude("../../../code/rust/src/memory_layout.rs", "inspect") }}
:::
::::
## {fa}`brands fa-linux` Linux / {fa}`brands fa-android` Android
On Linux and Android, the kernel names the regions that back the stack and the
heap of the process, so both can be located by name:
::::{tabs}
:::{tab} {fa}`brands fa-python` Python
{{ literalinclude("../../../code/python/memory_layout.py", "stack-heap") }}
:::
:::{tab} {fa}`regular fa-file-code` C++
{{ literalinclude("../../../code/cpp/memory_layout.cpp", "stack-heap") }}
:::
:::{tab} {fa}`brands fa-rust` Rust
{{ literalinclude("../../../code/rust/src/memory_layout.rs", "stack-heap") }}
:::
::::
```text
[heap]: 0x56367cb52000-0x56367cb73000
[stack]: 0x7ffd162f1000-0x7ffd16312000
```
{{ cross_api }}
+12
View File
@@ -72,6 +72,18 @@ ______________________________________________________________________
______________________________________________________________________
## {fa}`solid fa-map` MemoryLayout
```{eval-rst}
.. doxygenclass:: LIEF::runtime::MemoryLayout
```
```{eval-rst}
.. doxygenfunction:: LIEF::runtime::memory_layout()
```
______________________________________________________________________
## {fa}`brands fa-linux` Linux
### {fa}`solid fa-cubes` Module
+2
View File
@@ -10,6 +10,7 @@
components/host
components/process
components/memory
components/memory_layout
components/modules
```
@@ -92,5 +93,6 @@ The runtime features are split in the following components:
components/host
components/process
components/memory
components/memory_layout
components/modules
```
+12
View File
@@ -91,6 +91,18 @@ ______________________________________________________________________
______________________________________________________________________
## {fa}`solid fa-map` MemoryLayout
```{eval-rst}
.. autoclass:: lief.runtime.MemoryLayout
```
```{eval-rst}
.. autofunction:: lief.runtime.memory_layout
```
______________________________________________________________________
## {fa}`brands fa-linux` Linux
### {fa}`solid fa-cubes` Module
+6 -5
View File
@@ -32,11 +32,12 @@
#cmakedefine LIEF_ART_SUPPORT @LIEF_ART_SUPPORT@
// Extended features
#cmakedefine LIEF_DEBUG_INFO @LIEF_DEBUG_INFO_SUPPORT@
#cmakedefine LIEF_OBJC @LIEF_OBJC_SUPPORT@
#cmakedefine LIEF_DYLD_SHARED_CACHE @LIEF_DYLD_SHARED_CACHE_SUPPORT@
#cmakedefine LIEF_ASM_SUPPORT @LIEF_ASM_SUPPORT@
#cmakedefine LIEF_EXTENDED @LIEF_EXTENDED@
#cmakedefine LIEF_DEBUG_INFO @LIEF_DEBUG_INFO_SUPPORT@
#cmakedefine LIEF_OBJC @LIEF_OBJC_SUPPORT@
#cmakedefine LIEF_DYLD_SHARED_CACHE @LIEF_DYLD_SHARED_CACHE_SUPPORT@
#cmakedefine LIEF_ASM_SUPPORT @LIEF_ASM_SUPPORT@
#cmakedefine LIEF_RUNTIME_EXTENDED_SUPPORT @LIEF_RUNTIME_EXTENDED_SUPPORT@
#cmakedefine LIEF_EXTENDED @LIEF_EXTENDED@
// LIEF options
#cmakedefine LIEF_RUNTIME_SUPPORT @LIEF_RUNTIME_SUPPORT@
+1
View File
@@ -19,6 +19,7 @@
// IWYU pragma: begin_exports
#include "LIEF/runtime/Host.hpp"
#include "LIEF/runtime/Memory.hpp"
#include "LIEF/runtime/MemoryLayout.hpp"
#include "LIEF/runtime/Module.hpp"
#include "LIEF/runtime/Process.hpp"
#include "LIEF/runtime/assembler.hpp"
+149
View File
@@ -0,0 +1,149 @@
/* Copyright 2017 - 2026 R. Thomas
* Copyright 2017 - 2026 Quarkslab
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LIEF_RUNTIME_MEMORY_LAYOUT_H
#define LIEF_RUNTIME_MEMORY_LAYOUT_H
#include <string_view>
#include <cstdint>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
#include "LIEF/compiler_attributes.hpp"
#include "LIEF/iterators.hpp"
#include "LIEF/visibility.h"
namespace LIEF::runtime {
namespace details {
class MemoryLayoutIt;
}
/// This class exposes the memory layout of the current process
class LIEF_API MemoryLayout {
public:
/// A contiguous range of memory mapped in the current process
class Region {
public:
Region() = default;
Region(std::string name, uint64_t addr, uint64_t size) :
name_(std::move(name)),
addr_(addr),
size_(size) {}
Region(const Region&) = default;
Region& operator=(const Region&) = default;
Region(Region&&) noexcept = default;
Region& operator=(Region&&) noexcept = default;
~Region() = default;
/// Name associated with the region: name/path of the module
/// mapped at this address (e.g. `libc.so.6`) or the identifier of a
/// region that is not backed by a file (e.g. `[stack]`, `[heap]`).
///
/// It can be empty for anonymous regions.
std::string_view name() const LIEF_LIFETIMEBOUND {
return name_;
}
/// Address at which the region starts
uint64_t addr() const {
return addr_;
}
/// Size of the region
uint64_t size() const {
return size_;
}
/// Address at which the region ends
uint64_t end_addr() const {
return addr() + size();
}
/// Whether the given address is within this region
bool contains(uint64_t addr) const {
return addr_ <= addr && addr < end_addr();
}
std::string to_string() const;
LIEF_API friend std::ostream& operator<<(std::ostream& os,
const Region& region) {
os << region.to_string();
return os;
}
private:
std::string name_;
uint64_t addr_ = 0;
uint64_t size_ = 0;
};
/// Forward iterator over the regions of the memory layout
class Iterator final
: public iterator_facade_base<Iterator, std::forward_iterator_tag, Region,
std::ptrdiff_t, const Region*, const Region&> {
public:
using implementation = details::MemoryLayoutIt;
using iterator_facade_base::operator++;
LIEF_API Iterator();
LIEF_API Iterator(const Iterator&);
LIEF_API Iterator& operator=(const Iterator&);
LIEF_API Iterator(Iterator&&) noexcept;
LIEF_API Iterator& operator=(Iterator&&) noexcept;
LIEF_API Iterator(std::unique_ptr<details::MemoryLayoutIt> impl);
LIEF_API ~Iterator();
friend LIEF_API bool operator==(const Iterator& LHS, const Iterator& RHS);
friend LIEF_API bool operator!=(const Iterator& LHS, const Iterator& RHS) {
return !(LHS == RHS);
}
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
LIEF_API Iterator& operator++();
LIEF_API const Region& operator*() const LIEF_LIFETIMEBOUND;
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
LIEF_API const Region* operator->() const LIEF_LIFETIMEBOUND;
/// Transfer ownership of the region at the current position to the
/// caller. Returns `nullptr` if the iterator is past-the-end.
LIEF_API std::unique_ptr<Region> yield();
private:
void load() const;
std::unique_ptr<details::MemoryLayoutIt> impl_;
mutable std::unique_ptr<Region> cached_;
};
};
using memory_layout_it = iterator_range<MemoryLayout::Iterator>;
/// Return an iterator over the memory layout of the current process
LIEF_API memory_layout_it memory_layout();
}
#endif
+5
View File
@@ -60,6 +60,10 @@ if (LIEF_RUNTIME)
target_sources(LIB_LIEF_RUNTIME PRIVATE osx/no_runtime.cpp)
endif()
if (NOT LIEF_RUNTIME_EXTENDED)
target_sources(LIB_LIEF_RUNTIME PRIVATE MemoryLayout.cpp)
endif()
else()
target_sources(LIB_LIEF_RUNTIME PRIVATE
no_runtime.cpp
@@ -68,5 +72,6 @@ else()
windows/no_runtime.cpp
android/no_runtime.cpp
osx/no_runtime.cpp
MemoryLayout.cpp
)
endif()
+90
View File
@@ -0,0 +1,90 @@
/* Copyright 2017 - 2026 R. Thomas
* Copyright 2017 - 2026 Quarkslab
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// NOLINTBEGIN
#include "LIEF/runtime/MemoryLayout.hpp"
#include "internal_utils.hpp"
#include "logging.hpp"
#include "messages.hpp"
namespace LIEF::runtime {
namespace details {
class MemoryLayoutIt {};
}
MemoryLayout::Iterator::Iterator() :
impl_(nullptr) {}
MemoryLayout::Iterator::Iterator(const MemoryLayout::Iterator&) :
impl_(nullptr) {}
MemoryLayout::Iterator&
MemoryLayout::Iterator::operator=(const MemoryLayout::Iterator&) {
return *this;
}
MemoryLayout::Iterator::Iterator(MemoryLayout::Iterator&&) noexcept :
impl_(nullptr) {}
MemoryLayout::Iterator&
MemoryLayout::Iterator::operator=(MemoryLayout::Iterator&&) noexcept {
return *this;
}
MemoryLayout::Iterator::
Iterator(std::unique_ptr<details::MemoryLayoutIt> /*impl*/) :
impl_(nullptr) {}
MemoryLayout::Iterator::~Iterator() = default;
bool operator==(const MemoryLayout::Iterator& /*LHS*/,
const MemoryLayout::Iterator& /*RHS*/) {
return true;
}
MemoryLayout::Iterator& MemoryLayout::Iterator::operator++() {
return *this;
}
const MemoryLayout::Region& MemoryLayout::Iterator::operator*() const {
return *cached_;
}
const MemoryLayout::Region* MemoryLayout::Iterator::operator->() const {
return nullptr;
}
std::unique_ptr<MemoryLayout::Region> MemoryLayout::Iterator::yield() {
return nullptr;
}
void MemoryLayout::Iterator::load() const {
return;
}
std::string MemoryLayout::Region::to_string() const {
return "";
}
memory_layout_it memory_layout() {
LIEF_ERR(NEEDS_EXTENDED_MSG);
return make_empty_iterator<MemoryLayout>();
}
// NOLINTEND
}
+22
View File
@@ -0,0 +1,22 @@
import lief
import pytest
from lief.runtime import MemoryLayout
if not lief.runtime.enabled:
pytest.skip("skipping: needs runtime support", allow_module_level=True)
@pytest.mark.lief_extended
def test_basic():
if lief.runtime.platform != lief.runtime.PLATFORMS.LINUX:
pytest.skip("skipping: Linux only for now")
regions = list(lief.runtime.memory_layout())
assert len(regions) > 0
for region in regions:
assert isinstance(region, MemoryLayout.Region)
assert isinstance(region.name, str)
assert region.end_addr == region.addr + region.size
assert region.contains(region.addr)
assert not region.contains(region.end_addr)
assert len(str(region)) > 0