过go ,低级C/C++代码通过执行以下操作来识别指针ptr是否指向只读存储器in Linux OS:

extern char etext, edata;
...
(ptr > &etext) && (ptr < &edata);

事实上,我仍然不时地看到这一点在行动中.

然而,在go 年(Why does subtracting the value of etext from edata not give me the correct size for my text segment)别人发布的一个问题的答案中,显示出如今传统的连续内存布局text -> data -> bss -> heap -> ... -> stack -> kernel已经不再有保证.

这对我来说相当令人费解,因为正如我所说的,我看到上面的代码今天仍然在使用.还因为那个链接问题的操作员理所当然地给答案留下了 comments ,问他们现在在哪里可以找到关于记忆部分布局的信息--但答案的作者不知道.这是一个相当相关的技术信息,但我也找不到有关它的进一步信息,这些信息可以帮助我断言上面的代码是否仍然有用,或者如何根据现代内存节布局规则修改它.

This answer对于一个略有不同的问题,建议使用以下方法来判断指针是否在静态变量内存中--以解决如今内存段的顺序可能是随机的这一事实:

(void*)(x) <= (void*)&end || (void*)(x) <= (void*)&edata

其基本原理是,只需判断指针是否位于BSS和只读数据的结尾之前,即可确认静态内存中的指针.

因此,我的主要问题分为两部分:

  • 据我所知,上面的后一种方法是默默地假设,尽管堆的位置可能是随机的,但它位于BBS and只读数据之后-从不介于两者之间.这是一个安全的假设吗?

  • 如果是这样,并且知道文本代码段至少是第一位的,我们是否可以采用上面的方法来确保在modern Linux OSes中检测到指针特定于只读存储器数据(不包括BSS)中?我知道没有标准的通用解决方案,但目前我正试图仅为Linux系统解决这个问题(尽管Mac OS的解决方案可能非常相似,因为编译器也经常为这些操作系统提供与edata、etext和end类似功能).

我的try 是:

#include <iostream>
extern char etext, edata;


// the following is supposed to detect whether a pointer is in the Stack
// according to this answer:
https://stackoverflow.com/questions/35206343/is-it-possible-to-identify-whether-an-address-reference-belongs-to-static-heap-s
void *stack_bottom;
bool __attribute__((noinline)) inStack(void *x) {
    void *stack_top = &stack_top;
    return x <= stack_bottom && x >= stack_top;
}

// then my attempt at checking if a pointer is in read-only data,
// excluding the BSS segment:
bool roMem(void* c) {
    if(&etext < &edata && &end > &edata)
    {
        return (c > &etext) && (c < &edata) && (!inStack((void*)c));
    } else if(&etext > &edata && &end > &edata)
    {
        return (void*)(c) <= (void*)&edata && !inStack((void*)c);
    } else if(&end < &edata)
    {
        return (void*)(c) > (void*)&end && (void*)(c) <= (void*)&edata && !inStack((void*)c);
    }
}

class Test {
    public:
    const char* d;
    Test(const char* c) { d = c; }
};

static const char* str5 = "short";

int main() {
    const char* str1 = "short";
    char* str2 = const_cast<char*>("short");
    const char str3[6] = {'s', 'h', 'o', 'r', 't', 0};
    const char* str4 = "longgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg";
    std::cout << roMem((void*)str1) << std::endl; // prints 1
    std::cout << roMem((void*)(const char*)str2) << std::endl; // prints 1
    std::cout << roMem((void*)str3) << std::endl; // prints 0
    std::cout << roMem((void*)str4) << std::endl; // prints 1
    std::cout << roMem((void*)str5) << std::endl; // prints 1
    std::cout << roMem((void*)"short") << std::endl; // prints 1
    std::cout << roMem((void*)(const char*)2) << std::endl; // prints 0
    Test x((const char*)3);
    std::cout << roMem((void*)x.d) << std::endl; // prints 0
}

在我的Ubuntu 22.04机器上,上面的seems正在工作(用g++12和链接器ID或clang++13编译),但问题是,在判断(following this)时,它似乎也在生成紧跟在代码文本段之后的传统数据段序列-在这种情况下,旧的(ptr > &etext) && (ptr < &edata);就足够了.问题是当机器不遵循紧跟在文本段之后的传统数据段序列时.

推荐答案

读一读/proc/self/maps.随之而来的是:

#include <cstdint>
#include <fstream>
#include <iomanip>
#include <ios>
#include <iostream>
#include <limits>
#include <optional>
#include <sstream>
#include <vector>

std::optional<bool> roMem(uintptr_t addr) {
    auto f = std::ifstream("/proc/self/maps");
    while (f) {
        uintptr_t start;
        uintptr_t stop;
        char c;
        if ((f >> std::hex >> start >> c >> stop >> c >> c) && start <= addr && addr < stop) {
            return c != 'w';
        }
        f.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    return {};
}

template <typename T> std::optional<bool> roMem(T* c) {
    return roMem(reinterpret_cast<uintptr_t>(c));
}

std::ostream& operator<<(std::ostream& ss, std::optional<bool> o) {
    return ss << (o ? *o ? "true" : "false" : "none");
}

static const char* str5 = "short";
int main() {
    std::cout << roMem(str5) << '\n';
}

C++相关问答推荐

为什么海湾合作委员会在共享对象中的. init_data的虚拟内存地址之前留出一个空白

segfault在C中使用getline()函数

丑陋的三重间接:可扩展的缓冲区管理 struct

如何设置指针指向在函数中初始化的复合文字中的整数?

有没有更简单的方法从用户那里获取数据类型来计算结果

为什么输出不是从上到下C

GCC引发不明确的诊断消息

测量ARM MCU中断延迟的问题

如何在C客户端应用程序的ClientHello消息中添加自定义扩展?

Char变量如何在不使用方括号或花括号的情况下存储字符串,以及它如何迭代到下一个字符?

函数的限制限定指针参数允许优化调用方函数吗?

C代码在字符串中删除不区分大小写的子字符串的问题

C整型和_泛型.哪些类型是兼容的?

类型定义 struct 与简单的类型定义 struct

搜索使用int代替time_t的用法

合并对 struct 数组进行排序

如何在C中用bool进行文件I/O?

C:面筋蛋白';为什么不刷新窗口?

GDB 用内容初始化数组

将帧从相机 (/dev/video0) 复制到帧缓冲区 (/dev/fb0) 会产生意外结果