Question

I'm trying to create a small program that takes in a physical memory location and prints the data stored at the location. I'm passing two parameters into the program - the address, and the size (in bytes) of memory I want to print.

The issue I'm having is when the address I pass in gets above a certain value the strtol() function passes out a nonsensical value. Code below:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <asm-generic/fcntl.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    unsigned int mem_address,mem_size;
    int loop, i;
    int *ptr, *mem_address_current;

    printf("mem_addr: %s\n",argv[1]);
    printf("mem_size: %s\n",argv[2]);

    mem_address = strtol(argv[1], NULL, 16);
    mem_size = strtol(argv[2], NULL, 16);

    printf("mem_addr: %x\n",mem_address);
    printf("mem_size: %x\n",mem_size);

    int mem_dev = open("/dev/mem", O_RDWR);

    if(mem_dev == -1)
    {
        printf("No worky\n");
        exit(1);
    }

    int alloc_mem_size, page_mask, page_size;
    void *mem_pointer, *virt_addr;

    page_size = sysconf(_SC_PAGESIZE);
    alloc_mem_size = (((mem_size / page_size) + 1) * page_size);
    page_mask = (page_size - 1);

    mem_pointer = mmap(NULL,
                   alloc_mem_size,
                   PROT_READ | PROT_WRITE,
                   MAP_SHARED,
                   mem_dev,
                   (mem_address & ~page_mask)
                   );


    if(mem_pointer == MAP_FAILED)
    {  
        printf("no_worky2\n");
        exit(1) ;
    }

    virt_addr = (mem_pointer + (mem_address & page_mask)); 
    ptr = mem_pointer;
    loop = (mem_size/16) + 1;
    for(i = 0;i < loop;i++) {
        printf("%#x: %08x %08x %08x %08x\n", ptr, *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3));
        ptr = ptr + 4;
    }

    return 0;


}

If I run the the following command, I get the expected values.

root@p1025:~# ./test_prog_ppc 0100000 16                                                                                              
mem_addr: 0100000
mem_size: 16
mem_addr: 100000
mem_size: 16
0x48001000: 38210020 4e800020 9421ffe0 7c0802a6
0x48001010: 2c050000 bf410008 7c7e1b78 90010024

However, If I change the input address the value changes from what is expected:

root@p1025:~# ./test_prog_ppc ffee0000 16
mem_addr: ffee0000
mem_size: 16
mem_addr: 7fffffff
mem_size: 16

Any ideas as to why this would happen?

Thanks for the help.

Was it helpful?

Solution

strtol() returns a (signed) long. Based on your input, you are trying to parse unsigned 32 bit numbers, so if long is 32 bits on your system, strtol() is going to go out of range. In this case, it will return LONG_MAX, which on your system appears to be 0x7FFFFFFF.

You should use strtoul() instead, and store the result in an unsigned long, rather than an unsigned int as you are now, since while long is guaranteed to be at least 32 bits, int is not.

In addition, you really ought to be passing the address of a real pointer as the second argument to strtol() and friends, and using it to check for errors. Right now, if either of argv[1] or argv[2] did not start with a number, you wouldn't be able to distinguish whether the input was bad, or whether the input was a legitimate zero.

OTHER TIPS

This behaviour is documented, see strtol

RETURN VALUE
...
If the correct value is outside the range of representable values, {LONG_MIN}, {LONG_MAX}, {LLONG_MIN}, or {LLONG_MAX} shall be returned (according to the sign of the value), and errno set to [ERANGE].

Your return value is LONG_MAX and when you test errno, it should be ERANGE.

You could use one of strtoul, strtoll or strtoull, which all would be able to handle larger values. Although strtoul is restricted to unsigned long, so if you are on a 64 bit system, you should use one of the strto*ll functions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top