如何在运行时检查一个内存地址是否可写?

6

如何在运行时检查内存地址是否可写?

例如,我想要在以下代码中实现is_writable_address。这可行吗?

#include <stdio.h>

int is_writable_address(void *p) {
    // TODO
}

void func(char *s) {
    if (is_writable_address(s)) {
        *s = 'x';
    }
}

int main() {
    char *s1 = "foo";
    char s2[] = "bar";

    func(s1);
    func(s2);
    printf("%s, %s\n", s1, s2);
    return 0;
}

考虑到编译代码本质上是机器相关的,我不认为这是可能的。此外,我也不明白你为什么要这样做;我相信有其他方法可以实现你想要做的事情。 - Ricky Stewart
Linux有mprotect函数来设置内存保护,但是没有直接的方法来查询内存保护状态。请查看这个问题:http://stackoverflow.com/questions/3585641/how-to-get-the-memory-access-type-in-c-c-in-linux - user1129665
3
如果你不得不去询问,那么你的做法可能存在问题。 - Raymond Chen
为什么不使用类似于readelf的功能?并通过解析ELF结构来检查可读写部分? - Amir Naghizadeh
2个回答

8

我基本上同意那些认为这是一个坏主意的人。

话虽如此,鉴于该问题具有UNIX标签,在类UNIX操作系统上完成此操作的经典方法是使用从/dev/zero读取(read()):

#include <fcntl.h>
#include <unistd.h>

int is_writeable(void *p)
{
    int fd = open("/dev/zero", O_RDONLY);
    int writeable;

    if (fd < 0)
        return -1; /* Should not happen */

    writeable = read(fd, p, 1) == 1;
    close(fd);

    return writeable;
}

我看到libunwind有一个类似的函数:https://github.com/libunwind/libunwind/blob/master/src/x86_64/Ginit.c#L122,他们使用一个管道来读取并再次写入以检查地址是否可写,我只是想知道为什么他们要使用额外的写入调用,我认为只需要读取一次就足够了。 - prehistoricpenguin

0

从技术上讲,这可能是可行的,但没有可移植的方法来实现,而且也不应该有必要这样做。如果您已经无法跟踪指针是否可写,那么您还有更重要的细节不知道,例如它指向什么,以及您的代码是否应该对其进行写入。


1
在C语言中,确实没有一种干净的可移植方式来做到这一点。这将取决于操作系统。如果你使用C++,你可以尝试写入内存(在首先读取和保存已经存在的内容之后,以便在覆盖它时可以恢复它)。你可以将其包装在try catch中。如果你没有得到异常,那么看起来内存是可写的。但是多么危险啊!你正在写入内存,显然你不知道里面有什么。谁知道你正在覆盖什么! - DWright
2
分段错误(访问冲突、内存错误等)是CPU异常,而不是C++异常。它们无法被try/catch捕获;执行非法内存访问将会终止进程。 - user149341
请注意,您不能可移植地依赖它们不是C++异常。Windows具有SEH,它会在发生故障时抛出异常。这对您来说是未定义的行为... - Steve Jessop
1
这样做可能有合理的原因,例如如果您在超额提交系统上访问内存(现在几乎所有系统都是这样),当系统无法将物理内存页面映射到您的虚拟地址时,会出现总线错误。测试地址是否可写可以很好地帮助您更优雅地失败。 - Eloff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接