过去,低级 C/C++ 代码通过执行以下操作来识别指针
ptr
是否指向 Linux 操作系统中的只读内存:
extern char etext, edata;
...
(ptr > &etext) && (ptr < &edata);
事实上,我仍然时不时地看到这一点。然而,在去年别人发布的一个问题的答案中(
为什么从 edata 中减去 etext 的值不能给我正确的文本段大小),表明现在传统的连续内存布局 text -> data -> bss -> heap -> ... -> stack -> kernel
不再保证。这让我很困惑,因为正如我所说,我看到上面的代码至今仍在使用。但也因为该链接问题的操作员可以理解地对答案留下了评论,询问他们现在在哪里可以找到有关内存部分布局的信息 - 但答案作者不知道。这是一个非常相关的技术信息,但我也找不到有关它的更多信息来帮助我断言上述代码是否仍然有用或如何根据现代内存部分布局规则修改它。
这个答案对一个稍微不同的问题建议使用以下方法来检查指针是否在静态变量内存中 - 以解决当今内存部分的顺序可能是随机的事实:
(void*)(x) <= (void*)&end || (void*)(x) <= (void*)&edata
理由是只要检查指针是否位于 BSS 和只读数据末尾之前即可确认静态内存中的指针。所以,我的主要问题有两个部分:
和 只读数据之后 - 从不介于两者之间。这是一个安全的假设吗?
现代 Linux 操作系统中检测到指针专门位于只读内存数据(不包括 BSS)中?我知道没有标准的通用解决方案,但目前我正在尝试仅解决 Linux 系统的问题(尽管 Mac 操作系统的解决方案可能非常相似,因为编译器经常为这些操作系统提供 edata、etext 和 end 的等效项).
#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 机器中,上面的似乎可以工作(使用 g++12 和链接器 ld 或 clang++13 编译),但问题是,经过检查(以下),它似乎也在代码文本段之后立即生成传统的数据段序列 - 在这种情况下,老式的 (ptr > &etext) && (ptr < &edata);
就足够了。问题在于机器不遵循文本段之后紧接着的数据段的传统顺序。
/proc/self/maps
。一些事情:
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <vector>
bool roMem(uintptr_t addr) {
auto f = std::ifstream("/proc/self/maps");
for (std::string line; std::getline(f, line);) {
std::stringstream iss(line);
uintptr_t start;
uintptr_t stop;
char c;
if ((iss >> std::hex >> start >> c >> stop >> c >> c) &&
c != '-' && start <= addr && stop < addr) {
return true;
}
}
return false;
}
template <typename T> bool roMem(T* c) {
return roMem(reinterpret_cast<uintptr_t>(c));
}
static const char* str5 = "short";
int main() {
return roMem(str5);
}