theorbtwo has asked for the wisdom of the Perl Monks concerning the following question:

Allo, all.

I'm attempting to do some pointer work, in pure perl, under win32 (winxp, with AS perl 5.8.2, build 808). In particular, at this point, I want to get a long out of memory at a given address. I've tried using Pointer, but found it to give mildly odd results, possibly because I don't really understand it's API.

My current incarnation looks like this:

sub get_long_at { my $addr=shift; printf "get_long_at(0x%x)\n", $addr; my $data=unpack("P4", $addr); printf "Got it\n"; return unpack("L", $data); }
This does not have the expected effect. In particular... I get printed out get_long_at(0x6761c) (the address is as expected.) Then, I get a nice dialog box:

The instruction at "0x77c4337c" referenced memory at "0x34333234". The memory could not be "read".

Click on OK to terminate the program
Click on CANCEL to debug the program
Note that 0x34333234 is the ASCII representation of 4324. What does this mean? I have no idea. It seems to be a constant, though. The backtrace, once I say debug, looks like this.
MSVCRT! 77c4337c() PERL58! Perl_sv_setpvn + 149 bytes PERL58! Perl_unpackstring + 2264 bytes PERL58! Perl_unpackstring + 58 bytes PERL58! Perl_unpackstring + 10147 bytes PERL58! Perl_runops_standard + 12 bytes PERL58! RunPerl + 134 bytes PERL! 00401012() KERNEL32! GetCurrentDirectoryW + 68 bytes

Note that the segfault is in Perl_sv_setpvn, and not directly in the upack code, which suggests to me that $data=unpack("P4", $addr); is doing something different then what I thought it did, and not that perl itself is buggy. Of course, it's always possible that I'm wrong.


Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Replies are listed 'Best First'.
Re: unpack "P" and a horrible death (packed)
by tye (Sage) on Mar 21, 2004 at 16:24 UTC
    my $long= unpack "P4", pack "L", $addr;
    my $long= unpack "P4", pack "I", $addr;

    Unpack wants a packed pointer, not a number. Unpack takes a string. If you give it a number, Perl will format the number into a string in decimal and pass that to unpack. Unpack will pull the first 4 characters (bytes) out and treat them like a (void *) and try to dereference that.

    Update: The replying Anonymonk isn't particularly helpful and the pack docs aren't exactly clear on this point (that I saw) but I suspect that "I" gives an IV which is defined to be big enough to hold a pointer.

    - tye        

      That will fail when pointers are larger than 32 bits.
Re: unpack "P" and a horrible death
by bart (Canon) on Mar 21, 2004 at 17:28 UTC
    I don't know, this works fine for me:
    $s = pack "L", 0x03141593; # 4 byte string $p = pack "P", $s; # pointer printf "%08X\n", unpack "L", unpack "P4", $p;
    Result:
    03141593
    
    It would appear to me that your code looks fine.

    update No it doesn't. pack "P", $string returns a packed pointer, i.e. a 4 byte structure, containing a packed integer with the numeric pointer value. unpack "P", $packed_pointer does the reverse: first unpacking the address from the 4 bytes, and then unpacking the string it points at. So it does a double unpack.

    $s = "Kettle Of Fish"; # 14 byte string $p = pack "P", $s; # packed pointer printf "Length of the pointer: %d\n", length $p; printf "10 byte string: %s\n", unpack "P10", $p;
    Result:
    Length of the pointer: 4
    10 byte string: Kettle Of 
    

    In summary: you'd have to convert your numerical pointer to a packed integer, and then unpack using "P4". You can then unpack the integer in that string.

    $int= unpack "L", unpack "P4", pack "L", $addr;
Re: unpack "P" and a horrible death
by matija (Priest) on Mar 21, 2004 at 16:50 UTC
    From what I can see, the correct code in Pointer would be:
    use Pointer::long; my $p=pointer('long'); $p->to($addr); # where $addr is something like 0x123456 $data=$p->get;