runtime: add MemoryLayout API
This commit is contained in:
@@ -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: ...
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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 ®ions {
|
||||
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") {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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`
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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@
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user