Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

XS on 64-bit: Warning from sv_vsetpvf call

by songmaster (Sexton)
on Jun 26, 2008 at 20:29 UTC ( #694272=perlquestion: print w/ replies, xml ) Need Help??
songmaster has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on an XS interface to an external C library. My current code builds and works fine on 32-bit x86 Linux (Fedora Core 8, Perl 5.8.8) and on Solaris 10, but when I build it for 64-bit Linux (FC8 on x86_64) I get this compiler warning and the program segfaults when this routine is called:

Cap5.xs: In function ‘printf_handler’: Cap5.xs:1013: warning: passing argument 4 of ‘Perl_sv_vsetpvf’ from incompatible pointer type

The relevant lines of Cap5.xs are:

static int printf_handler(const char *format, va_list args) { SV *printf_str; dSP; ENTER; SAVETMPS; printf_str = NEWSV(0, strlen(format) + 32); sv_vsetpvf(printf_str, format, &args);

Line 1013 is the last one shown above.

Perl's proto.h file declares Perl_sv_vsetpvf as:

PERL_CALLCONV void Perl_sv_vsetpvf(pTHX_ SV* sv, const char* pat, va_list* args);

The final argument there is a va_list*, which is exactly what my code is giving it. I'm not sure it really should have been a va_list* given that vprintf et al. take a va_list, but that's what the header says and it works fine on the other systems.

Interestingly, if I drop the & and just pass args as the final argument the code seems to work fine on 64-bit Linux (although the compiler still warns), but then it segfaults on the other systems.

Any advice? I'm guessing this may be a bug in gcc and not really a Perl problem at all...

Update: It appears that taking the address of a va_list is non-conforming C, so Perl's API is broken. See slide 19 of this PDF slideshow for details. I looked in RT and only found one bug report from 2002 that could be about this issue; should I report it?

2nd Update: On amd64 (and apparently on PowerPC too) va_list is defined by the ABI as an array:

typedef struct unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; } va_list[1];

Thus within printf_handler() the args variable is already a pointer to the structure. This explains why dropping the & works as I described in my original question. Unfortunately that's no use on other architectures though.

Using va_copy() (or its equivalent) to copy args into an auto variable allows me to take the address of the copy and pass that pointer into sv_vsetpvf().

Comment on XS on 64-bit: Warning from sv_vsetpvf call
Select or Download Code
Re: XS on 64-bit: Warning from sv_vsetpvf call
by almut (Canon) on Jun 27, 2008 at 01:55 UTC

    Not exactly sure what this "non-conforming" is about... (so I could be wrong), but I think there is no general problem passing around pointers to va_list. It's just that you shouldn't do something like

    int printf_handler(const char *format, va_list args) { ... sv_vsetpvf(printf_str, format, &args); ...

    with a 64-bit compiler — unless you're using the va_start etc. macros in that same routine, in which case you wouldn't have va_list in its parameter list.  OTOH, something like this is fine

    int printf_handler(const char *format, va_list* args) { ... sv_vsetpvf(printf_str, format, args); ...

    (of course, you'd then have to pass &args to printf_handler()...)

    Note that both versions do work fine on 32-bit.

    Consider the following example (admittedly somewhat contrived and in pure C only (no Perl/XS involved) — but this helps keep it simple). First the erroneous version:

    #include <stdarg.h> #include <stdio.h> #include <errno.h> void vatest__(char* format, va_list* args) { // this kind of represents sv_vsetpvf() char buf[1024]; vsprintf(buf, format, *args); errno = 0; // "Success" perror(buf); } void vatest_(char* format, va_list args) { // this represents your printf_handler() vatest__(format, &args); } void vatest(char* format, ...) { va_list args; va_start (args, format); vatest_(format, args); va_end (args); } int main (int argc, char *argv[]) { char* msg1 = "foo"; char* msg2 = "bar"; vatest("%s: %s", argv[0], msg1); // two args vatest("%s: %s,%s", argv[0], msg1, msg2); // three args return 0; }

    ___

    $ gcc -Wall vatest.c -o vatest vatest.c: In function `vatest_': vatest.c:17: warning: passing arg 2 of `vatest__' from incompatible po +inter type $ ./vatest Segmentation fault $ file vatest vatest: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for G +NU/Linux 2.6.4, dynamically linked (uses shared libs), for GNU/Linux +2.6.4, not stripped

    However, if you change the middle two routines as mentioned above

    ... void vatest_(char* format, va_list* args) { vatest__(format, args); } void vatest(char* format, ...) { va_list args; va_start (args, format); vatest_(format, &args); va_end (args); } ...

    things are working fine, even on 64-bit:

    $ gcc -Wall vatest.c -o vatest $ ./vatest ./vatest: foo: Success ./vatest: foo,bar: Success

    (tested with gcc 3.4.6 on 64-bit SUSE Linux, and with gcc 4.0.4 on a 32-bit Debian box)

      Unfortunately I can't change printf_handler()'s arguments — it's a callback routine for the external library, whose API I can't alter.

      My words "non conforming" came from that PDF slideshow I pointed to. Looking at the C99 spec (Draft of May 6, 2005) though, footnote 215 does say you can take the address of a va_list and pass it to other functions, so Perl's API isn't broken as I originally thought. It's not obvious how passing the va_list by value affects the ability to then take the address of the copy, but it certainly seems to.

      Hmm, could I copy the original va_list object using va_copy() and then take the address of the copy? That's not particularly efficient, but efficiency doesn't matter much in this case which is in an error path anyway...

      Ok, so va_copy() isn't defined, but using __va_copy() works on both 32 and 64-bit systems, with no warnings. Of course using __va_copy() isn't going to be portable to other compilers, but I can probably live with a special case for 64-bit Linux.

        va_copy() was defined in C99. When using gcc you can get it with either -std=c99 or -std=gnu99.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://694272]
Approved by moritz
Front-paged by grinder
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (4)
As of 2015-07-03 23:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (57 votes), past polls