Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

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().

Replies are listed 'Best First'.
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?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://694272]
Approved by moritz
Front-paged by grinder
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (6)
As of 2018-04-25 11:19 GMT
Find Nodes?
    Voting Booth?