brng.dev

Brandon's Bytes


Project: libmem [1]: Wait, we can access physical addresses?

05 Feb 2018


This post is a copy from my old website and blog. I have kept its original post date.

This came as a surprise to me too. We learn early on in our operating systems course(s) that it’s the operating system’s job to protect the system’s resources from our grubby use-land paws and virtualize the memory space for userprocesses so we don’t compromise our computer.

If you are unfamiliar with the philosophy of *nix systems, it’s that everything is a file. The filesystem represents not only the existing physical filesystems that contain your documents and binaries that live on your mounted partitions but also the “virtual” filesystem that contains vital information about the system itself. Linux, for example, provides a lot of process information under /proc. This discussion can be saved for a later post/post series. More importantly for this post, /dev is the place where devices are exposed as files that you can read/write to. Turns out, there is the /dev/mem device file that exposes the physical address space! Quoting the manpage (man 4 mem, linux.die.net):

/dev/mem is a character device file that is an image
of the main memory of the computer. It may be used,
for example, to examine (and even patch) the system.

Byte addresses in /dev/mem are interpreted as physical
memory addresses. References to nonexistent locations
cause errors to be returned.

Armed with this knowledge, we can actually open this file to read and write to physical addresses!

Now let’s look at some code. I’ve written a really quick little chunk of code that reads from an address passed in from the command line: dm.c

Let’s take a look at what’s going on:

int main(int argc, char ** argv)
{
    if (argc < 2) {
        fprintf(stderr, "Provide address: dm <address>\n");
        return 1;
    }

This is just some C boilerplate to handle a lack of arguments.

    void * addr = NULL;
    uint32_t val = 0xDEADBEEF;

    sscanf(argv[1], "%p", &addr);

Here I’m providing some default values for the passed in address and the read value. sscanf() is used to parse the command line input for the address.

    int fd = open("/dev/mem", O_RDONLY, S_IRUSR);
    if (fd == -1) {
        fprintf(stderr, "Could not open /dev/mem: do you have permissions?\n");
        return 2;
    }

Here I’m opening the /dev/mem device using the POSIX open command, which provides a file descriptor. Note that this is different from fopen() of the C Standard Library that provides a FILE pointer. The flags are there for opening it as read-only.

    off_t offset = (off_t) (uintptr_t) addr;
    lseek(fd, offset, SEEK_SET);
    read(fd, &val, sizeof(uint32_t));
    printf("[%p] = 0x%08x\n", addr, val);

Here is where the more interesting stuff happens. The physical memory address we pass in effectively is the offset in the memory device file. I use lseek() to move the file position to that offset, which really is just cast from the address, and then perform a read on the file and write to val. This is then printed.

Compiling and running it on the ZedBoard (note the use of sudo):

That’s it! That’s pretty simple. Next, I’m going to encapsulate this functionality behind a library and API!