Back to index

cell-binutils  2.17cvs20070401
openrisc-asm.c
Go to the documentation of this file.
00001 /* Assembler interface for targets using CGEN. -*- C -*-
00002    CGEN: Cpu tools GENerator
00003 
00004    THIS FILE IS MACHINE GENERATED WITH CGEN.
00005    - the resultant file is machine generated, cgen-asm.in isn't
00006 
00007    Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2005
00008    Free Software Foundation, Inc.
00009 
00010    This file is part of the GNU Binutils and GDB, the GNU debugger.
00011 
00012    This program is free software; you can redistribute it and/or modify
00013    it under the terms of the GNU General Public License as published by
00014    the Free Software Foundation; either version 2, or (at your option)
00015    any later version.
00016 
00017    This program is distributed in the hope that it will be useful,
00018    but WITHOUT ANY WARRANTY; without even the implied warranty of
00019    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020    GNU General Public License for more details.
00021 
00022    You should have received a copy of the GNU General Public License
00023    along with this program; if not, write to the Free Software Foundation, Inc.,
00024    51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.  */
00025 
00026 /* ??? Eventually more and more of this stuff can go to cpu-independent files.
00027    Keep that in mind.  */
00028 
00029 #include "sysdep.h"
00030 #include <stdio.h>
00031 #include "ansidecl.h"
00032 #include "bfd.h"
00033 #include "symcat.h"
00034 #include "openrisc-desc.h"
00035 #include "openrisc-opc.h"
00036 #include "opintl.h"
00037 #include "xregex.h"
00038 #include "libiberty.h"
00039 #include "safe-ctype.h"
00040 
00041 #undef  min
00042 #define min(a,b) ((a) < (b) ? (a) : (b))
00043 #undef  max
00044 #define max(a,b) ((a) > (b) ? (a) : (b))
00045 
00046 static const char * parse_insn_normal
00047   (CGEN_CPU_DESC, const CGEN_INSN *, const char **, CGEN_FIELDS *);
00048 
00049 /* -- assembler routines inserted here.  */
00050 
00051 /* -- asm.c */
00052 
00053 static const char * MISSING_CLOSING_PARENTHESIS = N_("missing `)'");
00054 
00055 #define CGEN_VERBOSE_ASSEMBLER_ERRORS
00056 
00057 long
00058 openrisc_sign_extend_16bit (long value)
00059 {
00060   return ((value & 0xffff) ^ 0x8000) - 0x8000;
00061 }
00062 
00063 /* Handle hi().  */
00064 
00065 static const char *
00066 parse_hi16 (CGEN_CPU_DESC cd, const char ** strp, int opindex, long * valuep)
00067 {
00068   const char *errmsg;
00069   enum cgen_parse_operand_result result_type;
00070   unsigned long ret;
00071 
00072   if (**strp == '#')
00073     ++*strp;
00074 
00075   if (strncasecmp (*strp, "hi(", 3) == 0)
00076     {
00077       bfd_vma value;
00078 
00079       *strp += 3;
00080       errmsg = cgen_parse_address (cd, strp, opindex, BFD_RELOC_HI16,
00081                                & result_type, & value);
00082       if (**strp != ')')
00083         return MISSING_CLOSING_PARENTHESIS;
00084 
00085       ++*strp;
00086       if (errmsg == NULL
00087           && result_type == CGEN_PARSE_OPERAND_RESULT_NUMBER)
00088         value >>= 16;
00089       ret = value;
00090     }
00091   else
00092     {
00093       if (**strp == '-')
00094        {
00095          long value;
00096 
00097          errmsg = cgen_parse_signed_integer (cd, strp, opindex, &value);
00098          ret = value;
00099        }
00100       else
00101        {
00102          unsigned long value;
00103 
00104          errmsg = cgen_parse_unsigned_integer (cd, strp, opindex, &value);
00105          ret = value;
00106        }
00107     }
00108 
00109   *valuep = ((ret & 0xffff) ^ 0x8000) - 0x8000;
00110   return errmsg;
00111 }
00112 
00113 /* Handle lo().  */
00114 
00115 static const char *
00116 parse_lo16 (CGEN_CPU_DESC cd, const char ** strp, int opindex, long * valuep)
00117 {
00118   const char *errmsg;
00119   enum cgen_parse_operand_result result_type;
00120   unsigned long ret;
00121 
00122   if (**strp == '#')
00123     ++*strp;
00124 
00125   if (strncasecmp (*strp, "lo(", 3) == 0)
00126     {
00127       bfd_vma value;
00128 
00129       *strp += 3;
00130       errmsg = cgen_parse_address (cd, strp, opindex, BFD_RELOC_LO16,
00131                                & result_type, & value);
00132       if (**strp != ')')
00133         return MISSING_CLOSING_PARENTHESIS;
00134 
00135       ++*strp;
00136       ret = value;
00137     }
00138   else
00139     {
00140       if (**strp == '-')
00141        {
00142          long value;
00143 
00144          errmsg = cgen_parse_signed_integer (cd, strp, opindex, &value);
00145          ret = value;
00146        }
00147       else
00148        {
00149          unsigned long value;
00150 
00151          errmsg = cgen_parse_unsigned_integer (cd, strp, opindex, &value);
00152          ret = value;
00153        }
00154     }
00155 
00156   *valuep = ((ret & 0xffff) ^ 0x8000) - 0x8000;
00157   return errmsg;
00158 }
00159 
00160 /* -- */
00161 
00162 const char * openrisc_cgen_parse_operand
00163   (CGEN_CPU_DESC, int, const char **, CGEN_FIELDS *);
00164 
00165 /* Main entry point for operand parsing.
00166 
00167    This function is basically just a big switch statement.  Earlier versions
00168    used tables to look up the function to use, but
00169    - if the table contains both assembler and disassembler functions then
00170      the disassembler contains much of the assembler and vice-versa,
00171    - there's a lot of inlining possibilities as things grow,
00172    - using a switch statement avoids the function call overhead.
00173 
00174    This function could be moved into `parse_insn_normal', but keeping it
00175    separate makes clear the interface between `parse_insn_normal' and each of
00176    the handlers.  */
00177 
00178 const char *
00179 openrisc_cgen_parse_operand (CGEN_CPU_DESC cd,
00180                         int opindex,
00181                         const char ** strp,
00182                         CGEN_FIELDS * fields)
00183 {
00184   const char * errmsg = NULL;
00185   /* Used by scalar operands that still need to be parsed.  */
00186   long junk ATTRIBUTE_UNUSED;
00187 
00188   switch (opindex)
00189     {
00190     case OPENRISC_OPERAND_ABS_26 :
00191       {
00192         bfd_vma value = 0;
00193         errmsg = cgen_parse_address (cd, strp, OPENRISC_OPERAND_ABS_26, 0, NULL,  & value);
00194         fields->f_abs26 = value;
00195       }
00196       break;
00197     case OPENRISC_OPERAND_DISP_26 :
00198       {
00199         bfd_vma value = 0;
00200         errmsg = cgen_parse_address (cd, strp, OPENRISC_OPERAND_DISP_26, 0, NULL,  & value);
00201         fields->f_disp26 = value;
00202       }
00203       break;
00204     case OPENRISC_OPERAND_HI16 :
00205       errmsg = parse_hi16 (cd, strp, OPENRISC_OPERAND_HI16, (long *) (& fields->f_simm16));
00206       break;
00207     case OPENRISC_OPERAND_LO16 :
00208       errmsg = parse_lo16 (cd, strp, OPENRISC_OPERAND_LO16, (long *) (& fields->f_lo16));
00209       break;
00210     case OPENRISC_OPERAND_OP_F_23 :
00211       errmsg = cgen_parse_unsigned_integer (cd, strp, OPENRISC_OPERAND_OP_F_23, (unsigned long *) (& fields->f_op4));
00212       break;
00213     case OPENRISC_OPERAND_OP_F_3 :
00214       errmsg = cgen_parse_unsigned_integer (cd, strp, OPENRISC_OPERAND_OP_F_3, (unsigned long *) (& fields->f_op5));
00215       break;
00216     case OPENRISC_OPERAND_RA :
00217       errmsg = cgen_parse_keyword (cd, strp, & openrisc_cgen_opval_h_gr, & fields->f_r2);
00218       break;
00219     case OPENRISC_OPERAND_RB :
00220       errmsg = cgen_parse_keyword (cd, strp, & openrisc_cgen_opval_h_gr, & fields->f_r3);
00221       break;
00222     case OPENRISC_OPERAND_RD :
00223       errmsg = cgen_parse_keyword (cd, strp, & openrisc_cgen_opval_h_gr, & fields->f_r1);
00224       break;
00225     case OPENRISC_OPERAND_SIMM_16 :
00226       errmsg = cgen_parse_signed_integer (cd, strp, OPENRISC_OPERAND_SIMM_16, (long *) (& fields->f_simm16));
00227       break;
00228     case OPENRISC_OPERAND_UI16NC :
00229       errmsg = parse_lo16 (cd, strp, OPENRISC_OPERAND_UI16NC, (long *) (& fields->f_i16nc));
00230       break;
00231     case OPENRISC_OPERAND_UIMM_16 :
00232       errmsg = cgen_parse_unsigned_integer (cd, strp, OPENRISC_OPERAND_UIMM_16, (unsigned long *) (& fields->f_uimm16));
00233       break;
00234     case OPENRISC_OPERAND_UIMM_5 :
00235       errmsg = cgen_parse_unsigned_integer (cd, strp, OPENRISC_OPERAND_UIMM_5, (unsigned long *) (& fields->f_uimm5));
00236       break;
00237 
00238     default :
00239       /* xgettext:c-format */
00240       fprintf (stderr, _("Unrecognized field %d while parsing.\n"), opindex);
00241       abort ();
00242   }
00243 
00244   return errmsg;
00245 }
00246 
00247 cgen_parse_fn * const openrisc_cgen_parse_handlers[] = 
00248 {
00249   parse_insn_normal,
00250 };
00251 
00252 void
00253 openrisc_cgen_init_asm (CGEN_CPU_DESC cd)
00254 {
00255   openrisc_cgen_init_opcode_table (cd);
00256   openrisc_cgen_init_ibld_table (cd);
00257   cd->parse_handlers = & openrisc_cgen_parse_handlers[0];
00258   cd->parse_operand = openrisc_cgen_parse_operand;
00259 #ifdef CGEN_ASM_INIT_HOOK
00260 CGEN_ASM_INIT_HOOK
00261 #endif
00262 }
00263 
00264 
00265 
00266 /* Regex construction routine.
00267 
00268    This translates an opcode syntax string into a regex string,
00269    by replacing any non-character syntax element (such as an
00270    opcode) with the pattern '.*'
00271 
00272    It then compiles the regex and stores it in the opcode, for
00273    later use by openrisc_cgen_assemble_insn
00274 
00275    Returns NULL for success, an error message for failure.  */
00276 
00277 char * 
00278 openrisc_cgen_build_insn_regex (CGEN_INSN *insn)
00279 {  
00280   CGEN_OPCODE *opc = (CGEN_OPCODE *) CGEN_INSN_OPCODE (insn);
00281   const char *mnem = CGEN_INSN_MNEMONIC (insn);
00282   char rxbuf[CGEN_MAX_RX_ELEMENTS];
00283   char *rx = rxbuf;
00284   const CGEN_SYNTAX_CHAR_TYPE *syn;
00285   int reg_err;
00286 
00287   syn = CGEN_SYNTAX_STRING (CGEN_OPCODE_SYNTAX (opc));
00288 
00289   /* Mnemonics come first in the syntax string.  */
00290   if (! CGEN_SYNTAX_MNEMONIC_P (* syn))
00291     return _("missing mnemonic in syntax string");
00292   ++syn;
00293 
00294   /* Generate a case sensitive regular expression that emulates case
00295      insensitive matching in the "C" locale.  We cannot generate a case
00296      insensitive regular expression because in Turkish locales, 'i' and 'I'
00297      are not equal modulo case conversion.  */
00298 
00299   /* Copy the literal mnemonic out of the insn.  */
00300   for (; *mnem; mnem++)
00301     {
00302       char c = *mnem;
00303 
00304       if (ISALPHA (c))
00305        {
00306          *rx++ = '[';
00307          *rx++ = TOLOWER (c);
00308          *rx++ = TOUPPER (c);
00309          *rx++ = ']';
00310        }
00311       else
00312        *rx++ = c;
00313     }
00314 
00315   /* Copy any remaining literals from the syntax string into the rx.  */
00316   for(; * syn != 0 && rx <= rxbuf + (CGEN_MAX_RX_ELEMENTS - 7 - 4); ++syn)
00317     {
00318       if (CGEN_SYNTAX_CHAR_P (* syn)) 
00319        {
00320          char c = CGEN_SYNTAX_CHAR (* syn);
00321 
00322          switch (c) 
00323            {
00324              /* Escape any regex metacharacters in the syntax.  */
00325            case '.': case '[': case '\\': 
00326            case '*': case '^': case '$': 
00327 
00328 #ifdef CGEN_ESCAPE_EXTENDED_REGEX
00329            case '?': case '{': case '}': 
00330            case '(': case ')': case '*':
00331            case '|': case '+': case ']':
00332 #endif
00333              *rx++ = '\\';
00334              *rx++ = c;
00335              break;
00336 
00337            default:
00338              if (ISALPHA (c))
00339               {
00340                 *rx++ = '[';
00341                 *rx++ = TOLOWER (c);
00342                 *rx++ = TOUPPER (c);
00343                 *rx++ = ']';
00344               }
00345              else
00346               *rx++ = c;
00347              break;
00348            }
00349        }
00350       else
00351        {
00352          /* Replace non-syntax fields with globs.  */
00353          *rx++ = '.';
00354          *rx++ = '*';
00355        }
00356     }
00357 
00358   /* Trailing whitespace ok.  */
00359   * rx++ = '['; 
00360   * rx++ = ' '; 
00361   * rx++ = '\t'; 
00362   * rx++ = ']'; 
00363   * rx++ = '*'; 
00364 
00365   /* But anchor it after that.  */
00366   * rx++ = '$'; 
00367   * rx = '\0';
00368 
00369   CGEN_INSN_RX (insn) = xmalloc (sizeof (regex_t));
00370   reg_err = regcomp ((regex_t *) CGEN_INSN_RX (insn), rxbuf, REG_NOSUB);
00371 
00372   if (reg_err == 0) 
00373     return NULL;
00374   else
00375     {
00376       static char msg[80];
00377 
00378       regerror (reg_err, (regex_t *) CGEN_INSN_RX (insn), msg, 80);
00379       regfree ((regex_t *) CGEN_INSN_RX (insn));
00380       free (CGEN_INSN_RX (insn));
00381       (CGEN_INSN_RX (insn)) = NULL;
00382       return msg;
00383     }
00384 }
00385 
00386 
00387 /* Default insn parser.
00388 
00389    The syntax string is scanned and operands are parsed and stored in FIELDS.
00390    Relocs are queued as we go via other callbacks.
00391 
00392    ??? Note that this is currently an all-or-nothing parser.  If we fail to
00393    parse the instruction, we return 0 and the caller will start over from
00394    the beginning.  Backtracking will be necessary in parsing subexpressions,
00395    but that can be handled there.  Not handling backtracking here may get
00396    expensive in the case of the m68k.  Deal with later.
00397 
00398    Returns NULL for success, an error message for failure.  */
00399 
00400 static const char *
00401 parse_insn_normal (CGEN_CPU_DESC cd,
00402                  const CGEN_INSN *insn,
00403                  const char **strp,
00404                  CGEN_FIELDS *fields)
00405 {
00406   /* ??? Runtime added insns not handled yet.  */
00407   const CGEN_SYNTAX *syntax = CGEN_INSN_SYNTAX (insn);
00408   const char *str = *strp;
00409   const char *errmsg;
00410   const char *p;
00411   const CGEN_SYNTAX_CHAR_TYPE * syn;
00412 #ifdef CGEN_MNEMONIC_OPERANDS
00413   /* FIXME: wip */
00414   int past_opcode_p;
00415 #endif
00416 
00417   /* For now we assume the mnemonic is first (there are no leading operands).
00418      We can parse it without needing to set up operand parsing.
00419      GAS's input scrubber will ensure mnemonics are lowercase, but we may
00420      not be called from GAS.  */
00421   p = CGEN_INSN_MNEMONIC (insn);
00422   while (*p && TOLOWER (*p) == TOLOWER (*str))
00423     ++p, ++str;
00424 
00425   if (* p)
00426     return _("unrecognized instruction");
00427 
00428 #ifndef CGEN_MNEMONIC_OPERANDS
00429   if (* str && ! ISSPACE (* str))
00430     return _("unrecognized instruction");
00431 #endif
00432 
00433   CGEN_INIT_PARSE (cd);
00434   cgen_init_parse_operand (cd);
00435 #ifdef CGEN_MNEMONIC_OPERANDS
00436   past_opcode_p = 0;
00437 #endif
00438 
00439   /* We don't check for (*str != '\0') here because we want to parse
00440      any trailing fake arguments in the syntax string.  */
00441   syn = CGEN_SYNTAX_STRING (syntax);
00442 
00443   /* Mnemonics come first for now, ensure valid string.  */
00444   if (! CGEN_SYNTAX_MNEMONIC_P (* syn))
00445     abort ();
00446 
00447   ++syn;
00448 
00449   while (* syn != 0)
00450     {
00451       /* Non operand chars must match exactly.  */
00452       if (CGEN_SYNTAX_CHAR_P (* syn))
00453        {
00454          /* FIXME: While we allow for non-GAS callers above, we assume the
00455             first char after the mnemonic part is a space.  */
00456          /* FIXME: We also take inappropriate advantage of the fact that
00457             GAS's input scrubber will remove extraneous blanks.  */
00458          if (TOLOWER (*str) == TOLOWER (CGEN_SYNTAX_CHAR (* syn)))
00459            {
00460 #ifdef CGEN_MNEMONIC_OPERANDS
00461              if (CGEN_SYNTAX_CHAR(* syn) == ' ')
00462               past_opcode_p = 1;
00463 #endif
00464              ++ syn;
00465              ++ str;
00466            }
00467          else if (*str)
00468            {
00469              /* Syntax char didn't match.  Can't be this insn.  */
00470              static char msg [80];
00471 
00472              /* xgettext:c-format */
00473              sprintf (msg, _("syntax error (expected char `%c', found `%c')"),
00474                      CGEN_SYNTAX_CHAR(*syn), *str);
00475              return msg;
00476            }
00477          else
00478            {
00479              /* Ran out of input.  */
00480              static char msg [80];
00481 
00482              /* xgettext:c-format */
00483              sprintf (msg, _("syntax error (expected char `%c', found end of instruction)"),
00484                      CGEN_SYNTAX_CHAR(*syn));
00485              return msg;
00486            }
00487          continue;
00488        }
00489 
00490       /* We have an operand of some sort.  */
00491       errmsg = cd->parse_operand (cd, CGEN_SYNTAX_FIELD (*syn),
00492                                      &str, fields);
00493       if (errmsg)
00494        return errmsg;
00495 
00496       /* Done with this operand, continue with next one.  */
00497       ++ syn;
00498     }
00499 
00500   /* If we're at the end of the syntax string, we're done.  */
00501   if (* syn == 0)
00502     {
00503       /* FIXME: For the moment we assume a valid `str' can only contain
00504         blanks now.  IE: We needn't try again with a longer version of
00505         the insn and it is assumed that longer versions of insns appear
00506         before shorter ones (eg: lsr r2,r3,1 vs lsr r2,r3).  */
00507       while (ISSPACE (* str))
00508        ++ str;
00509 
00510       if (* str != '\0')
00511        return _("junk at end of line"); /* FIXME: would like to include `str' */
00512 
00513       return NULL;
00514     }
00515 
00516   /* We couldn't parse it.  */
00517   return _("unrecognized instruction");
00518 }
00519 
00520 /* Main entry point.
00521    This routine is called for each instruction to be assembled.
00522    STR points to the insn to be assembled.
00523    We assume all necessary tables have been initialized.
00524    The assembled instruction, less any fixups, is stored in BUF.
00525    Remember that if CGEN_INT_INSN_P then BUF is an int and thus the value
00526    still needs to be converted to target byte order, otherwise BUF is an array
00527    of bytes in target byte order.
00528    The result is a pointer to the insn's entry in the opcode table,
00529    or NULL if an error occured (an error message will have already been
00530    printed).
00531 
00532    Note that when processing (non-alias) macro-insns,
00533    this function recurses.
00534 
00535    ??? It's possible to make this cpu-independent.
00536    One would have to deal with a few minor things.
00537    At this point in time doing so would be more of a curiosity than useful
00538    [for example this file isn't _that_ big], but keeping the possibility in
00539    mind helps keep the design clean.  */
00540 
00541 const CGEN_INSN *
00542 openrisc_cgen_assemble_insn (CGEN_CPU_DESC cd,
00543                         const char *str,
00544                         CGEN_FIELDS *fields,
00545                         CGEN_INSN_BYTES_PTR buf,
00546                         char **errmsg)
00547 {
00548   const char *start;
00549   CGEN_INSN_LIST *ilist;
00550   const char *parse_errmsg = NULL;
00551   const char *insert_errmsg = NULL;
00552   int recognized_mnemonic = 0;
00553 
00554   /* Skip leading white space.  */
00555   while (ISSPACE (* str))
00556     ++ str;
00557 
00558   /* The instructions are stored in hashed lists.
00559      Get the first in the list.  */
00560   ilist = CGEN_ASM_LOOKUP_INSN (cd, str);
00561 
00562   /* Keep looking until we find a match.  */
00563   start = str;
00564   for ( ; ilist != NULL ; ilist = CGEN_ASM_NEXT_INSN (ilist))
00565     {
00566       const CGEN_INSN *insn = ilist->insn;
00567       recognized_mnemonic = 1;
00568 
00569 #ifdef CGEN_VALIDATE_INSN_SUPPORTED 
00570       /* Not usually needed as unsupported opcodes
00571         shouldn't be in the hash lists.  */
00572       /* Is this insn supported by the selected cpu?  */
00573       if (! openrisc_cgen_insn_supported (cd, insn))
00574        continue;
00575 #endif
00576       /* If the RELAXED attribute is set, this is an insn that shouldn't be
00577         chosen immediately.  Instead, it is used during assembler/linker
00578         relaxation if possible.  */
00579       if (CGEN_INSN_ATTR_VALUE (insn, CGEN_INSN_RELAXED) != 0)
00580        continue;
00581 
00582       str = start;
00583 
00584       /* Skip this insn if str doesn't look right lexically.  */
00585       if (CGEN_INSN_RX (insn) != NULL &&
00586          regexec ((regex_t *) CGEN_INSN_RX (insn), str, 0, NULL, 0) == REG_NOMATCH)
00587        continue;
00588 
00589       /* Allow parse/insert handlers to obtain length of insn.  */
00590       CGEN_FIELDS_BITSIZE (fields) = CGEN_INSN_BITSIZE (insn);
00591 
00592       parse_errmsg = CGEN_PARSE_FN (cd, insn) (cd, insn, & str, fields);
00593       if (parse_errmsg != NULL)
00594        continue;
00595 
00596       /* ??? 0 is passed for `pc'.  */
00597       insert_errmsg = CGEN_INSERT_FN (cd, insn) (cd, insn, fields, buf,
00598                                            (bfd_vma) 0);
00599       if (insert_errmsg != NULL)
00600         continue;
00601 
00602       /* It is up to the caller to actually output the insn and any
00603          queued relocs.  */
00604       return insn;
00605     }
00606 
00607   {
00608     static char errbuf[150];
00609 #ifdef CGEN_VERBOSE_ASSEMBLER_ERRORS
00610     const char *tmp_errmsg;
00611 
00612     /* If requesting verbose error messages, use insert_errmsg.
00613        Failing that, use parse_errmsg.  */
00614     tmp_errmsg = (insert_errmsg ? insert_errmsg :
00615                 parse_errmsg ? parse_errmsg :
00616                 recognized_mnemonic ?
00617                 _("unrecognized form of instruction") :
00618                 _("unrecognized instruction"));
00619 
00620     if (strlen (start) > 50)
00621       /* xgettext:c-format */
00622       sprintf (errbuf, "%s `%.50s...'", tmp_errmsg, start);
00623     else 
00624       /* xgettext:c-format */
00625       sprintf (errbuf, "%s `%.50s'", tmp_errmsg, start);
00626 #else
00627     if (strlen (start) > 50)
00628       /* xgettext:c-format */
00629       sprintf (errbuf, _("bad instruction `%.50s...'"), start);
00630     else 
00631       /* xgettext:c-format */
00632       sprintf (errbuf, _("bad instruction `%.50s'"), start);
00633 #endif
00634       
00635     *errmsg = errbuf;
00636     return NULL;
00637   }
00638 }