Home | History | Annotate | Line # | Download | only in opcodes
wasm32-dis.c revision 1.1
      1  1.1  christos /* Opcode printing code for the WebAssembly target
      2  1.1  christos    Copyright (C) 2017 Free Software Foundation, Inc.
      3  1.1  christos 
      4  1.1  christos    This file is part of libopcodes.
      5  1.1  christos 
      6  1.1  christos    This library is free software; you can redistribute it and/or modify
      7  1.1  christos    it under the terms of the GNU General Public License as published by
      8  1.1  christos    the Free Software Foundation; either version 3 of the License, or
      9  1.1  christos    (at your option) any later version.
     10  1.1  christos 
     11  1.1  christos    It is distributed in the hope that it will be useful, but WITHOUT
     12  1.1  christos    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     13  1.1  christos    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
     14  1.1  christos    License for more details.
     15  1.1  christos 
     16  1.1  christos    You should have received a copy of the GNU General Public License
     17  1.1  christos    along with this program; if not, write to the Free Software
     18  1.1  christos    Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
     19  1.1  christos    MA 02110-1301, USA.  */
     20  1.1  christos 
     21  1.1  christos #include "sysdep.h"
     22  1.1  christos #include "dis-asm.h"
     23  1.1  christos #include "opintl.h"
     24  1.1  christos #include "safe-ctype.h"
     25  1.1  christos #include "floatformat.h"
     26  1.1  christos #include "libiberty.h"
     27  1.1  christos #include "elf-bfd.h"
     28  1.1  christos #include "elf/internal.h"
     29  1.1  christos #include "elf/wasm32.h"
     30  1.1  christos #include <stdint.h>
     31  1.1  christos 
     32  1.1  christos /* Type names for blocks and signatures.  */
     33  1.1  christos #define BLOCK_TYPE_NONE              0x40
     34  1.1  christos #define BLOCK_TYPE_I32               0x7f
     35  1.1  christos #define BLOCK_TYPE_I64               0x7e
     36  1.1  christos #define BLOCK_TYPE_F32               0x7d
     37  1.1  christos #define BLOCK_TYPE_F64               0x7c
     38  1.1  christos 
     39  1.1  christos enum wasm_class
     40  1.1  christos {
     41  1.1  christos   wasm_typed,
     42  1.1  christos   wasm_special,
     43  1.1  christos   wasm_break,
     44  1.1  christos   wasm_break_if,
     45  1.1  christos   wasm_break_table,
     46  1.1  christos   wasm_return,
     47  1.1  christos   wasm_call,
     48  1.1  christos   wasm_call_import,
     49  1.1  christos   wasm_call_indirect,
     50  1.1  christos   wasm_get_local,
     51  1.1  christos   wasm_set_local,
     52  1.1  christos   wasm_tee_local,
     53  1.1  christos   wasm_drop,
     54  1.1  christos   wasm_constant_i32,
     55  1.1  christos   wasm_constant_i64,
     56  1.1  christos   wasm_constant_f32,
     57  1.1  christos   wasm_constant_f64,
     58  1.1  christos   wasm_unary,
     59  1.1  christos   wasm_binary,
     60  1.1  christos   wasm_conv,
     61  1.1  christos   wasm_load,
     62  1.1  christos   wasm_store,
     63  1.1  christos   wasm_select,
     64  1.1  christos   wasm_relational,
     65  1.1  christos   wasm_eqz,
     66  1.1  christos   wasm_current_memory,
     67  1.1  christos   wasm_grow_memory,
     68  1.1  christos   wasm_signature
     69  1.1  christos };
     70  1.1  christos 
     71  1.1  christos struct wasm32_private_data
     72  1.1  christos {
     73  1.1  christos   bfd_boolean print_registers;
     74  1.1  christos   bfd_boolean print_well_known_globals;
     75  1.1  christos 
     76  1.1  christos   /* Limit valid symbols to those with a given prefix.  */
     77  1.1  christos   const char *section_prefix;
     78  1.1  christos };
     79  1.1  christos 
     80  1.1  christos typedef struct
     81  1.1  christos {
     82  1.1  christos   const char *name;
     83  1.1  christos   const char *description;
     84  1.1  christos } wasm32_options_t;
     85  1.1  christos 
     86  1.1  christos static const wasm32_options_t options[] =
     87  1.1  christos {
     88  1.1  christos   { "registers", N_("Disassemble \"register\" names") },
     89  1.1  christos   { "globals",   N_("Name well-known globals") },
     90  1.1  christos };
     91  1.1  christos 
     92  1.1  christos #define WASM_OPCODE(opcode, name, intype, outtype, clas, signedness)     \
     93  1.1  christos   { name, wasm_ ## clas, opcode },
     94  1.1  christos 
     95  1.1  christos struct wasm32_opcode_s
     96  1.1  christos {
     97  1.1  christos   const char *name;
     98  1.1  christos   enum wasm_class clas;
     99  1.1  christos   unsigned char opcode;
    100  1.1  christos } wasm32_opcodes[] =
    101  1.1  christos {
    102  1.1  christos #include "opcode/wasm.h"
    103  1.1  christos   { NULL, 0, 0 }
    104  1.1  christos };
    105  1.1  christos 
    106  1.1  christos /* Parse the disassembler options in OPTS and initialize INFO.  */
    107  1.1  christos 
    108  1.1  christos static void
    109  1.1  christos parse_wasm32_disassembler_options (struct disassemble_info *info,
    110  1.1  christos                                    const char *opts)
    111  1.1  christos {
    112  1.1  christos   struct wasm32_private_data *private = info->private_data;
    113  1.1  christos 
    114  1.1  christos   while (opts != NULL)
    115  1.1  christos     {
    116  1.1  christos       if (CONST_STRNEQ (opts, "registers"))
    117  1.1  christos         private->print_registers = TRUE;
    118  1.1  christos       else if (CONST_STRNEQ (opts, "globals"))
    119  1.1  christos         private->print_well_known_globals = TRUE;
    120  1.1  christos 
    121  1.1  christos       opts = strchr (opts, ',');
    122  1.1  christos       if (opts)
    123  1.1  christos         opts++;
    124  1.1  christos     }
    125  1.1  christos }
    126  1.1  christos 
    127  1.1  christos /* Check whether SYM is valid.  Special-case absolute symbols, which
    128  1.1  christos    are unhelpful to print, and arguments to a "call" insn, which we
    129  1.1  christos    want to be in a section matching a given prefix.  */
    130  1.1  christos 
    131  1.1  christos static bfd_boolean
    132  1.1  christos wasm32_symbol_is_valid (asymbol *sym,
    133  1.1  christos                         struct disassemble_info *info)
    134  1.1  christos {
    135  1.1  christos   struct wasm32_private_data *private_data = info->private_data;
    136  1.1  christos 
    137  1.1  christos   if (sym == NULL)
    138  1.1  christos     return FALSE;
    139  1.1  christos 
    140  1.1  christos   if (strcmp(sym->section->name, "*ABS*") == 0)
    141  1.1  christos     return FALSE;
    142  1.1  christos 
    143  1.1  christos   if (private_data && private_data->section_prefix != NULL
    144  1.1  christos       && strncmp (sym->section->name, private_data->section_prefix,
    145  1.1  christos                   strlen (private_data->section_prefix)))
    146  1.1  christos     return FALSE;
    147  1.1  christos 
    148  1.1  christos   return TRUE;
    149  1.1  christos }
    150  1.1  christos 
    151  1.1  christos /* Initialize the disassembler structures for INFO.  */
    152  1.1  christos 
    153  1.1  christos void
    154  1.1  christos disassemble_init_wasm32 (struct disassemble_info *info)
    155  1.1  christos {
    156  1.1  christos   if (info->private_data == NULL)
    157  1.1  christos     {
    158  1.1  christos       static struct wasm32_private_data private;
    159  1.1  christos 
    160  1.1  christos       private.print_registers = FALSE;
    161  1.1  christos       private.print_well_known_globals = FALSE;
    162  1.1  christos       private.section_prefix = NULL;
    163  1.1  christos 
    164  1.1  christos       info->private_data = &private;
    165  1.1  christos     }
    166  1.1  christos 
    167  1.1  christos   if (info->disassembler_options)
    168  1.1  christos     {
    169  1.1  christos       parse_wasm32_disassembler_options (info, info->disassembler_options);
    170  1.1  christos 
    171  1.1  christos       info->disassembler_options = NULL;
    172  1.1  christos     }
    173  1.1  christos 
    174  1.1  christos   info->symbol_is_valid = wasm32_symbol_is_valid;
    175  1.1  christos }
    176  1.1  christos 
    177  1.1  christos /* Read an LEB128-encoded integer from INFO at address PC, reading one
    178  1.1  christos    byte at a time.  Set ERROR_RETURN if no complete integer could be
    179  1.1  christos    read, LENGTH_RETURN to the number oof bytes read (including bytes
    180  1.1  christos    in incomplete numbers).  SIGN means interpret the number as
    181  1.1  christos    SLEB128.  Unfortunately, this is a duplicate of wasm-module.c's
    182  1.1  christos    wasm_read_leb128 ().  */
    183  1.1  christos 
    184  1.1  christos static uint64_t
    185  1.1  christos wasm_read_leb128 (bfd_vma                   pc,
    186  1.1  christos                   struct disassemble_info * info,
    187  1.1  christos                   bfd_boolean *             error_return,
    188  1.1  christos                   unsigned int *            length_return,
    189  1.1  christos                   bfd_boolean               sign)
    190  1.1  christos {
    191  1.1  christos   uint64_t result = 0;
    192  1.1  christos   unsigned int num_read = 0;
    193  1.1  christos   unsigned int shift = 0;
    194  1.1  christos   unsigned char byte = 0;
    195  1.1  christos   bfd_boolean success = FALSE;
    196  1.1  christos 
    197  1.1  christos   while (info->read_memory_func (pc + num_read, &byte, 1, info) == 0)
    198  1.1  christos     {
    199  1.1  christos       num_read++;
    200  1.1  christos 
    201  1.1  christos       result |= ((bfd_vma) (byte & 0x7f)) << shift;
    202  1.1  christos 
    203  1.1  christos       shift += 7;
    204  1.1  christos       if ((byte & 0x80) == 0)
    205  1.1  christos         {
    206  1.1  christos           success = TRUE;
    207  1.1  christos           break;
    208  1.1  christos         }
    209  1.1  christos     }
    210  1.1  christos 
    211  1.1  christos   if (length_return != NULL)
    212  1.1  christos     *length_return = num_read;
    213  1.1  christos   if (error_return != NULL)
    214  1.1  christos     *error_return = ! success;
    215  1.1  christos 
    216  1.1  christos   if (sign && (shift < 8 * sizeof (result)) && (byte & 0x40))
    217  1.1  christos     result |= -((uint64_t) 1 << shift);
    218  1.1  christos 
    219  1.1  christos   return result;
    220  1.1  christos }
    221  1.1  christos 
    222  1.1  christos /* Read a 32-bit IEEE float from PC using INFO, convert it to a host
    223  1.1  christos    double, and store it at VALUE.  */
    224  1.1  christos 
    225  1.1  christos static int
    226  1.1  christos read_f32 (double *value, bfd_vma pc, struct disassemble_info *info)
    227  1.1  christos {
    228  1.1  christos   bfd_byte buf[4];
    229  1.1  christos 
    230  1.1  christos   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    231  1.1  christos     return -1;
    232  1.1  christos 
    233  1.1  christos   floatformat_to_double (&floatformat_ieee_single_little, buf,
    234  1.1  christos                          value);
    235  1.1  christos 
    236  1.1  christos   return sizeof (buf);
    237  1.1  christos }
    238  1.1  christos 
    239  1.1  christos /* Read a 64-bit IEEE float from PC using INFO, convert it to a host
    240  1.1  christos    double, and store it at VALUE.  */
    241  1.1  christos 
    242  1.1  christos static int
    243  1.1  christos read_f64 (double *value, bfd_vma pc, struct disassemble_info *info)
    244  1.1  christos {
    245  1.1  christos   bfd_byte buf[8];
    246  1.1  christos 
    247  1.1  christos   if (info->read_memory_func (pc, buf, sizeof (buf), info))
    248  1.1  christos     return -1;
    249  1.1  christos 
    250  1.1  christos   floatformat_to_double (&floatformat_ieee_double_little, buf,
    251  1.1  christos                          value);
    252  1.1  christos 
    253  1.1  christos   return sizeof (buf);
    254  1.1  christos }
    255  1.1  christos 
    256  1.1  christos /* Main disassembly routine.  Disassemble insn at PC using INFO.  */
    257  1.1  christos 
    258  1.1  christos int
    259  1.1  christos print_insn_wasm32 (bfd_vma pc, struct disassemble_info *info)
    260  1.1  christos {
    261  1.1  christos   unsigned char opcode;
    262  1.1  christos   struct wasm32_opcode_s *op;
    263  1.1  christos   bfd_byte buffer[16];
    264  1.1  christos   void *stream = info->stream;
    265  1.1  christos   fprintf_ftype prin = info->fprintf_func;
    266  1.1  christos   struct wasm32_private_data *private_data = info->private_data;
    267  1.1  christos   long long constant = 0;
    268  1.1  christos   double fconstant = 0.0;
    269  1.1  christos   long flags = 0;
    270  1.1  christos   long offset = 0;
    271  1.1  christos   long depth = 0;
    272  1.1  christos   long index = 0;
    273  1.1  christos   long target_count = 0;
    274  1.1  christos   long block_type = 0;
    275  1.1  christos   int len = 1;
    276  1.1  christos   int ret = 0;
    277  1.1  christos   unsigned int bytes_read = 0;
    278  1.1  christos   int i;
    279  1.1  christos   const char *locals[] =
    280  1.1  christos     {
    281  1.1  christos       "$dpc", "$sp1", "$r0", "$r1", "$rpc", "$pc0",
    282  1.1  christos       "$rp", "$fp", "$sp",
    283  1.1  christos       "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
    284  1.1  christos       "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i6", "$i7",
    285  1.1  christos       "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7",
    286  1.1  christos     };
    287  1.1  christos   int nlocals = ARRAY_SIZE (locals);
    288  1.1  christos   const char *globals[] =
    289  1.1  christos     {
    290  1.1  christos       "$got", "$plt", "$gpo"
    291  1.1  christos     };
    292  1.1  christos   int nglobals = ARRAY_SIZE (globals);
    293  1.1  christos   bfd_boolean error = FALSE;
    294  1.1  christos 
    295  1.1  christos   if (info->read_memory_func (pc, buffer, 1, info))
    296  1.1  christos     return -1;
    297  1.1  christos 
    298  1.1  christos   opcode = buffer[0];
    299  1.1  christos 
    300  1.1  christos   for (op = wasm32_opcodes; op->name; op++)
    301  1.1  christos     if (op->opcode == opcode)
    302  1.1  christos       break;
    303  1.1  christos 
    304  1.1  christos   if (!op->name)
    305  1.1  christos     {
    306  1.1  christos       prin (stream, "\t.byte 0x%02x\n", buffer[0]);
    307  1.1  christos       return 1;
    308  1.1  christos     }
    309  1.1  christos   else
    310  1.1  christos     {
    311  1.1  christos       len = 1;
    312  1.1  christos 
    313  1.1  christos       prin (stream, "\t");
    314  1.1  christos       prin (stream, "%s", op->name);
    315  1.1  christos 
    316  1.1  christos       if (op->clas == wasm_typed)
    317  1.1  christos         {
    318  1.1  christos           block_type = wasm_read_leb128
    319  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    320  1.1  christos           if (error)
    321  1.1  christos             return -1;
    322  1.1  christos           len += bytes_read;
    323  1.1  christos           switch (block_type)
    324  1.1  christos             {
    325  1.1  christos             case BLOCK_TYPE_NONE:
    326  1.1  christos               prin (stream, "[]");
    327  1.1  christos               break;
    328  1.1  christos             case BLOCK_TYPE_I32:
    329  1.1  christos               prin (stream, "[i]");
    330  1.1  christos               break;
    331  1.1  christos             case BLOCK_TYPE_I64:
    332  1.1  christos               prin (stream, "[l]");
    333  1.1  christos               break;
    334  1.1  christos             case BLOCK_TYPE_F32:
    335  1.1  christos               prin (stream, "[f]");
    336  1.1  christos               break;
    337  1.1  christos             case BLOCK_TYPE_F64:
    338  1.1  christos               prin (stream, "[d]");
    339  1.1  christos               break;
    340  1.1  christos             }
    341  1.1  christos         }
    342  1.1  christos 
    343  1.1  christos       switch (op->clas)
    344  1.1  christos         {
    345  1.1  christos         case wasm_special:
    346  1.1  christos         case wasm_eqz:
    347  1.1  christos         case wasm_binary:
    348  1.1  christos         case wasm_unary:
    349  1.1  christos         case wasm_conv:
    350  1.1  christos         case wasm_relational:
    351  1.1  christos         case wasm_drop:
    352  1.1  christos         case wasm_signature:
    353  1.1  christos         case wasm_call_import:
    354  1.1  christos         case wasm_typed:
    355  1.1  christos         case wasm_select:
    356  1.1  christos           break;
    357  1.1  christos 
    358  1.1  christos         case wasm_break_table:
    359  1.1  christos           target_count = wasm_read_leb128
    360  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    361  1.1  christos           if (error)
    362  1.1  christos             return -1;
    363  1.1  christos           len += bytes_read;
    364  1.1  christos           prin (stream, " %ld", target_count);
    365  1.1  christos           for (i = 0; i < target_count + 1; i++)
    366  1.1  christos             {
    367  1.1  christos               long target = 0;
    368  1.1  christos               target = wasm_read_leb128
    369  1.1  christos                 (pc + len, info, &error, &bytes_read, FALSE);
    370  1.1  christos               if (error)
    371  1.1  christos                 return -1;
    372  1.1  christos               len += bytes_read;
    373  1.1  christos               prin (stream, " %ld", target);
    374  1.1  christos             }
    375  1.1  christos           break;
    376  1.1  christos 
    377  1.1  christos         case wasm_break:
    378  1.1  christos         case wasm_break_if:
    379  1.1  christos           depth = wasm_read_leb128
    380  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    381  1.1  christos           if (error)
    382  1.1  christos             return -1;
    383  1.1  christos           len += bytes_read;
    384  1.1  christos           prin (stream, " %ld", depth);
    385  1.1  christos           break;
    386  1.1  christos 
    387  1.1  christos         case wasm_return:
    388  1.1  christos           break;
    389  1.1  christos 
    390  1.1  christos         case wasm_constant_i32:
    391  1.1  christos         case wasm_constant_i64:
    392  1.1  christos           constant = wasm_read_leb128
    393  1.1  christos             (pc + len, info, &error, &bytes_read, TRUE);
    394  1.1  christos           if (error)
    395  1.1  christos             return -1;
    396  1.1  christos           len += bytes_read;
    397  1.1  christos           prin (stream, " %lld", constant);
    398  1.1  christos           break;
    399  1.1  christos 
    400  1.1  christos         case wasm_constant_f32:
    401  1.1  christos           /* This appears to be the best we can do, even though we're
    402  1.1  christos              using host doubles for WebAssembly floats.  */
    403  1.1  christos           ret = read_f32 (&fconstant, pc + len, info);
    404  1.1  christos           if (ret < 0)
    405  1.1  christos             return -1;
    406  1.1  christos           len += ret;
    407  1.1  christos 	  prin (stream, " %.9g", fconstant);
    408  1.1  christos           break;
    409  1.1  christos 
    410  1.1  christos         case wasm_constant_f64:
    411  1.1  christos           ret = read_f64 (&fconstant, pc + len, info);
    412  1.1  christos           if (ret < 0)
    413  1.1  christos             return -1;
    414  1.1  christos           len += ret;
    415  1.1  christos 	  prin (stream, " %.17g", fconstant);
    416  1.1  christos           break;
    417  1.1  christos 
    418  1.1  christos         case wasm_call:
    419  1.1  christos           index = wasm_read_leb128
    420  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    421  1.1  christos           if (error)
    422  1.1  christos             return -1;
    423  1.1  christos           len += bytes_read;
    424  1.1  christos           prin (stream, " ");
    425  1.1  christos           private_data->section_prefix = ".space.function_index";
    426  1.1  christos           (*info->print_address_func) ((bfd_vma) index, info);
    427  1.1  christos           private_data->section_prefix = NULL;
    428  1.1  christos           break;
    429  1.1  christos 
    430  1.1  christos         case wasm_call_indirect:
    431  1.1  christos           constant = wasm_read_leb128
    432  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    433  1.1  christos           if (error)
    434  1.1  christos             return -1;
    435  1.1  christos           len += bytes_read;
    436  1.1  christos           prin (stream, " %lld", constant);
    437  1.1  christos           constant = wasm_read_leb128
    438  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    439  1.1  christos           if (error)
    440  1.1  christos             return -1;
    441  1.1  christos           len += bytes_read;
    442  1.1  christos           prin (stream, " %lld", constant);
    443  1.1  christos           break;
    444  1.1  christos 
    445  1.1  christos         case wasm_get_local:
    446  1.1  christos         case wasm_set_local:
    447  1.1  christos         case wasm_tee_local:
    448  1.1  christos           constant = wasm_read_leb128
    449  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    450  1.1  christos           if (error)
    451  1.1  christos             return -1;
    452  1.1  christos           len += bytes_read;
    453  1.1  christos           prin (stream, " %lld", constant);
    454  1.1  christos           if (strcmp (op->name + 4, "local") == 0)
    455  1.1  christos             {
    456  1.1  christos               if (private_data->print_registers
    457  1.1  christos                   && constant >= 0 && constant < nlocals)
    458  1.1  christos                 prin (stream, " <%s>", locals[constant]);
    459  1.1  christos             }
    460  1.1  christos           else
    461  1.1  christos             {
    462  1.1  christos               if (private_data->print_well_known_globals
    463  1.1  christos                   && constant >= 0 && constant < nglobals)
    464  1.1  christos                 prin (stream, " <%s>", globals[constant]);
    465  1.1  christos             }
    466  1.1  christos           break;
    467  1.1  christos 
    468  1.1  christos         case wasm_grow_memory:
    469  1.1  christos         case wasm_current_memory:
    470  1.1  christos           constant = wasm_read_leb128
    471  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    472  1.1  christos           if (error)
    473  1.1  christos             return -1;
    474  1.1  christos           len += bytes_read;
    475  1.1  christos           prin (stream, " %lld", constant);
    476  1.1  christos           break;
    477  1.1  christos 
    478  1.1  christos         case wasm_load:
    479  1.1  christos         case wasm_store:
    480  1.1  christos           flags = wasm_read_leb128
    481  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    482  1.1  christos           if (error)
    483  1.1  christos             return -1;
    484  1.1  christos           len += bytes_read;
    485  1.1  christos           offset = wasm_read_leb128
    486  1.1  christos             (pc + len, info, &error, &bytes_read, FALSE);
    487  1.1  christos           if (error)
    488  1.1  christos             return -1;
    489  1.1  christos           len += bytes_read;
    490  1.1  christos           prin (stream, " a=%ld %ld", flags, offset);
    491  1.1  christos         }
    492  1.1  christos     }
    493  1.1  christos   return len;
    494  1.1  christos }
    495  1.1  christos 
    496  1.1  christos /* Print valid disassembler options to STREAM.  */
    497  1.1  christos 
    498  1.1  christos void
    499  1.1  christos print_wasm32_disassembler_options (FILE *stream)
    500  1.1  christos {
    501  1.1  christos   unsigned int i, max_len = 0;
    502  1.1  christos 
    503  1.1  christos   fprintf (stream, _("\
    504  1.1  christos The following WebAssembly-specific disassembler options are supported for use\n\
    505  1.1  christos with the -M switch:\n"));
    506  1.1  christos 
    507  1.1  christos   for (i = 0; i < ARRAY_SIZE (options); i++)
    508  1.1  christos     {
    509  1.1  christos       unsigned int len = strlen (options[i].name);
    510  1.1  christos 
    511  1.1  christos       if (max_len < len)
    512  1.1  christos 	max_len = len;
    513  1.1  christos     }
    514  1.1  christos 
    515  1.1  christos   for (i = 0, max_len++; i < ARRAY_SIZE (options); i++)
    516  1.1  christos     fprintf (stream, "  %s%*c %s\n",
    517  1.1  christos 	     options[i].name,
    518  1.1  christos 	     (int)(max_len - strlen (options[i].name)), ' ',
    519  1.1  christos 	     _(options[i].description));
    520  1.1  christos }
    521