How to check what memory region a pointer belongs to?

I know pzittlau has already dug into the implementation of Allocator.free() and answered this question, but now I have a tangent, a question about this other idea.

Given a pointer, is there a way to tell that the memory it points to is comptime-known? (Or from any other specific region, like the stack?)

Somewhere in the compiler/linker they must know where to place something but afaik there is no easy way to get to it.

In theory there is the address_space of the pointer struct in the Type enum:

    pub const Pointer = struct {
        // [...]
        address_space: AddressSpace,
        // [...]
   }

But it isn’t really used for that and an address space is also something a bit different from the memory region.

The other alternative is to do it at runtime. On linux you can use a combination of auxv, /proc/self/maps and the ELF file(which you can get with /proc/self/exe) to find in which segment(or section) if any the pointer points to.

2 Likes

Here is something I hacked together for Zig 0.15.1. It’s by no means good code but just for illustration.

const std = @import("std");
const elf = std.elf;
const mem = std.mem;
const fs = std.fs;

const assert = std.debug.assert;


const static_str = "I am a compile-time constant";
const global_const: u64 = 64;
var global_var: u64 = 64;

pub fn main() !void {
    try printMaps();

    // Stack variable
    var stack_var: u64 = 0xDEADBEEF;

    // Heap variable
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const heap_ptr = try allocator.create(u64);
    defer allocator.destroy(heap_ptr);
    heap_ptr.* = 12345;

    try checkPointer(@ptrCast(&stack_var), "Stack Var");
    try checkPointer(@ptrCast(heap_ptr), "Heap Ptr");
    try checkPointer(@ptrCast(static_str), "Static String");
    try checkPointer(@ptrCast(&global_var), "Global Variable");
    try checkPointer(@ptrCast(&global_var), "Global Constant");
    try checkPointer(@ptrCast(&printMaps), "Function Pointer");
}

fn printMaps() !void {
    const path = "/proc/self/maps";
    var reader = try std.fs.cwd().openFile(path, .{});
    var buffer: [1024 * 1024]u8 = undefined;
    const size = try reader.readAll(&buffer);
    std.debug.print("\n{s}\n", .{buffer[0..size]});
}

fn checkPointer(ptr: *const anyopaque, name: []const u8) !void {
    const addr = @intFromPtr(ptr);

    // Open ELF File to check Sections
    const exe_file = try fs.openFileAbsolute("/proc/self/exe", .{});
    defer exe_file.close();
    var exe_buffer: [128]u8 = undefined;
    var exe_reader_inst = exe_file.reader(&exe_buffer);
    const exe_reader = &exe_reader_inst.interface;
    const ehdr = try elf.Header.read(exe_reader);
    var load_bias: usize = 0;
    if (ehdr.type == .DYN) {
        // Calculate Load Bias (using auxv)
        // We need this because the addresses in the ELF file (like .rodata) are relative.
        // The Load Bias = Runtime_Address_of_Headers (AT_PHDR) - File_Offset_of_Headers (phoff)
        var at_phdr: ?usize = null;
        if (std.os.linux.elf_aux_maybe) |auxv| {
            var i: usize = 0;
            while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
                if (auxv[i].a_type == elf.AT_PHDR) {
                    at_phdr = auxv[i].a_un.a_val;
                    break;
                }
            }
        }
        load_bias = at_phdr.? - ehdr.phoff;
    }

    // Find the String Table Section Header first to get its file offset
    var strtab_offset: u64 = 0;
    var find_strtab_iter = ehdr.iterateSectionHeaders(&exe_reader_inst);
    var sh_idx: usize = 0;
    while (try find_strtab_iter.next()) |shdr| : (sh_idx += 1) {
        if (sh_idx == ehdr.shstrndx) {
            strtab_offset = shdr.sh_offset;
            break;
        }
    }

    var section_iter = ehdr.iterateSectionHeaders(&exe_reader_inst);
    while (try section_iter.next()) |shdr| {
        // Calculate where this section actually lives in memory right now
        const section_start = shdr.sh_addr + load_bias;
        const section_end = section_start + shdr.sh_size;
        if (addr >= section_start and addr < section_end) {
            var name_buf: [64]u8 = undefined;
            // Read the name using preadAll to avoid disturbing the iterator's buffered reader
            const len = try exe_file.preadAll(&name_buf, strtab_offset + shdr.sh_name);
            const section_name = std.mem.sliceTo(name_buf[0..len], 0);
            std.debug.print("{s:<15} ({p}) -> ELF Section ({s})\n", .{ name, ptr, section_name });
            return;
        }
    }

    // Check /proc/self/maps (Stack / Heap)
    // If it wasn't in a specific ELF section, check the OS memory map
    const maps_file = try fs.openFileAbsolute("/proc/self/maps", .{});
    defer maps_file.close();
    var maps_buffer: [4096]u8 = undefined;
    var maps_reader_inst = maps_file.reader(&maps_buffer);
    const maps_reader = &maps_reader_inst.interface;
    while (true) {
        const line = maps_reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
            error.EndOfStream => break,
            else => return err,
        };

        // Line format: 00400000-00401000 r-xp ... /path/to/file or [stack]
        var it = mem.tokenizeAny(u8, line, " ");
        const range_str = it.next() orelse continue;

        // Parse range "start-end"
        var range_it = mem.splitScalar(u8, range_str, '-');
        const start = try std.fmt.parseInt(usize, range_it.first(), 16);
        const end = try std.fmt.parseInt(usize, range_it.next().?, 16);

        if (addr >= start and addr < end) {
            const rest = it.rest();
            if (mem.containsAtLeast(u8, rest, 1, "[stack]")) {
                std.debug.print("{s:<15} ({p}) -> Stack\n", .{ name, ptr });
                return;
            } else if (mem.containsAtLeast(u8, rest, 1, "[heap]")) {
                std.debug.print("{s:<15} ({p}) -> Heap (brk)\n", .{ name, ptr });
                return;
            } else {
                std.debug.print("{s:<15} ({p}) -> Heap (mmap/anon)\n", .{ name, ptr });
                return;
            }
        }
    }

    std.debug.print("{s:<15} ({p}) -> Unknown Region\n", .{ name, ptr });
}
2 Likes

The broad areas to search for and some working code! Thank you!!

1 Like